Merge "Fix coverage filters" into ub-jack
diff --git a/jack/src/com/android/jack/coverage/CodeCoverageSelector.java b/jack/src/com/android/jack/coverage/CodeCoverageSelector.java
index 786bd29..c95a2ea 100644
--- a/jack/src/com/android/jack/coverage/CodeCoverageSelector.java
+++ b/jack/src/com/android/jack/coverage/CodeCoverageSelector.java
@@ -80,15 +80,9 @@
   /**
    * A {@link CoverageFilter} singleton used to cache include and exclude properties.
    */
-  private static CoverageFilter singleton = null;
-
-  private static CoverageFilter getFilterInstance() {
-    if (singleton == null) {
-      singleton = new CoverageFilter(
-          ThreadConfig.get(COVERAGE_JACOCO_INCLUDES), ThreadConfig.get(COVERAGE_JACOCO_EXCLUDES));
-    }
-    return singleton;
-  }
+  @Nonnull
+  private final CoverageFilter filter = new CoverageFilter(
+      ThreadConfig.get(COVERAGE_JACOCO_INCLUDES), ThreadConfig.get(COVERAGE_JACOCO_EXCLUDES));
 
   @Override
   public void run(@Nonnull JDefinedClassOrInterface t) throws Exception {
@@ -98,7 +92,7 @@
     }
   }
 
-  private static boolean needsCoverage(@Nonnull JDefinedClassOrInterface declaredType) {
+  private boolean needsCoverage(@Nonnull JDefinedClassOrInterface declaredType) {
     if (declaredType.isExternal()) {
       // Do not instrument classes that will no be part of the output.
       return false;
@@ -108,7 +102,6 @@
       return false;
     }
     // Manage class filtering.
-    CoverageFilter filter = getFilterInstance();
     String typeName = formatter.getName(declaredType);
     return filter.matches(typeName);
   }
diff --git a/jack/src/com/android/jack/coverage/CoverageFilter.java b/jack/src/com/android/jack/coverage/CoverageFilter.java
index a0f5dd4..c025a80 100644
--- a/jack/src/com/android/jack/coverage/CoverageFilter.java
+++ b/jack/src/com/android/jack/coverage/CoverageFilter.java
@@ -44,10 +44,10 @@
   private final CoverageFilterSet excludes;
 
   public CoverageFilter(@Nonnull CoverageFilterSet includes, @Nonnull CoverageFilterSet excludes) {
-    this.includes = includes;
-    this.excludes = excludes;
+    this.includes = includes.makeCopy();
+    this.excludes = excludes.makeCopy();
     for (String packageName : EXCLUDED_PACKAGES) {
-      excludes.addPattern(new CoveragePattern(packageName));
+      this.excludes.addPattern(new CoveragePattern(packageName));
     }
   }
 
diff --git a/jack/src/com/android/jack/coverage/CoverageFilterSet.java b/jack/src/com/android/jack/coverage/CoverageFilterSet.java
index 8002c93..c0b83ff 100644
--- a/jack/src/com/android/jack/coverage/CoverageFilterSet.java
+++ b/jack/src/com/android/jack/coverage/CoverageFilterSet.java
@@ -50,7 +50,7 @@
   }
 
   public boolean isEmpty() {
-    return patterns.size() == 0;
+    return patterns.isEmpty();
   }
 
   /**
@@ -67,4 +67,17 @@
     }
     return false;
   }
+
+  /**
+   * Returns a copy of this {@link CoverageFilterSet} with the same patterns.
+   *
+   * @return a {@link CoverageFilterSet}
+   */
+  public CoverageFilterSet makeCopy() {
+    CoverageFilterSet copy = new CoverageFilterSet();
+    for (CoveragePattern cp : getPatterns()) {
+      copy.addPattern(cp);
+    }
+    return copy;
+  }
 }
