Merge pull request #1220 from hapifhir/gg-202304-rework-tx2

restore external terminology server tests
This commit is contained in:
Grahame Grieve 2023-04-17 09:40:30 +10:00 committed by GitHub
commit 2aaa965de4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 158 additions and 48 deletions

View File

@ -5,9 +5,10 @@ public class Servers {
public static final String TX_SERVER_PROD = "http://tx.fhir.org"; public static final String TX_SERVER_PROD = "http://tx.fhir.org";
public static final String TX_SERVER_DEV = "http://tx-dev.fhir.org"; public static final String TX_SERVER_DEV = "http://tx-dev.fhir.org";
public static final String TX_SERVER_LOCAL = "http://local.fhir.org"; public static final String TX_SERVER_LOCAL = "http://local.fhir.org";
public static final String TX_SERVER_LOCAL2 = "http://local.fhir.org:8090";
public static boolean isTxFhirOrg(String s) { public static boolean isTxFhirOrg(String s) {
return Utilities.existsInList(s.replace("https://", "http://"), TX_SERVER_PROD, TX_SERVER_DEV, TX_SERVER_LOCAL); return Utilities.startsWithInList(s.replace("https://", "http://"), TX_SERVER_PROD, TX_SERVER_DEV, TX_SERVER_LOCAL);
} }
} }

View File

