JAL-4199 Test URL settings and basic service discovery
authorMateusz Warowny <mmzwarowny@dundee.ac.uk>
Wed, 14 Jun 2023 14:25:28 +0000 (16:25 +0200)
committerMateusz Warowny <mmzwarowny@dundee.ac.uk>
Wed, 14 Jun 2023 14:27:18 +0000 (16:27 +0200)
src/jalview/ws2/client/slivka/SlivkaWSDiscoverer.java
test/jalview/ws2/client/slivka/SlivkaWSDiscovererTest.java
test/jalview/ws2/client/slivka/default.jvprops [new file with mode: 0644]
utils/testnglibs/hamcrest-2.2-sources.jar [new file with mode: 0644]
utils/testnglibs/hamcrest-2.2.jar [new file with mode: 0644]

index 2509a19..51e27ad 100644 (file)
@@ -231,10 +231,9 @@ public class SlivkaWSDiscoverer extends AbstractWebServiceDiscoverer
     for (String classifier : service.getClassifiers())
     {
       String[] path = classifier.split("\\s*::\\s*");
-      if (path.length < 3 || !path[0].equalsIgnoreCase("operation") ||
-          !path[1].equalsIgnoreCase("analysis"))
+      if (path.length < 3 || !path[0].equalsIgnoreCase("operation"))
         continue;
-      // classifier is operation :: analysis :: *
+      // classifier is operation :: *
       var tail = path[path.length - 1].toLowerCase();
       switch (tail)
       {
index 5519156..a656f14 100644 (file)
 package jalview.ws2.client.slivka;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Function;
 
 import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
+import jalview.bin.Cache;
+import jalview.bin.Console;
+import jalview.ws2.actions.alignment.AlignmentAction;
+import jalview.ws2.actions.annotation.AnnotationAction;
+import jalview.ws2.client.api.WebServiceDiscovererI;
+import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
+import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
+
 public class SlivkaWSDiscovererTest
 {
-  @BeforeClass
-  public void setupClass() throws IOException
+  private static final String URLS_PROPERTY_NAME = "SLIVKAHOSTURLS";
+
+  SlivkaClient clientMock;
+
+  Function<URL, SlivkaClient> factoryMock;
+
+  @BeforeClass(alwaysRun = true)
+  public void setupProperties()
+  {
+    Cache.loadProperties("test/jalview/ws2/client/slivka/default.jvprops");
+    Console.initLogger();
+  }
+
+  @BeforeMethod
+  public void setupDiscoverer() throws IOException
+  {
+    clientMock = mock(SlivkaClient.class);
+  }
+
+  @Test
+  public void getStatusForUrl_servicesReturned_statusIsOK() throws Exception
+  {
+    when(clientMock.getServices())
+            .thenReturn(List.of(mock(SlivkaService.class)));
+    var discoverer = new SlivkaWSDiscoverer(
+            url -> url.toString().equals("http://example.org") ? clientMock
+                    : null);
+    assertThat(discoverer.getStatusForUrl(new URL("http://example.org")),
+            is(WebServiceDiscovererI.STATUS_OK));
+  }
+
+  @Test
+  public void getStatusForUrl_noServicesReturned_statusIsNoServices()
+          throws Exception
+  {
+    when(clientMock.getServices()).thenReturn(List.of());
+    var discoverer = new SlivkaWSDiscoverer(
+            url -> url.toString().equals("http://example.org") ? clientMock
+                    : null);
+    assertThat(discoverer.getStatusForUrl(new URL("http://example.org")),
+            is(WebServiceDiscovererI.STATUS_NO_SERVICES));
+  }
+
+  @Test
+  public void getStatusForUrl_exceptionThrown_statusIsInvalid()
+          throws Exception
+  {
+    when(clientMock.getServices()).thenThrow(new IOException());
+    var discoverer = new SlivkaWSDiscoverer(
+            url -> url.toString().equals("http://example.org") ? clientMock
+                    : null);
+    assertThat(discoverer.getStatusForUrl(new URL("http://example.org")),
+            is(WebServiceDiscovererI.STATUS_INVALID));
+  }
+
+  @Test
+  public void testGetUrls_noPropEntry_defaultUrlReturned()
+          throws MalformedURLException
   {
     var discoverer = SlivkaWSDiscoverer.getInstance();
-    
+    assertThat(discoverer.getUrls(),
+            contains(new URL("https://www.compbio.dundee.ac.uk/slivka/")));
   }
-  
+
+  @DataProvider
+  public Object[][] urlPropertyValues() throws MalformedURLException
+  {
+    return new Object[][] {
+        { "http://example.org/", List.of(new URL("http://example.org/")) },
+        { "https://example.org/slivka/",
+            List.of(new URL("https://example.org/slivka/")) },
+        { "https://www.compbio.dundee.ac.uk/,http://www.example.org/",
+            List.of(new URL("https://www.compbio.dundee.ac.uk/"),
+                    new URL("http://www.example.org/")) },
+        { "http://example.org/,", List.of(new URL("http://example.org/")) },
+        { ",http://example.org", List.of(new URL("http://example.org")) },
+        { "", List.of() },
+        { ",", List.of() },
+        { "example.org", List.of() },
+        { "example.org,http://example.org",
+            List.of(new URL("http://example.org")) } };
+  }
+
+  @Test(dataProvider = "urlPropertyValues")
+  public void testGetUrls_urlsProperlyParsed(String propValue,
+          List<URL> expected)
+  {
+    Cache.setProperty(URLS_PROPERTY_NAME, propValue);
+    var discoverer = SlivkaWSDiscoverer.getInstance();
+    assertThat(discoverer.getUrls(), equalTo(expected));
+  }
+
   @Test
-  public void testServiceFetch() throws IOException
+  public void testSetUrls_emptyList_propertyReset()
   {
+    Cache.setProperty(URLS_PROPERTY_NAME, "http://www.example.org");
     var discoverer = SlivkaWSDiscoverer.getInstance();
-    var services = discoverer.fetchServices(discoverer.getDefaultUrl());
-    for (var service : services)
+    discoverer.setUrls(List.of());
+    assertThat(Cache.getProperty(URLS_PROPERTY_NAME), is(nullValue()));
+  }
+
+  @Test
+  public void testSetUrls_null_propertyReset()
+  {
+    Cache.setProperty(URLS_PROPERTY_NAME, "http://www.example.org");
+    var discoverer = SlivkaWSDiscoverer.getInstance();
+    discoverer.setUrls(null);
+    assertThat(Cache.getProperty(URLS_PROPERTY_NAME), is(nullValue()));
+  }
+
+  @DataProvider
+  public Object[][] urlsList() throws MalformedURLException
+  {
+    return new Object[][] {
+        { List.of(new URL("http://example.org")), "http://example.org" },
+        { List.of(new URL("http://example.org/")), "http://example.org/" },
+        { List.of(new URL("http://example.org/slivka/")),
+            "http://example.org/slivka/" },
+        { List.of(new URL("https://www.compbio.dundee.ac.uk/slivka/"),
+                new URL("http://example.org")),
+            "https://www.compbio.dundee.ac.uk/slivka/,http://example.org" }, };
+  }
+
+  @Test(dataProvider = "urlsList")
+  public void testSetUrls_urlsPropertySet(List<URL> urls, String expected)
+          throws MalformedURLException
+  {
+    var discoverer = SlivkaWSDiscoverer.getInstance();
+    discoverer.setUrls(urls);
+    assertThat(Cache.getProperty(URLS_PROPERTY_NAME), equalTo(expected));
+  }
+
+  @Test
+  public void testFetchServices_oneService_basicDataMatches()
+          throws IOException
+  {
+    var service = new SlivkaService(
+            URI.create("http://example.org/api/services/example"),
+            "example", "Example name", "Example service description",
+            "John Smith", "1.0", "MIT License",
+            List.of("operation::analysis::multiple sequence alignment"),
+            List.of(), List.of(), null);
+    when(clientMock.getServices()).thenReturn(List.of(service));
+    when(clientMock.getUrl()).thenReturn(URI.create("http://example.org/"));
+    var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
+    var webServices = discoverer
+            .fetchServices(new URL("http://example.org/"));
+    assertThat(webServices, hasSize(1));
+    var webService = webServices.get(0);
+    assertThat(webService.getUrl(),
+            equalTo(new URL("http://example.org/")));
+    assertThat(webService.getClientName(), equalTo("slivka"));
+    assertThat(webService.getName(), equalTo("Example name"));
+    assertThat(webService.getDescription(),
+            equalTo("Example service description"));
+  }
+
+  @DataProvider
+  public String[] validMultipleSequenceAlignmentClassifiers()
+  {
+    return new String[] {
+        "Operation :: Analysis :: Multiple sequence alignment",
+        "operation :: analysis :: multiple sequence alignment",
+        "Operation\t::\tAnalysis\t::\tMultiple sequence alignment",
+        "Operation::Analysis::Multiple sequence alignment",
+        "Operation :: Analysis :: Multiple Sequence Alignment",
+        "OPERATION :: ANALYSIS :: MULTIPLE SEQUENCE ALIGNMENT",
+        "Operation :: Analysis :: Sequence alignment :: Multiple sequence alignment",
+        "Operation :: Analysis :: Sequence analysis :: Sequence alignment :: Multiple sequence alignment",
+        "Operation :: Alignment :: Multiple sequence alignment",
+        "Operation :: Alignment :: Sequence alignment :: Multiple sequence alignment",
+        "Operation :: Comparison :: Multiple sequence alignment",
+        "Operation :: Comparison :: Sequence comparison :: Sequence alignment :: Multiple sequence alignment" };
+
+  }
+
+  @Test(dataProvider = "validMultipleSequenceAlignmentClassifiers")
+  public void testFetchServices_multipleSequenceAlignmentClassifier_serviceTypeIsMSA(
+          String classifier) throws IOException
+  {
+    var service = new SlivkaService(URI.create("http://example.org/"),
+            "example", "name", "description", "author", "1.0", "MIT",
+            List.of(classifier), List.of(), List.of(), null);
+    when(clientMock.getServices()).thenReturn(List.of(service));
+    when(clientMock.getUrl()).thenReturn(URI.create("http://example.org/"));
+    var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
+    var webServices = discoverer
+            .fetchServices(new URL("http://example.org/"));
+    assertThat(webServices, hasSize(1));
+    assertThat(webServices.get(0).getCategory(), equalTo("Alignment"));
+    assertThat(webServices.get(0).getActionClass(),
+            typeCompatibleWith(AlignmentAction.class));
+  }
+
+  @DataProvider
+  public SlivkaService[] multipleSequenceAlignmentService()
+  {
+    return new SlivkaService[] { new SlivkaService(
+            URI.create("http://example.org/"), "example", "Examaple name",
+            "Example description", "John Smith", "1.0", "MIT",
+            List.of("Operation :: Analysis :: Multiple sequence alignment"),
+            List.of(), List.of(), null),
+        new SlivkaService(
+                URI.create("http://example.org/api/services/muscle"),
+                "muscle", "MUSCLE",
+                "MUltiple Sequence Comparison by Log- Expectation",
+                "Robert C. Edgar", "3.8.31", "Public domain",
+                List.of("Topic :: Computational biology :: Sequence analysis",
+                        "Operation :: Analysis :: Sequence analysis :: Sequence alignment :: Multiple sequence alignment"),
+                List.of(), List.of(), null),
+        new SlivkaService(
+                URI.create("http://example.org/api/services/tcoffee"),
+                "tcoffee", "TCoffee",
+                "Tree-based Consistency Objective Function for Alignment Evaluation",
+                "Cedric Notredame", "13.41.0", "GNU GPL",
+                List.of("Topic :: Computational biology :: Sequence analysis",
+                        "Operation :: Analysis :: Sequence analysis :: Sequence alignment :: Multiple sequence alignment"),
+                List.of(), List.of(), null) };
+  }
+
+  @Test(dataProvider = "multipleSequenceAlignmentService")
+  public void testFetchServices_multipleSequenceAlignmentService_actionTypeIsAlignment(
+          SlivkaService service) throws IOException
+  {
+    when(clientMock.getServices()).thenReturn(List.of(service));
+    when(clientMock.getUrl()).thenReturn(URI.create("http://example.org/"));
+    var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
+    var webServices = discoverer
+            .fetchServices(new URL("http://example.org/"));
+    assertThat(webServices.get(0).getCategory(), equalTo("Alignment"));
+    assertThat(webServices.get(0).getActionClass(),
+            typeCompatibleWith(AlignmentAction.class));
+  }
+
+  @Test(dataProvider = "multipleSequenceAlignmentService")
+  public void testFetchServices_multipleSequenceAlignmentService_serviceIsNonInteractive(
+          SlivkaService service) throws IOException
+  {
+    when(clientMock.getServices()).thenReturn(List.of(service));
+    when(clientMock.getUrl()).thenReturn(URI.create("http://example.org/"));
+    var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
+    var webServices = discoverer
+            .fetchServices(new URL("http://example.org/"));
+    assertThat(webServices.get(0).isInteractive(), is(false));
+  }
+
+  @DataProvider
+  public SlivkaService[] clustalFamilyService()
+  {
+    return new SlivkaService[] { new SlivkaService(
+            URI.create("http://example.org/api/services/clustalo"),
+            "clustalo", "ClustalO",
+            "Clustal Omega is the latest addition to the Clustal family.",
+            "Fabian Sievers, et al.", "1.2.4", "GNU GPL ver. 2",
+            List.of("Topic :: Computational biology :: Sequence analysis",
+                    "Operation :: Analysis :: Sequence analysis :: Sequence alignment :: Multiple sequence alignment"),
+            List.of(), List.of(), null),
+        new SlivkaService(
+                URI.create("http://example.org/api/services/clustalw"),
+                "clustalw", "ClustalW",
+                "ClustalW is a general purpose multiple alignment program.",
+                "Larkin MA, et al.", "2.1", "GNU GPL ver. 3",
+                List.of("Topic :: Computation biology :: Sequence analysis",
+                        "Operation :: Analysis :: Multiple sequence alignment"),
+                List.of(), List.of(), null),
+        new SlivkaService(
+                URI.create("http://example.org/api/services/clustalw2"),
+                "clustalw2", "ClustalW2",
+                "ClustalW is a general purpose multiple alignment program.",
+                "Larkin MA, et al.", "2.1", "GNU GPL ver. 3",
+                List.of("Topic :: Computation biology :: Sequence analysis",
+                        "Operation :: Analysis :: Multiple sequence alignment"),
+                List.of(), List.of(), null), };
+  }
+
+  @Test(dataProvider = "clustalFamilyService")
+  public void testFetchService_clustalFamilyService_containsTwoActions(
+          SlivkaService service) throws IOException
+  {
+    when(clientMock.getServices()).thenReturn(List.of(service));
+    when(clientMock.getUrl()).thenReturn(URI.create("http://example.org"));
+    var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
+    var webServices = discoverer
+            .fetchServices(new URL("http://example.org"));
+    var actions = webServices.get(0).getActions();
+    assertThat(actions, hasSize(2));
+    assertThat(actions.get(0), allOf(hasProperty("name", is("Alignment")),
+            hasProperty("subcategory", is("Align"))));
+    assertThat(actions.get(1),
+            allOf(hasProperty("name", is("Re-alignment")),
+                    hasProperty("subcategory", is("Realign"))));
+  }
+
+  @DataProvider
+  public String[] validRNASecondaryStructurePredictionClassifiers()
+  {
+    return new String[] {
+        "Operation :: Analysis :: RNA secondary structure prediction",
+        "operation :: analysis :: rna secondary structure prediction",
+        "OPERATION :: ANALYSIS :: RNA SECONDARY STRUCTURE PREDICTION",
+        "Operation\t::\tAnalysis\t::\tRNA secondary structure prediction",
+        "Operation::Analysis::RNA secondary structure prediction",
+        "Operation :: Analysis :: Structure analysis :: RNA secondary structure prediction",
+        "Operation :: Analysis :: Structure analysis :: Nucleic acid structure analysis :: RNA secondary structure analysis :: RNA secondary structure prediction",
+        "Operation :: Analysis :: Structure analysis :: Nucleic acid structure analysis :: Nucleic acid structure prediction :: RNA secondary structure prediction",
+        "Operation :: Analysis :: Sequence analysis :: Nucleic acid sequence analysis :: Nucleic acid feature detection :: RNA secondary structure prediction",
+        "Operation :: Prediction and recognition :: RNA secondary structure prediction",
+        "Operation :: Prediction and recognition :: Nucleic acid feature detection :: RNA secondary structure prediction",
+        "Operation :: Prediction and recignition :: Nucleic acid structure prediction :: RNA secondary structure prediction", };
+  }
+
+  @DataProvider
+  public Iterator<Object> RNASecondaryStructurePredictionService()
+  {
+    var services = new ArrayList<>();
+    for (var classifier : validRNASecondaryStructurePredictionClassifiers())
     {
-      System.out.format("Service(%s>%s @%s)%n", service.getCategory(), 
-          service.getName(), service.getUrl());
-      var datastore = service.getParamDatastore();
-      for (var param : datastore.getServiceParameters())
-      {
-        System.out.format("  %s :%s%n", param.getName(), param.getClass().getSimpleName()); 
-      }
+      services.add(new SlivkaService(URI.create("http://example.org/"),
+              "example", "name", "description", "author", "1.0", "MIT",
+              List.of(classifier), List.of(), List.of(), null));
     }
+    return services.iterator();
+  }
+
+  @Test(dataProvider = "RNASecondaryStructurePredictionService")
+  public void testFetchServices_RNASecStrPredClassifier_serviceTypeIsRNASecStrPred(
+          SlivkaService service) throws IOException
+  {
+    when(clientMock.getServices()).thenReturn(List.of(service));
+    when(clientMock.getUrl()).thenReturn(URI.create("http://example.org/"));
+    var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
+    var webServices = discoverer
+            .fetchServices(new URL("http://example.org/"));
+    assertThat(webServices, hasSize(1));
+    assertThat(webServices.get(0).getCategory(),
+            equalTo("Secondary Structure Prediction"));
+    assertThat(webServices.get(0).getActionClass(),
+            typeCompatibleWith(AnnotationAction.class));
+  }
+
+  @DataProvider
+  public String[] validConservationAnalysisClassifiers()
+  {
+    return new String[] {
+        "Operation :: Analysis :: Sequence alignment analysis (conservation)",
+        "Operation::Analysis::Sequence alignment analysis (conservation)",
+        "Operation\t::\tAnalysis\t::\tSequence alignment analysis (conservation)",
+        "Operation :: Analysis :: Sequence analysis :: Sequence alignment analysis (conservation)",
+        "Operation :: Analysis :: Sequence analysis :: Sequence alignment analysis :: Sequence alignment analysis (conservation)", };
+  }
+
+  @DataProvider
+  public Iterator<Object> ConservationAnalysisService()
+  {
+    var services = new ArrayList<>();
+    for (var classifier : validConservationAnalysisClassifiers())
+    {
+      services.add(new SlivkaService(URI.create("http://example.org/"),
+              "example", "name", "description", "author", "1.0", "MIT",
+              List.of(classifier), List.of(), List.of(), null));
+    }
+    return services.iterator();
+  }
+
+  @Test(dataProvider = "validConservationAnalysisClassifiers")
+  public void testFetchServices_conservationAnalysisClassifier_serviceTypeIsConservation(
+          String classifier) throws IOException
+  {
+    var service = new SlivkaService(URI.create("http://example.org/"),
+            "example", "name", "description", "author", "1.0", "MIT",
+            List.of(classifier), List.of(), List.of(), null);
+    when(clientMock.getServices()).thenReturn(List.of(service));
+    when(clientMock.getUrl()).thenReturn(URI.create("http://example.org/"));
+    var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
+    var webServices = discoverer
+            .fetchServices(new URL("http://example.org/"));
+    assertThat(webServices, hasSize(1));
+    assertThat(webServices.get(0).getCategory(), equalTo("Conservation"));
+    assertThat(webServices.get(0).getActionClass(),
+            typeCompatibleWith(AnnotationAction.class));
+  }
+
+  @DataProvider
+  public Object[] validProteinSequenceAnalysisClassifiers()
+  {
+    return new Object[] {
+        "Operation :: Analysis :: Sequence analysis :: Protein sequence analysis", };
+  }
+
+  @Test(dataProvider = "validProteinSequenceAnalysisClassifiers")
+  public void testFetchServices_proteinSequenceAnalysisClassifier_serviceTypeIsProtSeqAnalysis(
+          String classifier) throws IOException
+  {
+    var service = new SlivkaService(URI.create("http://example.org/"),
+            "example", "name", "description", "author", "1.0", "MIT",
+            List.of(classifier), List.of(), List.of(), null);
+    when(clientMock.getServices()).thenReturn(List.of(service));
+    when(clientMock.getUrl()).thenReturn(URI.create("http://example.org/"));
+    var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
+    var webServices = discoverer
+            .fetchServices(new URL("http://example.org/"));
+    assertThat(webServices, hasSize(1));
+    assertThat(webServices.get(0).getCategory(),
+            equalTo("Protein Disorder"));
+    assertThat(webServices.get(0).getActionClass(),
+            typeCompatibleWith(AnnotationAction.class));
+  }
+
+  @DataProvider
+  public Object[] validProteinSecondaryStructurePredictionClassifiers()
+  {
+    return new Object[] {
+        "Operation ;: Analysis :: Protein secondary structure prediction",
+        "Operation :: Analysis :: Structure analysis :: Protein structure analysis :: Protein secondary structure analysis :: Protein secondary structure prediction",
+        "Operation :: Analysis :: Sequence analysis :: Protein sequence analysis :: Protein feature detection :: Protein secondary structure prediction",
+        "Operation :: Analysis :: Sequence analysis :: Protein sequence analysis :: Protein secondary structure prediction",
+        "Operation :: Prediction and recognition :: Protein secondary structure prediction",
+        "Operation :: Prediction and recognition :: Protein feature detection :: Protein secondary structure prediction", };
+  }
+
+  @Test(
+    enabled = false, // sec. str. pred. not implemented for slivka
+    dataProvider = "validProteinSecondaryStructurePredictionClassifiers")
+  public void testFetchServices_proteinSecStrPredClassifier_serviceTypeIsProtSecStrPred(
+          String classifier) throws IOException
+  {
+    var service = new SlivkaService(URI.create("http://example.org/"),
+            "example", "name", "description", "author", "1.0", "MIT",
+            List.of(classifier), List.of(), List.of(), null);
+    when(clientMock.getServices()).thenReturn(List.of(service));
+    when(clientMock.getUrl()).thenReturn(URI.create("http://example.org/"));
+    var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
+    var webServices = discoverer
+            .fetchServices(new URL("http://example.org/"));
+    assertThat(webServices, hasSize(1));
+    assertThat(webServices.get(0).getCategory(),
+            equalTo("Protein Disorder"));
+    assertThat(webServices.get(0).getActionClass(),
+            typeCompatibleWith(AnnotationAction.class));
   }
 }
diff --git a/test/jalview/ws2/client/slivka/default.jvprops b/test/jalview/ws2/client/slivka/default.jvprops
new file mode 100644 (file)
index 0000000..190ca63
--- /dev/null
@@ -0,0 +1,2 @@
+#---JalviewX Properties File---
+#Wed Jun 07 18:01:12 CET 2023
diff --git a/utils/testnglibs/hamcrest-2.2-sources.jar b/utils/testnglibs/hamcrest-2.2-sources.jar
new file mode 100644 (file)
index 0000000..6124211
Binary files /dev/null and b/utils/testnglibs/hamcrest-2.2-sources.jar differ
diff --git a/utils/testnglibs/hamcrest-2.2.jar b/utils/testnglibs/hamcrest-2.2.jar
new file mode 100644 (file)
index 0000000..7106578
Binary files /dev/null and b/utils/testnglibs/hamcrest-2.2.jar differ