diff --git a/jack/src/com/android/jack/coverage/CoverageFilterSetCodec.java b/jack/src/com/android/jack/coverage/CoverageFilterSetCodec.java
index dad1623..54e33c9 100644
--- a/jack/src/com/android/jack/coverage/CoverageFilterSetCodec.java
+++ b/jack/src/com/android/jack/coverage/CoverageFilterSetCodec.java
@@ -51,7 +51,11 @@
   public CoverageFilterSet checkString(@Nonnull CodecContext context, @Nonnull String string)
       throws ParsingException {
     List<CoveragePattern> patterns = parser.checkString(context, string);
-    return createFromPatterns(patterns);
+    if (patterns == null) {
+      return null;
+    } else {
+      return createFromPatterns(patterns);
+    }
   }
 
   private static CoverageFilterSet createFromPatterns(
diff --git a/jack/tests/com/android/jack/coverage/CoverageFilterSetCodecTest.java b/jack/tests/com/android/jack/coverage/CoverageFilterSetCodecTest.java
index da1063c..06fa0a4 100644
--- a/jack/tests/com/android/jack/coverage/CoverageFilterSetCodecTest.java
+++ b/jack/tests/com/android/jack/coverage/CoverageFilterSetCodecTest.java
@@ -24,30 +24,92 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class CoverageFilterSetCodecTest {
   private CoverageFilterSetCodec codec;
   private CodecContext codecContext;
+  private List<PatternTest> patternTests;
+
+  static class PatternTest {
+    public PatternTest(String pattern, String[] matchStrings, String[] noMatchStrings) {
+      this.pattern = pattern;
+      this.matchStrings = matchStrings;
+      this.noMatchStrings = noMatchStrings;
+    }
+
+    public String describe() {
+      StringBuilder sb = new StringBuilder();
+      sb.append("Pattern: \"");
+      sb.append(pattern);
+      sb.append("\", ");
+      appendStrings(sb, "matchStrings", matchStrings);
+      sb.append(", ");
+      appendStrings(sb, "noMatchStrings", noMatchStrings);
+      return sb.toString();
+    }
+
+    private static void appendStrings(StringBuilder sb, String name, String[] strings) {
+      sb.append(name);
+      sb.append("={");
+      boolean first = true;
+      for (String s : strings) {
+        if (!first) {
+          sb.append(',');
+        }
+        first = false;
+        sb.append('\"');
+        sb.append(s);
+        sb.append('\"');
+      }
+      sb.append("}");
+    }
+    private final String pattern;
+    private final String[] matchStrings;
+    private final String[] noMatchStrings;
+  }
 
   @Before
   public void setUp() {
     codec = new CoverageFilterSetCodec();
     codecContext = new CodecContext();
+    patternTests = new ArrayList<CoverageFilterSetCodecTest.PatternTest>();
+    // Single pattern testing.
+    addPatternTest("", new String[0], new String[]{"a", "foo", "*", "???"});
+    addPatternTest("*", new String[]{"foo", "bar", "foo.bar"}, new String[0]);
+    addPatternTest("?", new String[]{"", "a"}, new String[]{"foo", "bar"});
+    addPatternTest("???", new String[]{"foo", "bar", "", "a", "aa"}, new String[]{"aaaa"});
+    addPatternTest("foo", new String[]{"foo"}, new String[]{"bar", "*", "???"});
+    addPatternTest("bar", new String[]{"bar"}, new String[]{"foo", "*", "???"});
+    addPatternTest("foo.bar", new String[]{"foo.bar"}, new String[]{"foo", "bar", "*", "???"});
+    // Multiple patterns testing.
+    addPatternTest("foo,bar", new String[]{"foo", "bar"}, new String[]{"foo.bar", "*", "???"});
+    addPatternTest("foo, bar", new String[]{"foo", "bar"}, new String[]{"foo.bar", "*", "???"});
+    addPatternTest(" foo, bar ", new String[]{"foo", "bar"}, new String[]{"foo.bar", "*", "???"});
+    addPatternTest("foo,*,bar", new String[]{"foo", "bar", "foo.bar"}, new String[0]);
+    addPatternTest("*,?", new String[]{"a", "aa"}, new String[0]);
+  }
+
+  private void addPatternTest(String pattern, String[] matchStrings, String[] noMatchStrings) {
+    patternTests.add(new PatternTest(pattern, matchStrings, noMatchStrings));
   }
 
   @Test
   public void testCodec_checkString() throws ParsingException {
-    codec.checkString(codecContext, "foo");
-    codec.checkString(codecContext, "foo.bar");
-    codec.checkString(codecContext, "foo.bar$inner");
-    codec.checkString(codecContext, "foo.bar$8");
-
-    codec.checkString(codecContext, "*");
-    codec.checkString(codecContext, "foo.bar.*");
-    codec.checkString(codecContext, "*foo*");
-
-    codec.checkString(codecContext, "?");
-    codec.checkString(codecContext, "foo.bar.?");
-    codec.checkString(codecContext, "?foo?");
+    for (PatternTest t : patternTests) {
+      CoverageFilterSet cfs = codec.checkString(codecContext, t.pattern);
+      Assert.assertNotNull(cfs);
+      for (CoveragePattern p : cfs.getPatterns()) {
+        Assert.assertNotNull(p);
+      }
+      for (String matchString : t.matchStrings) {
+        Assert.assertTrue(t.describe(), cfs.matchesAny(matchString));
+      }
+      for (String noMatchString : t.noMatchStrings) {
+        Assert.assertFalse(t.describe(), cfs.matchesAny(noMatchString));
+      }
+    }
 
     try {
       codec.checkString(codecContext, "foo/bar");
@@ -64,25 +126,19 @@
 
   @Test
   public void testParse() {
-    CoverageFilterSet filter = codec.parseString(codecContext, "a");
-    Assert.assertTrue(filter.matchesAny("a"));
-    Assert.assertFalse(filter.matchesAny("ab"));
-
-    filter = codec.parseString(codecContext, "ab");
-    Assert.assertFalse(filter.matchesAny("a"));
-    Assert.assertTrue(filter.matchesAny("ab"));
-
-    filter = codec.parseString(codecContext, "?");
-    Assert.assertTrue(filter.matchesAny("a"));
-    Assert.assertFalse(filter.matchesAny("ab"));
-
-    filter = codec.parseString(codecContext, "??");
-    Assert.assertTrue(filter.matchesAny("a"));
-    Assert.assertTrue(filter.matchesAny("ab"));
-
-    filter = codec.parseString(codecContext, "*");
-    Assert.assertTrue(filter.matchesAny("a"));
-    Assert.assertTrue(filter.matchesAny("ab"));
+    for (PatternTest t : patternTests) {
+      CoverageFilterSet cfs = codec.parseString(codecContext, t.pattern);
+      Assert.assertNotNull(cfs);
+      for (CoveragePattern p : cfs.getPatterns()) {
+        Assert.assertNotNull(p);
+      }
+      for (String matchString : t.matchStrings) {
+        Assert.assertTrue(t.describe(), cfs.matchesAny(matchString));
+      }
+      for (String noMatchString : t.noMatchStrings) {
+        Assert.assertFalse(t.describe(), cfs.matchesAny(noMatchString));
+      }
+    }
   }
 
   @Test