@ -4,6 +4,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.Authenticator; import java.net.Authenticator;
import java.net.PasswordAuthentication; import java.net.PasswordAuthentication;
import java.net.URISyntaxException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -84,6 +85,8 @@ import org.hl7.fhir.validation.cli.utils.Display;
import org.hl7.fhir.validation.cli.utils.EngineMode; import org.hl7.fhir.validation.cli.utils.EngineMode;
import org.hl7.fhir.validation.cli.utils.Params; import org.hl7.fhir.validation.cli.utils.Params;
import org.hl7.fhir.validation.special.R4R5MapTester; import org.hl7.fhir.validation.special.R4R5MapTester;
import org.hl7.fhir.validation.special.TxTester;
import org.hl7.fhir.validation.special.TxTester.InternalTxLoader;
import org.hl7.fhir.validation.testexecutor.TestExecutor; import org.hl7.fhir.validation.testexecutor.TestExecutor;
import org.hl7.fhir.validation.testexecutor.TestExecutorParams; import org.hl7.fhir.validation.testexecutor.TestExecutorParams;
@ -135,7 +138,7 @@ public class ValidatorCli {
if (destinationDirectoryValid(Params.getParam(args, Params.DESTINATION))) { if (destinationDirectoryValid(Params.getParam(args, Params.DESTINATION))) {
doLeftRightComparison(args, cliContext, tt); doLeftRightComparison(args, cliContext, tt);
} }
} else if (Params.hasParam(args, Params.TEST)) { } else if (Params.hasParam(args, Params.TEST) || Params.hasParam(args, Params.TX_TESTS)) {
parseTestParamsAndExecute(args); parseTestParamsAndExecute(args);
} else if (Params.hasParam(args, Params.SPECIAL)) { } else if (Params.hasParam(args, Params.SPECIAL)) {
executeSpecial(args); executeSpecial(args);
@ -208,19 +211,29 @@ public class ValidatorCli {
} }
} }
protected static void parseTestParamsAndExecute(String[] args) { protected static void parseTestParamsAndExecute(String[] args) throws IOException, URISyntaxException {
final String testModuleParam = Params.getParam(args, Params.TEST_MODULES); if (Params.hasParam(args, Params.TX_TESTS)) {
final String testClassnameFilter = Params.getParam(args, Params.TEST_NAME_FILTER); final String source = Params.getParam(args, Params.SOURCE);
final String testCasesDirectory = Params.getParam(args, Params.TEST); final String output = Params.getParam(args, Params.OUTPUT);
final String txCacheDirectory = Params.getParam(args, Params.TERMINOLOGY_CACHE); final String version = Params.getParam(args, Params.VERSION);
assert TestExecutorParams.isValidModuleParam(testModuleParam) : "Invalid test module param: " + testModuleParam; final String tx = Params.getParam(args, Params.TERMINOLOGY);
final String[] moduleNamesArg = TestExecutorParams.parseModuleParam(testModuleParam); final String filter = Params.getParam(args, Params.FILTER);
boolean ok = new TxTester(new InternalTxLoader(source, output)).setOutput(output).execute(tx, version, filter);
System.exit(ok ? 1 : 0);
} else {
final String testModuleParam = Params.getParam(args, Params.TEST_MODULES);
final String testClassnameFilter = Params.getParam(args, Params.TEST_NAME_FILTER);
final String testCasesDirectory = Params.getParam(args, Params.TEST);
final String txCacheDirectory = Params.getParam(args, Params.TERMINOLOGY_CACHE);
assert TestExecutorParams.isValidModuleParam(testModuleParam) : "Invalid test module param: " + testModuleParam;
final String[] moduleNamesArg = TestExecutorParams.parseModuleParam(testModuleParam);
assert TestExecutorParams.isValidClassnameFilterParam(testClassnameFilter) : "Invalid regex for test classname filter: " + testClassnameFilter; assert TestExecutorParams.isValidClassnameFilterParam(testClassnameFilter) : "Invalid regex for test classname filter: " + testClassnameFilter;
new TestExecutor(moduleNamesArg).executeTests(testClassnameFilter, txCacheDirectory, testCasesDirectory); new TestExecutor(moduleNamesArg).executeTests(testClassnameFilter, txCacheDirectory, testCasesDirectory);
System.exit(0); System.exit(0);
}
} }
private static String[] addAdditionalParamsForIpsParam(String[] args) { private static String[] addAdditionalParamsForIpsParam(String[] args) {

View File

@ -57,6 +57,7 @@ public class Params {
public static final String CONVERT = "-convert"; public static final String CONVERT = "-convert";
public static final String FHIRPATH = "-fhirpath"; public static final String FHIRPATH = "-fhirpath";
public static final String TEST = "-tests"; public static final String TEST = "-tests";
public static final String TX_TESTS = "-txTests";
public static final String HELP = "help"; public static final String HELP = "help";
public static final String COMPARE = "-compare"; public static final String COMPARE = "-compare";
public static final String SPREADSHEET = "-spreadsheet"; public static final String SPREADSHEET = "-spreadsheet";

View File

@ -4,11 +4,23 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.hl7.fhir.convertors.txClient.TerminologyClientFactory; import org.hl7.fhir.convertors.txClient.TerminologyClientFactory;
import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.DefinitionException;
@ -27,8 +39,10 @@ import org.hl7.fhir.utilities.FhirPublication;
import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.json.JsonException; import org.hl7.fhir.utilities.json.JsonException;
import org.hl7.fhir.utilities.json.model.JsonArray;
import org.hl7.fhir.utilities.json.model.JsonObject; import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.json.parser.JsonParser; import org.hl7.fhir.utilities.json.parser.JsonParser;
import org.hl7.fhir.utilities.npm.NpmPackage;
public class TxTester { public class TxTester {
@ -42,6 +56,7 @@ public class TxTester {
private String server; private String server;
private ITxTesterLoader loader; private ITxTesterLoader loader;
private String error; private String error;
private String output;
public TxTester(ITxTesterLoader loader) { public TxTester(ITxTesterLoader loader) {
@ -50,30 +65,61 @@ public class TxTester {
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
new TxTester(new InternalLoader(args[0])).execute(args[1], args[2]); new TxTester(new InternalTxLoader(args[0])).execute(args[1], args[2], args[3]);
} }
public void execute(String server, String filter) { public boolean execute(String server, String version, String filter) throws IOException, URISyntaxException {
this.server = server; this.server = server;
System.out.println("Run terminology service Tests"); if (output == null) {
output = Utilities.path("[tmp]", serverId());
}
System.out.println("Run terminology service Tests");
System.out.println(" Source for tests: "+loader.describe());
System.out.println(" Output Directory: "+output);
System.out.println(" Term Service Url: "+server);
if (version != null) {
System.out.println(" Tx FHIR Version: "+version);
}
if (filter != null) {
System.out.println(" Filter Parameter: "+filter);
}
JsonObject json = new JsonObject();
json.add("date", new SimpleDateFormat("EEE, MMM d, yyyy HH:mmZ", new Locale("en", "US")).format(Calendar.getInstance().getTime()) + timezone());
try { try {
JsonObject tests = loadTests(); JsonObject tests = loadTests();
TerminologyClient tx = connectToServer(); TerminologyClient tx = connectToServer();
boolean ok = checkClient(tx); boolean ok = checkClient(tx);
for (JsonObject suite : tests.getJsonObjects("suites")) { for (JsonObject suite : tests.getJsonObjects("suites")) {
ok = runSuite(suite, tx, filter) && ok; ok = runSuite(suite, tx, filter, json.forceArray("suites")) && ok;
} }
TextFile.stringToFile(JsonParser.compose(json, true), Utilities.path(output, "test-results.json"));
if (ok) { if (ok) {
System.out.println("Terminology Service Tests all passed"); System.out.println("Terminology Service Tests all passed");
return true;
} else { } else {
System.out.println("Terminology Service Tests did not all pass"); System.out.println("Terminology Service Tests did not all pass");
return false;
} }
} catch (Exception e) { } catch (Exception e) {
System.out.println("Exception running Terminology Service Tests: "+e.getMessage()); System.out.println("Exception running Terminology Service Tests: "+e.getMessage());
e.printStackTrace(); e.printStackTrace();
return false;
} }
} }
private String timezone() {
TimeZone tz = TimeZone.getDefault();
Calendar cal = GregorianCalendar.getInstance(tz);
int offsetInMillis = tz.getOffset(cal.getTimeInMillis());
String offset = String.format("%02d:%02d", Math.abs(offsetInMillis / 3600000), Math.abs((offsetInMillis / 60000) % 60));
offset = (offsetInMillis >= 0 ? "+" : "-") + offset;
return offset;
}
private boolean checkClient(TerminologyClient tx) { private boolean checkClient(TerminologyClient tx) {
tx.getCapabilitiesStatementQuick(); tx.getCapabilitiesStatementQuick();
@ -98,23 +144,33 @@ public class TxTester {
TerminologyClient tx = connectToServer(); TerminologyClient tx = connectToServer();
checkClient(tx); checkClient(tx);
List<Resource> setup = loadSetupResources(suite); List<Resource> setup = loadSetupResources(suite);
if (runTest(test, tx, setup, "*")) { if (runTest(test, tx, setup, "*", null)) {
return null; return null;
} else { } else {
return error; return error;
} }
} }
private boolean runSuite(JsonObject suite, TerminologyClient tx, String filter) throws FHIRFormatError, FileNotFoundException, IOException { private boolean runSuite(JsonObject suite, TerminologyClient tx, String filter, JsonArray output) throws FHIRFormatError, FileNotFoundException, IOException {
System.out.println("Group "+suite.asString("name")); System.out.println("Group "+suite.asString("name"));
JsonObject outputS = new JsonObject();
if (output != null) {
output.add(outputS);
}
outputS.add("name", suite.asString("name"));
List<Resource> setup = loadSetupResources(suite); List<Resource> setup = loadSetupResources(suite);
boolean ok = true; boolean ok = true;
for (JsonObject test : suite.getJsonObjects("tests")) { for (JsonObject test : suite.getJsonObjects("tests")) {
ok = runTest(test, tx, setup, filter) && ok; ok = runTest(test, tx, setup, filter, outputS.forceArray("tests")) && ok;
} }
return ok; return ok;
} }
private boolean runTest(JsonObject test, TerminologyClient tx, List<Resource> setup, String filter) { private boolean runTest(JsonObject test, TerminologyClient tx, List<Resource> setup, String filter, JsonArray output) {
JsonObject outputT = new JsonObject();
if (output != null) {
output.add(outputT);
}
outputT.add("name", test.asString("name"));
if (Utilities.noString(filter) || filter.equals("*") || test.asString("name").contains(filter)) { if (Utilities.noString(filter) || filter.equals("*") || test.asString("name").contains(filter)) {
System.out.print(" Test "+test.asString("name")+": "); System.out.print(" Test "+test.asString("name")+": ");
try { try {
@ -142,6 +198,10 @@ public class TxTester {
System.out.println(" "+msg); System.out.println(" "+msg);
error = msg; error = msg;
} }
outputT.add("status", msg == null ? "pass" : "fail");
if (msg != null) {
outputT.add("message", msg);
}
return msg == null; return msg == null;
} catch (Exception e) { } catch (Exception e) {
System.out.println(" ... Exception: "+e.getMessage()); System.out.println(" ... Exception: "+e.getMessage());
@ -151,6 +211,7 @@ public class TxTester {
return false; return false;
} }
} else { } else {
outputT.add("status", "ignored");
return true; return true;
} }
} }
@ -213,14 +274,73 @@ public class TxTester {
return res; return res;
} }
public static class InternalLoader implements ITxTesterLoader { public String getOutput() {
return output;
}
public TxTester setOutput(String output) {
this.output = output;
return this;
}
public static class InternalTxLoader implements ITxTesterLoader {
private String folder; private String folder;
public InternalLoader(String folder) { public InternalTxLoader(String folder) {
this.folder = folder; this.folder = folder;
} }
public InternalTxLoader(String source, String local) throws IOException {
if (source.startsWith("http://") || source.startsWith("https://")) {
this.folder = Utilities.path(local, "source");
URL url = new URL(zipUrl(source));
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
InputStream zip = connection.getInputStream();
unzip(zip);
} else {
this.folder = source;
}
}
public void unzip(InputStream is) throws IOException {
try (ZipInputStream zipIn = new ZipInputStream(is)) {
for (ZipEntry ze; (ze = zipIn.getNextEntry()) != null; ) {
if (ze.getName().startsWith("fhir-test-cases-master/tx/")) {
Path path = Path.of(Utilities.path(this.folder, ze.getName().substring(26))).normalize();
String pathString = path.toFile().getAbsolutePath();
if (!path.startsWith(Path.of(this.folder).normalize())) {
// see: https://snyk.io/research/zip-slip-vulnerability
throw new RuntimeException("Entry with an illegal path: " + ze.getName());
}
if (ze.isDirectory()) {
Utilities.createDirectory(pathString);
} else {
Utilities.createDirectory(Utilities.getDirectoryForFile(pathString));
TextFile.streamToFileNoClose(zipIn, pathString);
}
}
}
}
}
private String zipUrl(String template) {
if (!template.startsWith("https://github.")) {
throw new FHIRException("Cannot refer to source by URL unless referring to a github repository: "+template);
} else if (Utilities.charCount(template, '/') == 4) {
return Utilities.pathURL(template, "archive", "master.zip");
} else if (Utilities.charCount(template, '/') == 6) {
String[] p = template.split("\\/");
return Utilities.pathURL("https://"+p[2], p[3], p[4], "archive", p[6]+".zip");
} else {
throw new FHIRException("Source syntax in URL referring to a github repository was not understood: "+template);
}
}
@Override @Override
public String describe() { public String describe() {
return folder; return folder;

View File

@ -1,6 +1,5 @@
package org.hl7.fhir.terminology.tests; package org.hl7.fhir.terminology.tests;
import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -18,38 +17,15 @@ import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50;
import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.formats.JsonParser; import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.formats.XmlParser; import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.Constants; import org.hl7.fhir.r5.model.Constants;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.r5.model.OperationOutcome.IssueType;
import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent;
import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.test.utils.CompareUtilities;
import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.terminology.tests.ExternalTerminologyServiceTests.JsonObjectPair;
import org.hl7.fhir.utilities.FhirPublication;
import org.hl7.fhir.utilities.Servers; import org.hl7.fhir.utilities.Servers;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.json.model.JsonObject; import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.special.TxTester; import org.hl7.fhir.validation.special.TxTester;
import org.hl7.fhir.validation.special.TxTester.ITxTesterLoader; import org.hl7.fhir.validation.special.TxTester.ITxTesterLoader;
import org.hl7.fhir.validation.tests.ValidationEngineTests;
import org.hl7.fhir.validation.tests.utilities.TestUtilities;
import org.junit.AfterClass;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
@ -58,9 +34,7 @@ import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters; import org.junit.runners.Parameterized.Parameters;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.gson.JsonSyntaxException;
@Ignore
@RunWith(Parameterized.class) @RunWith(Parameterized.class)
public class ExternalTerminologyServiceTests implements ITxTesterLoader { public class ExternalTerminologyServiceTests implements ITxTesterLoader {
@ -74,6 +48,7 @@ public class ExternalTerminologyServiceTests implements ITxTesterLoader {
} }
private static final String SERVER = Servers.TX_SERVER_DEV; private static final String SERVER = Servers.TX_SERVER_DEV;
// private static final String SERVER = Servers.TX_SERVER_LOCAL2;
@Parameters(name = "{index}: id {0}") @Parameters(name = "{index}: id {0}")
public static Iterable<Object[]> data() throws IOException { public static Iterable<Object[]> data() throws IOException {