summaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
authorGeorge Fraser <george@fivetran.com>2019-01-01 20:51:24 -0800
committerGeorge Fraser <george@fivetran.com>2019-01-01 20:51:24 -0800
commitb398d1ca3643a6e58aa83c7df98312e538c966c0 (patch)
tree021cdaab4c8ed9416904776b3ab11e450f5f7690 /src/test
parentf3faf409445e82cfa12f9993c4075a5523000c0e (diff)
downloadjava-language-server-b398d1ca3643a6e58aa83c7df98312e538c966c0.zip
Error-prone is too slow
Diffstat (limited to 'src/test')
-rw-r--r--src/test/java/org/javacs/ErrorProneBenchmark.java96
-rw-r--r--src/test/java/org/javacs/JavaCompilerServiceTest.java7
-rw-r--r--src/test/java/org/javacs/StringSearchBenchmark.java44
-rw-r--r--src/test/test-project/workspace/src/org/javacs/example/BenchmarkStringSearch.java195
4 files changed, 318 insertions, 24 deletions
diff --git a/src/test/java/org/javacs/ErrorProneBenchmark.java b/src/test/java/org/javacs/ErrorProneBenchmark.java
new file mode 100644
index 0000000..6e0b7ba
--- /dev/null
+++ b/src/test/java/org/javacs/ErrorProneBenchmark.java
@@ -0,0 +1,96 @@
+package org.javacs;
+
+import com.sun.source.util.JavacTask;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.concurrent.TimeUnit;
+import javax.tools.JavaCompiler;
+import javax.tools.StandardJavaFileManager;
+import org.openjdk.jmh.annotations.*;
+
+// TODO this is coloring badly
+@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(1)
+public class ErrorProneBenchmark {
+ private static Path file = Paths.get(FindResource.uri("/org/javacs/example/BenchmarkStringSearch.java"));
+
+ @State(Scope.Thread)
+ public static class BenchmarkState {
+ JavaCompiler compiler = ServiceLoader.load(JavaCompiler.class).iterator().next();
+ StandardJavaFileManager fileManager =
+ new FileManagerWrapper(compiler.getStandardFileManager(__ -> {}, null, Charset.defaultCharset()));
+ }
+
+ @Benchmark
+ public void vanilla(BenchmarkState state) {
+ var options = JavaCompilerService.options(Set.of(), Set.of());
+
+ analyze(state, options);
+ }
+
+ @Benchmark
+ public void withErrorProne(BenchmarkState state) {
+ var options = JavaCompilerService.options(Set.of(), Set.of());
+ // Add error-prone
+ options.addAll(errorProneOptions());
+
+ analyze(state, options);
+ }
+
+ private void analyze(BenchmarkState state, List<String> options) {
+ var sources = state.fileManager.getJavaFileObjectsFromPaths(List.of(file));
+ // Create task
+ var task =
+ (JavacTask)
+ state.compiler.getTask(
+ null, state.fileManager, __ -> {}, options, Collections.emptyList(), sources);
+ // Print timing information for optimization
+ var profiler = new Profiler();
+ try {
+ task.analyze();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static List<String> errorProneOptions() {
+ var options = new ArrayList<String>();
+
+ // https://errorprone.info/docs/installation "Command Line"
+ Collections.addAll(options, "-XDcompilePolicy=byfile");
+ Collections.addAll(options, "-processorpath", Lib.ERROR_PRONE);
+
+ // https://errorprone.info/bugpatterns
+ var bugPatterns = new StringJoiner(" ");
+ // bugPatterns.add("-Xep:EmptyIf");
+ // bugPatterns.add("-Xep:NumericEquality");
+ // bugPatterns.add("-Xep:ConstructorLeaksThis");
+ // bugPatterns.add("-Xep:EqualsBrokenForNull");
+ // bugPatterns.add("-Xep:InvalidThrows");
+ // bugPatterns.add("-Xep:RedundantThrows");
+ // bugPatterns.add("-Xep:StaticQualifiedUsingExpression");
+ // bugPatterns.add("-Xep:StringEquality");
+ // bugPatterns.add("-Xep:Unused");
+ // bugPatterns.add("-Xep:UnusedException");
+ // bugPatterns.add("-Xep:FieldCanBeFinal");
+ // bugPatterns.add("-Xep:FieldMissingNullable");
+ // bugPatterns.add("-Xep:MethodCanBeStatic");
+ // bugPatterns.add("-Xep:PackageLocation");
+ // bugPatterns.add("-Xep:PrivateConstructorForUtilityClass");
+ // bugPatterns.add("-Xep:ReturnMissingNullable");
+
+ Collections.addAll(
+ options, "-Xplugin:ErrorProne -XepAllErrorsAsWarnings " + bugPatterns + " --illegal-access=warn");
+
+ return options;
+ }
+}
diff --git a/src/test/java/org/javacs/JavaCompilerServiceTest.java b/src/test/java/org/javacs/JavaCompilerServiceTest.java
index e06e72a..0785acb 100644
--- a/src/test/java/org/javacs/JavaCompilerServiceTest.java
+++ b/src/test/java/org/javacs/JavaCompilerServiceTest.java
@@ -18,6 +18,7 @@ import java.util.stream.Collectors;
import javax.lang.model.element.Element;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
+import org.junit.Ignore;
import org.junit.Test;
public class JavaCompilerServiceTest {
@@ -300,10 +301,10 @@ public class JavaCompilerServiceTest {
return strings;
}
+ // TODO get these back somehow
@Test
+ @Ignore
public void errorProne() {
- // TODO verify that error-prone *only* runs when you call reportErrors(),
- // by calling compileFile() and checking no diagnostic is reported
var uri = resourceUri("ErrorProne.java");
var files = Collections.singleton(uri);
var diags = compiler.reportErrors(files);
@@ -311,7 +312,9 @@ public class JavaCompilerServiceTest {
assertThat(strings, hasItem(containsString("ErrorProne.java(7): [CollectionIncompatibleType]")));
}
+ // TODO get these back somehow
@Test
+ @Ignore
public void unusedVar() {
var uri = resourceUri("UnusedVar.java");
var files = Collections.singleton(uri);
diff --git a/src/test/java/org/javacs/StringSearchBenchmark.java b/src/test/java/org/javacs/StringSearchBenchmark.java
index 92abbbf..e044812 100644
--- a/src/test/java/org/javacs/StringSearchBenchmark.java
+++ b/src/test/java/org/javacs/StringSearchBenchmark.java
@@ -14,31 +14,31 @@ public class StringSearchBenchmark {
smallFile = Paths.get(FindResource.uri("/org/javacs/example/Goto.java"));
// "removeMethodBodies" appears late in the file, so stopping early will not be very effective
private static final String query = "removeMethodBodies";
- /*
- @Benchmark
- public void containsWordMatchingSmall() {
- var found = Parser.containsWordMatching(smallFile, query);
- assert found;
- }
- @Benchmark
- public void containsWordMatchingLarge() {
- var found = Parser.containsWordMatching(largeFile, query);
- assert found;
- }
+ @Benchmark
+ public void containsWordMatchingSmall() {
+ var found = Parser.containsWordMatching(smallFile, query);
+ assert found;
+ }
+
+ @Benchmark
+ public void containsWordMatchingLarge() {
+ var found = Parser.containsWordMatching(largeFile, query);
+ assert found;
+ }
- @Benchmark
- public void containsTextSmall() {
- var found = Parser.containsText(smallFile, query);
- assert found;
- }
+ @Benchmark
+ public void containsTextSmall() {
+ var found = Parser.containsText(smallFile, query);
+ assert found;
+ }
+
+ @Benchmark
+ public void containsTextLarge() {
+ var found = Parser.containsText(largeFile, query);
+ assert found;
+ }
- @Benchmark
- public void containsTextLarge() {
- var found = Parser.containsText(largeFile, query);
- assert found;
- }
- */
@Benchmark
public void containsImportLarge() {
var found = JavaCompilerService.containsImport("java.util.nopkg", "Logger", largeFile);
diff --git a/src/test/test-project/workspace/src/org/javacs/example/BenchmarkStringSearch.java b/src/test/test-project/workspace/src/org/javacs/example/BenchmarkStringSearch.java
new file mode 100644
index 0000000..218d766
--- /dev/null
+++ b/src/test/test-project/workspace/src/org/javacs/example/BenchmarkStringSearch.java
@@ -0,0 +1,195 @@
+package org.javacs.example;
+
+import java.nio.ByteBuffer;
+
+// Translated from https://golang.org/src/strings/search.go
+
+// Search efficiently finds strings in a source text. It's implemented
+// using the Boyer-Moore string search algorithm:
+// https://en.wikipedia.org/wiki/Boyer-Moore_string_search_algorithm
+// https://www.cs.utexas.edu/~moore/publications/fstrpos.pdf (note: this aged
+// document uses 1-based indexing)
+class BenchmarkStringSearch {
+ // pattern is the string that we are searching for in the text.
+ private final byte[] pattern;
+
+ // badCharSkip[b] contains the distance between the last byte of pattern
+ // and the rightmost occurrence of b in pattern. If b is not in pattern,
+ // badCharSkip[b] is len(pattern).
+ //
+ // Whenever a mismatch is found with byte b in the text, we can safely
+ // shift the matching frame at least badCharSkip[b] until the next time
+ // the matching char could be in alignment.
+ // TODO 256 is not coloring
+ private final int[] badCharSkip = new int[256];
+
+ // goodSuffixSkip[i] defines how far we can shift the matching frame given
+ // that the suffix pattern[i+1:] matches, but the byte pattern[i] does
+ // not. There are two cases to consider:
+ //
+ // 1. The matched suffix occurs elsewhere in pattern (with a different
+ // byte preceding it that we might possibly match). In this case, we can
+ // shift the matching frame to align with the next suffix chunk. For
+ // example, the pattern "mississi" has the suffix "issi" next occurring
+ // (in right-to-left order) at index 1, so goodSuffixSkip[3] ==
+ // shift+len(suffix) == 3+4 == 7.
+ //
+ // 2. If the matched suffix does not occur elsewhere in pattern, then the
+ // matching frame may share part of its prefix with the end of the
+ // matching suffix. In this case, goodSuffixSkip[i] will contain how far
+ // to shift the frame to align this portion of the prefix to the
+ // suffix. For example, in the pattern "abcxxxabc", when the first
+ // mismatch from the back is found to be in position 3, the matching
+ // suffix "xxabc" is not found elsewhere in the pattern. However, its
+ // rightmost "abc" (at position 6) is a prefix of the whole pattern, so
+ // goodSuffixSkip[3] == shift+len(suffix) == 6+5 == 11.
+ private final int[] goodSuffixSkip;
+
+ BenchmarkStringSearch(String pattern) {
+ this(pattern.getBytes());
+ }
+
+ BenchmarkStringSearch(byte[] pattern) {
+ this.pattern = pattern;
+ this.goodSuffixSkip = new int[pattern.length];
+
+ // last is the index of the last character in the pattern.
+ var last = pattern.length - 1;
+
+ // Build bad character table.
+ // Bytes not in the pattern can skip one pattern's length.
+ for (var i = 0; i < badCharSkip.length; i++) {
+ badCharSkip[i] = pattern.length;
+ }
+ // The loop condition is < instead of <= so that the last byte does not
+ // have a zero distance to itself. Finding this byte out of place implies
+ // that it is not in the last position.
+ for (var i = 0; i < last; i++) {
+ badCharSkip[pattern[i] + 128] = last - i;
+ }
+
+ // Build good suffix table.
+ // First pass: set each value to the next index which starts a prefix of
+ // pattern.
+ var lastPrefix = last;
+ for (var i = last; i >= 0; i--) {
+ if (hasPrefix(pattern, new Slice(pattern, i + 1))) lastPrefix = i + 1;
+ // lastPrefix is the shift, and (last-i) is len(suffix).
+ goodSuffixSkip[i] = lastPrefix + last - i;
+ }
+ // Second pass: find repeats of pattern's suffix starting from the front.
+ for (var i = 0; i < last; i++) {
+ var lenSuffix = longestCommonSuffix(pattern, new Slice(pattern, 1, i + 1));
+ if (pattern[i - lenSuffix] != pattern[last - lenSuffix]) {
+ // (last-i) is the shift, and lenSuffix is len(suffix).
+ goodSuffixSkip[last - lenSuffix] = lenSuffix + last - i;
+ }
+ }
+ }
+
+ int next(String text) {
+ return next(text.getBytes());
+ }
+
+ int next(byte[] text) {
+ return next(ByteBuffer.wrap(text));
+ }
+
+ int next(ByteBuffer text) {
+ return next(text, 0);
+ }
+
+ int next(ByteBuffer text, int startingAfter) {
+ var i = startingAfter + pattern.length - 1;
+ while (i < text.limit()) {
+ // Compare backwards from the end until the first unmatching character.
+ var j = pattern.length - 1;
+ while (j >= 0 && text.get(i) == pattern[j]) {
+ i--;
+ j--;
+ }
+ if (j < 0) {
+ return i + 1; // match
+ }
+ i += Math.max(badCharSkip[text.get(i) + 128], goodSuffixSkip[j]);
+ }
+ return -1;
+ }
+
+ boolean isWordChar(byte b) {
+ char c = (char) (b + 128);
+ return Character.isAlphabetic(c) || Character.isDigit(c) || c == '$' || c == '_';
+ }
+
+ boolean startsWord(ByteBuffer text, int offset) {
+ if (offset == 0) return true;
+ return !isWordChar(text.get(offset - 1));
+ }
+
+ boolean endsWord(ByteBuffer text, int offset) {
+ if (offset + 1 >= text.limit()) return true;
+ return !isWordChar(text.get(offset + 1));
+ }
+
+ boolean isWord(ByteBuffer text, int offset) {
+ return startsWord(text, offset) && endsWord(text, offset + pattern.length - 1);
+ }
+
+ int nextWord(String text) {
+ return nextWord(text.getBytes());
+ }
+
+ int nextWord(byte[] text) {
+ return nextWord(ByteBuffer.wrap(text));
+ }
+
+ int nextWord(ByteBuffer text) {
+ var i = 0;
+ while (true) {
+ i = next(text, i);
+ if (i == -1) return -1;
+ if (isWord(text, i)) return i;
+ i++;
+ }
+ }
+
+ private boolean hasPrefix(byte[] s, Slice prefix) {
+ for (int i = 0; i < prefix.length(); i++) {
+ if (s[i] != prefix.get(i)) return false;
+ }
+ return true;
+ }
+
+ private int longestCommonSuffix(byte[] a, Slice b) {
+ int i = 0;
+ for (; i < a.length && i < b.length(); i++) {
+ if (a[a.length - 1 - i] != b.get(b.length() - 1 - i)) {
+ break;
+ }
+ }
+ return i;
+ }
+
+ private static class Slice {
+ private final byte[] target;
+ private int from, until;
+
+ int length() {
+ return until - from;
+ }
+
+ byte get(int i) {
+ return target[from + i];
+ }
+
+ public Slice(byte[] target, int from) {
+ this(target, from, target.length);
+ }
+
+ public Slice(byte[] target, int from, int until) {
+ this.target = target;
+ this.from = from;
+ this.until = until;
+ }
+ }
+}