Added some quality measures to the completion stress test

Change-Id: I6d1a90fdabda2db33cc96f84fe8ee1b8aba98d34
Reviewed-on: https://dart-review.googlesource.com/69282
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/test/stress/completion/completion.dart b/pkg/analysis_server/test/stress/completion/completion.dart
index 9581943..a16fb14 100644
--- a/pkg/analysis_server/test/stress/completion/completion.dart
+++ b/pkg/analysis_server/test/stress/completion/completion.dart
@@ -21,11 +21,12 @@
     CompletionRunner runner = new CompletionRunner(
         output: stdout,
         printMissing: result['missing'],
+        printQuality: result['quality'],
         timing: result['timing'],
         useCFE: result['use-cfe'],
         verbose: result['verbose']);
     await runner.runAll(analysisRoot);
-    stdout.flush();
+    await stdout.flush();
   }
 }
 
@@ -46,6 +47,11 @@
     negatable: false,
   );
   parser.addFlag(
+    'quality',
+    help: 'Report on the quality of the sort order',
+    negatable: false,
+  );
+  parser.addFlag(
     'timing',
     help: 'Report timing information',
     negatable: false,
diff --git a/pkg/analysis_server/test/stress/completion/completion_runner.dart b/pkg/analysis_server/test/stress/completion/completion_runner.dart
index 2f49a24..886a07d 100644
--- a/pkg/analysis_server/test/stress/completion/completion_runner.dart
+++ b/pkg/analysis_server/test/stress/completion/completion_runner.dart
@@ -35,6 +35,12 @@
   final bool printMissing;
 
   /**
+   * A flag indicating whether to produce output about the quality of the sort
+   * order.
+   */
+  final bool printQuality;
+
+  /**
    * A flag indicating whether to produce timing information.
    */
   final bool timing;
@@ -61,11 +67,13 @@
   CompletionRunner(
       {StringSink output,
       bool printMissing,
+      bool printQuality,
       bool timing,
       bool useCFE,
       bool verbose})
       : this.output = output ?? new NullStringSink(),
         this.printMissing = printMissing ?? false,
+        this.printQuality = printQuality ?? false,
         this.timing = timing ?? false,
         this.useCFE = useCFE ?? false,
         this.verbose = verbose ?? false;
@@ -87,7 +95,10 @@
 
     int fileCount = 0;
     int identifierCount = 0;
+    int expectedCount = 0;
     int missingCount = 0;
+    List<int> indexCount = new List.filled(20, 0);
+    List<int> filteredIndexCount = new List.filled(20, 0);
 
     // Consider getting individual timings so that we can also report the
     // longest and shortest times, or even a distribution.
@@ -95,6 +106,9 @@
 
     for (AnalysisContext context in collection.contexts) {
       for (String path in context.contextRoot.analyzedFiles()) {
+        if (!path.endsWith('.dart')) {
+          continue;
+        }
         fileCount++;
         output.write('.');
         ResolveResult result =
@@ -123,7 +137,10 @@
 
           if (!identifier.inDeclarationContext() &&
               !_isNamedExpressionName(identifier)) {
-            if (!_hasSuggestion(suggestions, identifier.name)) {
+            expectedCount++;
+            suggestions = _sort(suggestions.toList());
+            int index = _indexOf(suggestions, identifier.name);
+            if (index < 0) {
               missingCount++;
               if (printMissing) {
                 CharacterLocation location = lineInfo.getLocation(offset);
@@ -133,6 +150,17 @@
                   _printSuggestions(suggestions);
                 }
               }
+            } else if (printQuality) {
+              if (index < indexCount.length) {
+                indexCount[index]++;
+              }
+              List<CompletionSuggestion> filteredSuggestions =
+                  _filterBy(suggestions, identifier.name.substring(0, 1));
+              int filteredIndex =
+                  _indexOf(filteredSuggestions, identifier.name);
+              if (filteredIndex < filteredIndexCount.length) {
+                filteredIndexCount[filteredIndex]++;
+              }
             }
           }
         }
@@ -145,12 +173,53 @@
     if (printMissing) {
       output.writeln();
     }
-    if (identifierCount == 0) {
-      output.writeln('No identifiers found in $fileCount files');
-    } else {
-      int percent = (missingCount * 100 / identifierCount).round();
-      output.writeln('$percent% missing suggestions '
-          '($missingCount of $identifierCount in $fileCount files)');
+    output.writeln('Found $identifierCount identifiers in $fileCount files');
+    if (expectedCount > 0) {
+      output.writeln('  $expectedCount were expected to code complete');
+      if (printQuality) {
+        int percent = (missingCount * 100 / expectedCount).round();
+        output.writeln('  $percent% of which were missing suggestions '
+            '($missingCount)');
+
+        int foundCount = expectedCount - missingCount;
+
+        void printCount(int count) {
+          if (count < 10) {
+            output.write('      $count  ');
+          } else if (count < 100) {
+            output.write('     $count  ');
+          } else if (count < 1000) {
+            output.write('    $count  ');
+          } else if (count < 10000) {
+            output.write('   $count  ');
+          } else {
+            output.write('  $count  ');
+          }
+          int percent = (count * 100 / foundCount).floor();
+          for (int j = 0; j < percent; j++) {
+            output.write('-');
+          }
+          output.writeln();
+        }
+
+        void _printCounts(List<int> counts) {
+          int nearTopCount = 0;
+          for (int i = 0; i < counts.length; i++) {
+            int count = counts[i];
+            printCount(count);
+            nearTopCount += count;
+          }
+          printCount(foundCount - nearTopCount);
+        }
+
+        output.writeln();
+        output.writeln('By position in the list');
+        _printCounts(indexCount);
+        output.writeln();
+        output.writeln('By position in the list (filtered by first character)');
+        _printCounts(filteredIndexCount);
+        output.writeln();
+      }
     }
     if (timing && identifierCount > 0) {
       int time = timer.elapsedMilliseconds;
@@ -160,18 +229,11 @@
     }
   }
 
-  /**
-   * Return `true` if the given list of [suggestions] includes a suggestion for
-   * the given [identifier].
-   */
-  bool _hasSuggestion(
-      List<CompletionSuggestion> suggestions, String identifier) {
-    for (CompletionSuggestion suggestion in suggestions) {
-      if (suggestion.completion == identifier) {
-        return true;
-      }
-    }
-    return false;
+  List<CompletionSuggestion> _filterBy(
+      List<CompletionSuggestion> suggestions, String pattern) {
+    return suggestions
+        .where((suggestion) => suggestion.completion.startsWith(pattern))
+        .toList();
   }
 
   /**
@@ -185,6 +247,19 @@
   }
 
   /**
+   * If the given list of [suggestions] includes a suggestion for the given
+   * [identifier], return the index of the suggestion. Otherwise, return `-1`.
+   */
+  int _indexOf(List<CompletionSuggestion> suggestions, String identifier) {
+    for (int i = 0; i < suggestions.length; i++) {
+      if (suggestions[i].completion == identifier) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  /**
    * Return `true` if the given [identifier] is being used as the name of a
    * named expression.
    */
@@ -206,6 +281,11 @@
       output.writeln('    ${suggestion.completion}');
     }
   }
+
+  List<CompletionSuggestion> _sort(List<CompletionSuggestion> suggestions) {
+    suggestions.sort((first, second) => second.relevance - first.relevance);
+    return suggestions;
+  }
 }
 
 /**