rework txTester to support additional test files, and better R4/R5 logging

This commit is contained in:
Grahame Grieve 2025-01-12 23:02:40 +11:00
parent 0cee7d4607
commit 6b52ee5291
17 changed files with 458 additions and 115 deletions

View File

@ -265,4 +265,10 @@ public class TerminologyClientR2 implements ITerminologyClient {
return (Parameters) VersionConvertorFactory_10_50.convertResource(client.translate((org.hl7.fhir.dstu2.model.Parameters) VersionConvertorFactory_10_50.convertResource(params)));
}
@Override
public void setConversionLogger(ITerminologyConversionLogger logger) {
// TODO Auto-generated method stub
}
}

View File

@ -268,4 +268,10 @@ public class TerminologyClientR3 implements ITerminologyClient {
return (Parameters) VersionConvertorFactory_30_50.convertResource(client.transform((org.hl7.fhir.dstu3.model.Parameters) VersionConvertorFactory_30_50.convertResource(params)));
}
@Override
public void setConversionLogger(ITerminologyConversionLogger logger) {
// TODO Auto-generated method stub
}
}

View File

@ -11,6 +11,8 @@ import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.utils.client.EFhirClientException;
import org.hl7.fhir.r4.utils.client.FHIRToolingClient;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CapabilityStatement;
@ -28,9 +30,10 @@ import org.hl7.fhir.utilities.http.HTTPHeader;
public class TerminologyClientR4 implements ITerminologyClient {
private final FHIRToolingClient client; // todo: use the R2 client
private final FHIRToolingClient client;
private ClientHeaders clientHeaders;
private String id;
private ITerminologyConversionLogger logger;
public TerminologyClientR4(String id, String address, String userAgent) throws URISyntaxException {
this.client = new FHIRToolingClient(address, userAgent);
@ -71,7 +74,7 @@ public class TerminologyClientR4 implements ITerminologyClient {
@Override
public TerminologyCapabilities getTerminologyCapabilities() throws FHIRException {
return (TerminologyCapabilities) VersionConvertorFactory_40_50.convertResource(client.getTerminologyCapabilities());
return (TerminologyCapabilities) convertResource("getTerminologyCapabilities.response", client.getTerminologyCapabilities());
}
@Override
@ -81,29 +84,30 @@ public class TerminologyClientR4 implements ITerminologyClient {
@Override
public ValueSet expandValueset(ValueSet vs, Parameters p) throws FHIRException {
org.hl7.fhir.r4.model.ValueSet vs2 = vs == null ? null : (org.hl7.fhir.r4.model.ValueSet) VersionConvertorFactory_40_50.convertResource(vs);
org.hl7.fhir.r4.model.Parameters p2 = p == null ? null : (org.hl7.fhir.r4.model.Parameters) VersionConvertorFactory_40_50.convertResource(p);
org.hl7.fhir.r4.model.ValueSet vs2 = vs == null ? null : (org.hl7.fhir.r4.model.ValueSet) convertResource("expandValueset.valueset", vs);
org.hl7.fhir.r4.model.Parameters p2 = p == null ? null : (org.hl7.fhir.r4.model.Parameters) convertResource("expandValueset.parameters", p);
try {
vs2 = client.expandValueset(vs2, p2); // todo: second parameter
return (ValueSet) VersionConvertorFactory_40_50.convertResource(vs2);
return (ValueSet) convertResource("expandValueset.response", vs2);
} catch (org.hl7.fhir.r4.utils.client.EFhirClientException e) {
if (e.getServerErrors().size() > 0) {
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getCode(), e.getMessage(), (org.hl7.fhir.r5.model.OperationOutcome) VersionConvertorFactory_40_50.convertResource(e.getServerErrors().get(0)));
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getCode(), e.getMessage(), (org.hl7.fhir.r5.model.OperationOutcome) convertResource("expandValueset.error", e.getServerErrors().get(0)));
} else {
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getCode(), e.getMessage());
}
}
}
@Override
public Parameters validateCS(Parameters pin) throws FHIRException {
try {
org.hl7.fhir.r4.model.Parameters p2 = (org.hl7.fhir.r4.model.Parameters) VersionConvertorFactory_40_50.convertResource(pin);
org.hl7.fhir.r4.model.Parameters p2 = (org.hl7.fhir.r4.model.Parameters) convertResource("validateCS.request", pin);
p2 = client.operateType(org.hl7.fhir.r4.model.CodeSystem.class, "validate-code", p2);
return (Parameters) VersionConvertorFactory_40_50.convertResource(p2);
return (Parameters) convertResource("validateCS.response", p2);
} catch (EFhirClientException e) {
if (e.getServerErrors().size() == 1) {
OperationOutcome op = (OperationOutcome) VersionConvertorFactory_40_50.convertResource(e.getServerErrors().get(0));
OperationOutcome op = (OperationOutcome) convertResource("validateCS.error", e.getServerErrors().get(0));
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getCode(), e.getMessage(), op, e);
} else {
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getCode(), e.getMessage(), e);
@ -117,12 +121,12 @@ public class TerminologyClientR4 implements ITerminologyClient {
@Override
public Parameters subsumes(Parameters pin) throws FHIRException {
try {
org.hl7.fhir.r4.model.Parameters p2 = (org.hl7.fhir.r4.model.Parameters) VersionConvertorFactory_40_50.convertResource(pin);
org.hl7.fhir.r4.model.Parameters p2 = (org.hl7.fhir.r4.model.Parameters) convertResource("subsumes.request", pin);
p2 = client.operateType(org.hl7.fhir.r4.model.CodeSystem.class, "subsumes", p2);
return (Parameters) VersionConvertorFactory_40_50.convertResource(p2);
return (Parameters) convertResource("subsumes.response", p2);
} catch (EFhirClientException e) {
if (e.getServerErrors().size() == 1) {
OperationOutcome op = (OperationOutcome) VersionConvertorFactory_40_50.convertResource(e.getServerErrors().get(0));
OperationOutcome op = (OperationOutcome) convertResource("subsumes.error", e.getServerErrors().get(0));
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getCode(), e.getMessage(), op, e);
} else {
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getCode(), e.getMessage(), e);
@ -135,12 +139,12 @@ public class TerminologyClientR4 implements ITerminologyClient {
@Override
public Parameters validateVS(Parameters pin) throws FHIRException {
try {
org.hl7.fhir.r4.model.Parameters p2 = (org.hl7.fhir.r4.model.Parameters) VersionConvertorFactory_40_50.convertResource(pin);
org.hl7.fhir.r4.model.Parameters p2 = (org.hl7.fhir.r4.model.Parameters) convertResource("validateVS.request", pin);
p2 = client.operateType(org.hl7.fhir.r4.model.ValueSet.class, "validate-code", p2);
return (Parameters) VersionConvertorFactory_40_50.convertResource(p2);
return (Parameters) convertResource("validateVS.response", p2);
} catch (EFhirClientException e) {
if (e.getServerErrors().size() == 1) {
OperationOutcome op = (OperationOutcome) VersionConvertorFactory_40_50.convertResource(e.getServerErrors().get(0));
OperationOutcome op = (OperationOutcome) convertResource("validateVS.error", e.getServerErrors().get(0));
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getCode(), e.getMessage(), op, e);
} else {
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getCode(), e.getMessage(), e);
@ -175,22 +179,22 @@ public class TerminologyClientR4 implements ITerminologyClient {
@Override
public CapabilityStatement getCapabilitiesStatementQuick() throws FHIRException {
return (CapabilityStatement) VersionConvertorFactory_40_50.convertResource(client.getCapabilitiesStatementQuick());
return (CapabilityStatement) convertResource("getCapabilitiesStatementQuick.response", client.getCapabilitiesStatementQuick());
}
@Override
public CapabilityStatement getCapabilitiesStatement() throws FHIRException {
return (CapabilityStatement) VersionConvertorFactory_40_50.convertResource(client.getCapabilitiesStatement());
return (CapabilityStatement) convertResource("getCapabilitiesStatement.response", client.getCapabilitiesStatement());
}
@Override
public Parameters lookupCode(Map<String, String> params) throws FHIRException {
return (Parameters) VersionConvertorFactory_40_50.convertResource(client.lookupCode(params));
return (Parameters) convertResource("lookupCode.response", client.lookupCode(params));
}
@Override
public Parameters lookupCode(Parameters params) throws FHIRException {
return (Parameters) VersionConvertorFactory_40_50.convertResource(client.lookupCode((org.hl7.fhir.r4.model.Parameters) VersionConvertorFactory_40_50.convertResource(params)));
return (Parameters) convertResource("lookupCode.response", client.lookupCode((org.hl7.fhir.r4.model.Parameters) convertResource("lookupCode.request", params)));
}
@Override
@ -200,8 +204,8 @@ public class TerminologyClientR4 implements ITerminologyClient {
@Override
public Bundle validateBatch(Bundle batch) {
org.hl7.fhir.r4.model.Bundle result = client.transaction((org.hl7.fhir.r4.model.Bundle) VersionConvertorFactory_40_50.convertResource(batch));
return result == null ? null : (Bundle) VersionConvertorFactory_40_50.convertResource(result);
org.hl7.fhir.r4.model.Bundle result = client.transaction((org.hl7.fhir.r4.model.Bundle) convertResource("validateBatch.request", batch));
return result == null ? null : (Bundle) convertResource("validateBatch.response", result);
}
@Override
@ -216,7 +220,7 @@ public class TerminologyClientR4 implements ITerminologyClient {
if (r4 == null) {
throw new FHIRException("Unable to fetch resource " + Utilities.pathURL(getAddress(), type, id));
}
org.hl7.fhir.r5.model.Resource r5 = VersionConvertorFactory_40_50.convertResource(r4);
org.hl7.fhir.r5.model.Resource r5 = convertResource("read.result", r4);
if (r5 == null) {
throw new FHIRException("Unable to convert resource " + Utilities.pathURL(getAddress(), type, id) + " to R5 (internal representation)");
}
@ -278,12 +282,55 @@ public class TerminologyClientR4 implements ITerminologyClient {
@Override
public Bundle search(String type, String criteria) {
org.hl7.fhir.r4.model.Bundle result = client.search(type, criteria);
return result == null ? null : (Bundle) VersionConvertorFactory_40_50.convertResource(result);
return result == null ? null : (Bundle) convertResource("search.result", result);
}
@Override
public Parameters translate(Parameters params) throws FHIRException {
return (Parameters) VersionConvertorFactory_40_50.convertResource(client.translate((org.hl7.fhir.r4.model.Parameters) VersionConvertorFactory_40_50.convertResource(params)));
return (Parameters) convertResource("translate.response", client.translate((org.hl7.fhir.r4.model.Parameters) convertResource("translate.request", params)));
}
private org.hl7.fhir.r4.model.Resource convertResource(String name, org.hl7.fhir.r5.model.Resource resource) {
if (logger != null) {
try {
logger.log(name, resource.fhirType(), "r5", new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeBytes(resource));
} catch (IOException e) {
throw new FHIRException(e);
}
}
org.hl7.fhir.r4.model.Resource res = VersionConvertorFactory_40_50.convertResource(resource);
if (logger != null) {
try {
logger.log(name, resource.fhirType(), "r4", new org.hl7.fhir.r4.formats.JsonParser().setOutputStyle(org.hl7.fhir.r4.formats.IParser.OutputStyle.PRETTY).composeBytes(res));
} catch (IOException e) {
throw new FHIRException(e);
}
}
return res;
}
private org.hl7.fhir.r5.model.Resource convertResource(String name, org.hl7.fhir.r4.model.Resource resource) {
if (logger != null && name != null) {
try {
logger.log(name, resource.fhirType(), "r4", new org.hl7.fhir.r4.formats.JsonParser().setOutputStyle(org.hl7.fhir.r4.formats.IParser.OutputStyle.PRETTY).composeBytes(resource));
} catch (IOException e) {
throw new FHIRException(e);
}
}
org.hl7.fhir.r5.model.Resource res = VersionConvertorFactory_40_50.convertResource(resource);
if (logger != null && name != null) {
try {
logger.log(name, resource.fhirType(), "r5", new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeBytes(res));
} catch (IOException e) {
throw new FHIRException(e);
}
}
return res;
}
@Override
public void setConversionLogger(ITerminologyConversionLogger logger) {
this.logger = logger;
}
}

View File

@ -76,4 +76,10 @@ public interface ITerminologyClient {
String getUserAgent();
int getUseCount();
Bundle search(String type, String criteria);
// internal conversion logging
public interface ITerminologyConversionLogger {
void log(String name, String resourceType, String version, byte[] cnt);
}
void setConversionLogger(ITerminologyConversionLogger logger);
}

View File

@ -275,5 +275,11 @@ public class TerminologyClientR5 implements ITerminologyClient {
return client.translate(params);
}
@Override
public void setConversionLogger(ITerminologyConversionLogger logger) {
// TODO Auto-generated method stub
}
}

View File

@ -477,6 +477,11 @@ public class Utilities {
return ManagedFileAccess.file(path);
}
public static File createDirectoryNC(String path) throws IOException {
ManagedFileAccess.file(path).mkdirs();
return ManagedFileAccess.file(path);
}
public static String changeFileExt(String name, String ext) {
if (name.lastIndexOf('.') > -1)
return name.substring(0, name.lastIndexOf('.')) + ext;

View File

@ -35,6 +35,18 @@ public class JsonObject extends JsonElement {
propMap.put(name, p);
return this;
}
public JsonObject add(int index, String name, JsonElement value) throws JsonException {
check(name != null, "Json Property Name is null");
check(value != null, "Json Property Value is null");
if (get(name) != null) {
check(false, "Name '"+name+"' already exists (value = "+get(name).toString()+")");
}
JsonProperty p = new JsonProperty(name, value);
properties.add(index, p);
propMap.put(name, p);
return this;
}
public JsonObject addIfNotNull(String name, JsonElement value) throws JsonException {
if (value != null) {
@ -347,6 +359,16 @@ public class JsonObject extends JsonElement {
}
return getJsonArray(name);
}
public JsonArray forceArray(int index, String name) throws JsonException {
if (has(name) && !hasArray(name)) {
remove(name);
}
if (!has(name)) {
add(index, name, new JsonArray());
}
return getJsonArray(name);
}
public List<JsonObject> getJsonObjects(String name) {
List<JsonObject> res = new ArrayList<>();

View File

@ -6,6 +6,7 @@ import java.io.PrintStream;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.utilities.TimeTracker;
import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.cli.model.CliContext;
import org.hl7.fhir.validation.cli.services.ValidationService;
@ -47,7 +48,7 @@ public class TxPackTask extends ValidationEngineTask {
String pid = cliContext.getPackageName();
boolean json = cliContext.getFormat() != FhirFormat.XML;
String output = cliContext.getOutput();
File f = new File(output);
File f = ManagedFileAccess.file(output);
ExpansionPackageGeneratorOutputType t = ExpansionPackageGeneratorOutputType.FOLDER;
if (f.exists() && f.isDirectory()) {
t = ExpansionPackageGeneratorOutputType.FOLDER;

View File

@ -49,7 +49,11 @@ public class TxTestsTask extends StandaloneTask{
if (output == null ) {
output = Utilities.path("[tmp]");
}
boolean ok = new TxTester(new TxTester.InternalTxLoader(version), tx, false, loadExternals(externals)).setOutput(output).execute(cliContext.getModeParams(), filter);
TxTester txTester = new TxTester(new TxTester.InternalTxLoader(version), tx, false, loadExternals(externals));
for (String input : cliContext.getInputs()) {
txTester.addLoader(new TxTester.InternalTxLoader(input, true));
}
boolean ok = txTester.setOutput(output).execute(cliContext.getModeParams(), filter);
SystemExitManager.setError(ok ? 0 : 1);
SystemExitManager.finish();
}

View File

@ -405,7 +405,7 @@ public class Params {
cliContext.setOutputStyle(args[++i]);
} else if (args[i].equals(ADVSIOR_FILE)) {
cliContext.setAdvisorFile(args[++i]);
File f = new File(cliContext.getAdvisorFile());
File f = ManagedFileAccess.file(cliContext.getAdvisorFile());
if (!f.exists()) {
throw new Error("Cannot find advisor file "+cliContext.getAdvisorFile());
} else if (!Utilities.existsInList(Utilities.getFileExtension(f.getName()), "json", "txt")) {

View File

@ -12,6 +12,7 @@ import org.hl7.fhir.r5.test.utils.CompareUtilities;
import org.hl7.fhir.utilities.FhirPublication;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.validation.ValidationOptions;
@ -174,7 +175,7 @@ public class TxServiceTestHelper {
parameters.addParameter(validationResult.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED ? "x-caused-by-unknown-system" : "x-unknown-system", new CanonicalType(s));
}
}
if (validationResult.getIssues().size() > 0) {
if (validationResult.getIssues().size() > 0) {
operationOutcome = new OperationOutcome();
operationOutcome.getIssue().addAll(validationResult.getIssues());
parameters.addParameter().setName("issues").setResource(operationOutcome);
@ -205,12 +206,12 @@ public class TxServiceTestHelper {
}
String fullExpected = rootDirectory + "/expected/";
String fullActual = rootDirectory + "/actual/";
File expectedDirectory = new File(fullExpected);
File expectedDirectory = ManagedFileAccess.file(fullExpected);
if (!expectedDirectory.exists()) {
expectedDirectory.mkdirs();
}
File actualDirectory = new File(fullActual);
File actualDirectory = ManagedFileAccess.file(fullActual);
if (!actualDirectory.exists()) {
actualDirectory.mkdirs();
}

View File

@ -3,13 +3,17 @@ package org.hl7.fhir.validation.special;
import lombok.Getter;
import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
import org.hl7.fhir.utilities.json.JsonException;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.json.parser.JsonParser;
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
import org.hl7.fhir.utilities.npm.NpmPackage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
public class TxTestData {
@ -24,17 +28,74 @@ public class TxTestData {
private final List<Object[]> testData;
private final NpmPackage npm;
private final File folder;
private final String code;
private final String version;
private final String testFileName;
private TxTestData(List<Object[]> testData, JsonObject manifest, JsonObject externals, NpmPackage npm) throws IOException {
private TxTestData(List<Object[]> testData, JsonObject manifest, JsonObject externals, NpmPackage npm, String code, String version) throws IOException {
this.testData = testData;
this.manifest = manifest;
this.externals = externals;
this.npm = npm;
this.folder = null;
this.code = code;
this.version = version;
this.testFileName = "test-cases.json";
}
private TxTestData(List<Object[]> testData, JsonObject manifest, JsonObject externals, File folder, String filename, String code, String version) throws IOException {
this.testData = testData;
this.manifest = manifest;
this.externals = externals;
this.npm = null;
this.folder = folder;
this.code = code;
this.version = version;
this.testFileName = filename;
}
public static TxTestData loadTestDataFromPackage(String version) throws IOException {
public static TxTestData loadTestDataFromFolder(File folder, String name) throws IOException {
String contents = TextFile.streamToString(loadFile(folder, name));
String externalSource = ManagedFileAccess.file(folder.getAbsolutePath(), "messages-tx.fhir.org.json").exists() ?
TextFile.streamToString(loadFile(folder, "messages-tx.fhir.org.json")) : null;
JsonObject externals = externalSource == null ? new JsonObject() : org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(externalSource);
Map<String, TxTestSetup> examples = new HashMap<String, TxTestSetup>();
JsonObject manifest = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(contents);
for (JsonObject suite : manifest.getJsonObjects("suites")) {
if (!"tx.fhir.org".equals(suite.asString("mode"))) {
String sn = suite.asString("name");
for (JsonObject test : suite.getJsonObjects("tests")) {
String tn = test.asString("name");
examples.put(sn + "." + tn, new TxTestSetup(suite, test));
}
}
}
List<String> names = new ArrayList<String>(examples.size());
names.addAll(examples.keySet());
Collections.sort(names);
List<Object[]> testData = new ArrayList<Object[]>(examples.size());
for (String id : names) {
testData.add(new Object[]{id, examples.get(id)});
}
return new TxTestData(testData, manifest, externals, folder, name, manifest.asString("code"), manifest.asString("version"));
}
private static InputStream loadFile(File folder, String filename) throws IOException {
File f = ManagedFileAccess.file(Utilities.path(folder.getAbsolutePath(), filename));
return ManagedFileAccess.inStream(f);
}
public static TxTestData loadTestDataFromPackage(String source) throws IOException {
FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager.Builder().build();
NpmPackage npm = pcm.loadPackage("hl7.fhir.uv.tx-ecosystem", version);
NpmPackage npm = pcm.loadPackage(source);
String contents = TextFile.streamToString(npm.load("tests", "test-cases.json"));
String externalSource = TextFile.streamToString(npm.load("tests", "messages-tx.fhir.org.json"));
@ -61,23 +122,39 @@ public class TxTestData {
testData.add(new Object[]{id, examples.get(id)});
}
return new TxTestData(testData, manifest, externals, npm);
return new TxTestData(testData, manifest, externals, npm, manifest.asString("code"), manifest.asString("version"));
}
public String load(String fn) throws IOException {
return TextFile.streamToString(npm.load("tests", fn));
if (folder != null) {
return TextFile.streamToString(loadFile(folder, fn));
} else {
return TextFile.streamToString(npm.load("tests", fn));
}
}
public byte[] loadBytes(String fn) throws IOException {
return TextFile.streamToBytes(npm.load("tests", fn));
if (folder != null) {
return TextFile.streamToBytes(loadFile(folder, fn));
} else {
return TextFile.streamToBytes(npm.load("tests", fn));
}
}
public boolean hasFile(String filename) throws IOException {
return npm.hasFile("tests", filename);
if (folder != null) {
return ManagedFileAccess.file(Utilities.path(folder.getAbsolutePath(), filename)).exists();
} else {
return npm.hasFile("tests", filename);
}
}
public String loadVersion() throws JsonException, IOException {
return readHistory(loadBytes("history.json"));
if (version == null) {
return readHistory(loadBytes("history.json"));
} else {
return version;
}
}
private String readHistory(byte[] content) throws JsonException, IOException {
@ -86,7 +163,19 @@ public class TxTestData {
}
public String describe() {
return npm.name()+"#"+npm.version();
if (folder != null) {
return folder.getAbsolutePath();
} else {
return npm.name()+"#"+npm.version();
}
}
public String code() {
return code;
}
public String testFileName() {
return testFileName;
}

View File

@ -38,6 +38,7 @@ import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.TerminologyCapabilities;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.terminologies.client.ITerminologyClient;
import org.hl7.fhir.r5.terminologies.client.ITerminologyClient.ITerminologyConversionLogger;
import org.hl7.fhir.r5.test.utils.CompareUtilities;
import org.hl7.fhir.r5.utils.client.EFhirClientException;
import org.hl7.fhir.r5.utils.client.network.ClientHeaders;
@ -73,12 +74,42 @@ public class TxTester {
public Resource loadResource(String filename) throws IOException, FHIRFormatError, FileNotFoundException, FHIRException, DefinitionException;
public byte[] loadContent(String filename) throws FileNotFoundException, IOException;
public boolean hasContent(String filename) throws IOException;
public String code();
public String version() throws JsonException, IOException;
public String testFileName();
}
private class TxTesterConversionLogger implements ITerminologyConversionLogger {
public String suiteName;
public String testName;
@Override
public void log(String name, String resourceType, String version, byte[] cnt) {
if (!"expandValueset.response".equals(name)) {
return;
}
String base;
try {
base = Utilities.path(outputDir, "conversions");
if (ManagedFileAccess.file(base).exists()) {
String dir = Utilities.path(base, version, suiteName);
Utilities.createDirectory(dir);
String filename = Utilities.path(dir, testName+"."+resourceType+".json");
TextFile.bytesToFile(cnt, filename);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private String server;
private ITxTesterLoader loader;
private List<ITxTesterLoader> loaders = new ArrayList<>();
private String error;
private String output;
private String outputDir;
private ITerminologyClient terminologyClient;
private boolean tight;
private JsonObject externals;
@ -86,33 +117,44 @@ public class TxTester {
private List<String> fails = new ArrayList<>();
private CapabilityStatement cstmt;
private TerminologyCapabilities tc;
private TxTesterConversionLogger conversionLogger;
public TxTester(ITxTesterLoader loader, String server, boolean tight, JsonObject externals) {
super();
this.server = server;
this.loader = loader;
this.loaders.add(loader);
this.tight = tight;
this.externals = externals;
conversionLogger = new TxTesterConversionLogger();
}
public static void main(String[] args) throws Exception {
new TxTester(new InternalTxLoader(args[0]), args[1], "true".equals(args[2]), args.length == 5 ? JsonParser.parseObjectFromFile(args[4]) : null).execute(new ArrayList<>(), args[3]);
}
public void addLoader(ITxTesterLoader loader) {
this.loaders.add(loader);
}
public boolean execute(List<String> modes, String filter) throws IOException, URISyntaxException {
if (output == null) {
output = Utilities.path("[tmp]", serverId());
if (outputDir == null) {
outputDir = 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);
if (!new File(output).exists()) {
Utilities.createDirectory(output);
System.out.println(" Source for tests: "+loaders.get(0).describe());
for (ITxTesterLoader loader : loaders) {
if (loader != loaders.get(0)) {
System.out.println(" Additional Tests: "+loader.describe());
}
}
if (!new File(output).exists()) {
throw new IOException("Unable to create output directory "+output);
System.out.println(" Output Directory: "+outputDir);
if (!ManagedFileAccess.file(outputDir).exists()) {
Utilities.createDirectory(outputDir);
}
if (!ManagedFileAccess.file(outputDir).exists()) {
throw new IOException("Unable to create output directory "+outputDir);
}
System.out.println(" Term Service Url: "+server);
System.out.println(" External Strings: "+(externals != null));
@ -121,32 +163,37 @@ public class TxTester {
if (filter != null) {
System.out.println(" Filter Parameter: "+filter);
}
IntHolder counter = new IntHolder();
IntHolder errCount = new IntHolder();
JsonObject json = new JsonObject();
List<StringPair> versions = new ArrayList<StringPair>();
json.add("date", new SimpleDateFormat("EEE, MMM d, yyyy HH:mmZ", new Locale("en", "US")).format(Calendar.getInstance().getTime()) + timezone());
try {
JsonObject tests = loadTests();
terminologyClient = connectToServer(modes);
boolean ok = checkClient();
for (JsonObject suite : tests.getJsonObjects("suites")) {
if ((!suite.has("mode") || modes.contains(suite.asString("mode")))) {
if (suite.asBoolean("disabled")) {
// ok = true;
} else {
ok = runSuite(suite, modes, filter, json.forceArray("suites"), counter) && ok;
for (ITxTesterLoader loader : loaders) {
JsonObject tests = loadTests(loader);
versions.add(new StringPair(loader.code(), loader.version()));
for (JsonObject suite : tests.getJsonObjects("suites")) {
if ((!suite.has("mode") || modes.contains(suite.asString("mode")))) {
if (suite.asBoolean("disabled")) {
// ok = true;
} else {
ok = runSuite(loader, suite, modes, filter, json.forceArray("suites"), counter, errCount) && ok;
}
}
}
}
TextFile.stringToFile(JsonParser.compose(json, true), Utilities.path(output, "test-results.json"));
TextFile.stringToFile(JsonParser.compose(json, true), Utilities.path(outputDir, "test-results.json"));
if (filter == null) {
String m = modes.isEmpty() ? "[none]" : CommaSeparatedStringBuilder.join(";", modes);
if (ok) {
System.out.println(software+" passed all "+counter.total()+" HL7 terminology service tests ("+Utilities.pluralize("mode", modes.size())+" "+m+", tests v"+loadVersion()+", runner v"+VersionUtil.getBaseVersion()+")");
System.out.println(software+" passed all "+counter.total()+" HL7 terminology service tests ("+Utilities.pluralize("mode", modes.size())+" "+m+", tests v"+vString(versions)+", runner v"+VersionUtil.getBaseVersion()+")");
return true;
} else {
System.out.println(software+" did not pass all "+counter.total()+" HL7 terminology service tests ("+Utilities.pluralize("mode", modes.size())+" "+m+", tests v"+loadVersion()+", runner v"+VersionUtil.getBaseVersion()+")");
System.out.println(software+" failed "+errCount.total()+" of "+counter.total()+" HL7 terminology service tests ("+Utilities.pluralize("mode", modes.size())+" "+m+", tests v"+vString(versions)+", runner v"+VersionUtil.getBaseVersion()+")");
System.out.println("Failed Tests: "+ CommaSeparatedStringBuilder.join(",", fails ));
return false;
}
@ -162,6 +209,24 @@ public class TxTester {
}
private String vString(List<StringPair> versions) {
StringBuilder b = new StringBuilder();
b.append(versions.get(0).getValue());
if (versions.size() > 1) {
b.append("[");
for (int i = 1; i < versions.size(); i++) {
if (i > 1) {
b.append(",");
}
b.append(versions.get(i).getName());
b.append(":");
b.append(versions.get(i).getValue());
}
b.append("]");
}
return b.toString();
}
private String timezone() {
TimeZone tz = TimeZone.getDefault();
Calendar cal = GregorianCalendar.getInstance(tz);
@ -174,6 +239,8 @@ public class TxTester {
}
private boolean checkClient() {
conversionLogger.suiteName = "connect";
conversionLogger.testName = "checkClient";
cstmt = terminologyClient.getCapabilitiesStatement();
if (cstmt.hasSoftware()) {
software = cstmt.getSoftware().getName()+" v"+cstmt.getSoftware().getVersion();
@ -182,75 +249,76 @@ public class TxTester {
return true;
}
private JsonObject loadTests() throws JsonException, IOException {
private JsonObject loadTests(ITxTesterLoader loader) throws JsonException, IOException {
System.out.println("Load Tests from "+loader.describe());
return JsonParser.parseObject(loader.loadContent("test-cases.json"));
return JsonParser.parseObject(loader.loadContent(loader.testFileName()));
}
public String loadVersion() throws JsonException, IOException {
if (loader.hasContent("history.json")) {
return readHistory(loader.loadContent("history.json"));
} else {
throw new Error("history.md is no longer supported");
}
}
private String readHistory(byte[] content) throws JsonException, IOException {
JsonObject json = JsonParser.parseObject(content);
return json.getJsonObjects("versions").get(0).asString("version");
}
private ITerminologyClient connectToServer(List<String> modes) throws URISyntaxException, IOException {
System.out.println("Connect to "+server);
software = server;
if (outputDir == null) {
outputDir = Utilities.path("[tmp]", serverId());
}
// todo: we don't necessarily want R4:
Utilities.createDirectory(Utilities.path(outputDir, "conversions", "r4"));
Utilities.createDirectory(Utilities.path(outputDir, "conversions", "r5"));
ITerminologyClient client = new TerminologyClientFactory(FhirPublication.R4).makeClient("Test-Server", server, "Tools/Java", null);
client.setConversionLogger(conversionLogger);
return client;
}
public String executeTest(JsonObject suite, JsonObject test, List<String> modes) throws URISyntaxException, FHIRFormatError, FileNotFoundException, IOException {
public String executeTest(ITxTesterLoader loader, JsonObject suite, JsonObject test, List<String> modes) throws URISyntaxException, FHIRFormatError, FileNotFoundException, IOException {
error = null;
if (terminologyClient == null) {
terminologyClient = connectToServer(modes);
checkClient();
}
List<Resource> setup = loadSetupResources(suite);
List<Resource> setup = loadSetupResources(loader, suite);
if (runTest(suite, test, setup, modes, "*", null, new IntHolder())) {
if (runTest(loader, suite, test, setup, modes, "*", null, new IntHolder())) {
return null;
} else {
return error;
}
}
private boolean runSuite(JsonObject suite, List<String> modes, String filter, JsonArray output, IntHolder counter) throws FHIRFormatError, FileNotFoundException, IOException {
private boolean runSuite(ITxTesterLoader loader, JsonObject suite, List<String> modes, String filter, JsonArray output, IntHolder counter, IntHolder errCount) throws FHIRFormatError, FileNotFoundException, IOException {
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(loader, suite);
boolean ok = true;
for (JsonObject test : suite.getJsonObjects("tests")) {
if ((!test.has("mode") || modes.contains(test.asString("mode")))) {
if (test.asBoolean("disabled")) {
ok = true;
} else {
ok = runTest(suite, test, setup, modes, filter, outputS.forceArray("tests"), counter) && ok;
boolean tok = runTest(loader, suite, test, setup, modes, filter, outputS.forceArray("tests"), counter);
if (!tok) {
errCount.count();
}
ok = tok && ok;
}
}
}
return ok;
}
private boolean runTest(JsonObject suite, JsonObject test, List<Resource> setup, List<String> modes, String filter, JsonArray output, IntHolder counter) throws FHIRFormatError, DefinitionException, FileNotFoundException, FHIRException, IOException {
private boolean runTest(ITxTesterLoader loader, JsonObject suite, JsonObject test, List<Resource> setup, List<String> modes, String filter, JsonArray output, IntHolder counter) throws FHIRFormatError, DefinitionException, FileNotFoundException, FHIRException, IOException {
JsonObject outputT = new JsonObject();
if (output != null) {
output.add(outputT);
}
long start = System.currentTimeMillis();
Parameters profile = loadProfile(test);
Parameters profile = loadProfile(loader, test);
outputT.add("name", test.asString("name"));
if (Utilities.noString(filter) || filter.equals("*") || test.asString("name").contains(filter)) {
System.out.print(" Test "+test.asString("name")+": ");
@ -264,13 +332,14 @@ public class TxTester {
terminologyClient.setClientHeaders(new ClientHeaders(List.of(header)));
}
}
conversionLogger.suiteName = suite.asString("name");
conversionLogger.testName = test.asString("name");
String reqFile = chooseParam(test, "request", modes);
Parameters req = reqFile == null ? null : (Parameters) loader.loadResource(reqFile);
String fn = chooseParam(test, "response", modes);
String resp = TextFile.bytesToString(loader.loadContent(fn));
String fp = this.output == null ? Utilities.path("[tmp]", serverId(), fn) : Utilities.path(this.output, fn);
String fp = this.outputDir == null ? Utilities.path("[tmp]", serverId(), fn) : Utilities.path(this.outputDir, fn);
File fo = ManagedFileAccess.file(fp);
if (fo.exists()) {
fo.delete();
@ -373,7 +442,7 @@ public class TxTester {
return test.asString(name);
}
private Parameters loadProfile(JsonObject test) throws FHIRFormatError, DefinitionException, FileNotFoundException, FHIRException, IOException {
private Parameters loadProfile(ITxTesterLoader loader, JsonObject test) throws FHIRFormatError, DefinitionException, FileNotFoundException, FHIRException, IOException {
if (test.has("profile")) {
return (Parameters) loader.loadResource(test.asString("profile"));
} else {
@ -560,7 +629,7 @@ public class TxTester {
}
private List<Resource> loadSetupResources(JsonObject suite) throws FHIRFormatError, FileNotFoundException, IOException {
private List<Resource> loadSetupResources(ITxTesterLoader loader, JsonObject suite) throws FHIRFormatError, FileNotFoundException, IOException {
List<Resource> res = new ArrayList<>();
for (String s : suite.getStrings("setup")) {
res.add(loader.loadResource(s));
@ -569,27 +638,44 @@ public class TxTester {
}
public String getOutput() {
return output;
return outputDir;
}
public TxTester setOutput(String output) {
this.output = output;
this.outputDir = output;
return this;
}
public static class InternalTxLoader implements ITxTesterLoader {
TxTestData txtests;
private TxTestData txtests;
private boolean additional;
public InternalTxLoader(String version) throws IOException {
load(version);
File f = ManagedFileAccess.file(version);
if (f.exists() && f.isDirectory()) {
txtests = TxTestData.loadTestDataFromFolder(f, "test-cases.json");
} else {
load(version);
}
}
public InternalTxLoader(String source, boolean additional) throws IOException {
this.additional = additional;
File f = ManagedFileAccess.file(source);
if (f.exists() && f.isDirectory()) {
txtests = TxTestData.loadTestDataFromFolder(f, "test-cases.json");
} else if (f.exists()) {
txtests = TxTestData.loadTestDataFromFolder(ManagedFileAccess.file(Utilities.getDirectoryForFile(source)), f.getName());
} else {
load(source);
}
}
private void load(String version) throws IOException {
txtests = TxTestData.loadTestDataFromPackage(version);
txtests = TxTestData.loadTestDataFromPackage("hl7.fhir.uv.tx-ecosystem#"+version);
}
@Override
public String describe() {
return txtests.describe();
@ -617,6 +703,22 @@ public class TxTester {
public boolean hasContent(String filename) throws IOException {
return txtests.hasFile(filename);
}
@Override
public String code() {
return txtests.code();
}
@Override
public String version() throws JsonException, IOException {
return txtests.loadVersion();
}
@Override
public String testFileName() {
return txtests.testFileName();
}
}
}

View File

@ -21,6 +21,7 @@ import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Constants;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.utilities.json.JsonException;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.settings.FhirSettings;
import org.hl7.fhir.validation.special.TxTestData;
@ -52,7 +53,7 @@ public class ExternalTerminologyServiceTests implements ITxTesterLoader {
@Parameters(name = "{index}: id {0}")
public static Iterable<Object[]> data() throws IOException {
txtests = TxTestData.loadTestDataFromPackage("dev");
txtests = TxTestData.loadTestDataFromPackage("hl7.fhir.uv.tx-ecosystem#dev");
String contents = txtests.load("test-cases.json");
externals = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(txtests.load("messages-tx.fhir.org.json"));
@ -101,7 +102,7 @@ public class ExternalTerminologyServiceTests implements ITxTesterLoader {
if (setup.suite.asBoolean("disabled") || setup.test.asBoolean("disabled")) {
return;
}
String err = tester.executeTest(setup.suite, setup.test, modes);
String err = tester.executeTest(this, setup.suite, setup.test, modes);
Assertions.assertTrue(err == null, err);
} else {
Assertions.assertTrue(true);
@ -155,4 +156,19 @@ public class ExternalTerminologyServiceTests implements ITxTesterLoader {
public boolean hasContent(String filename) throws IOException {
return txtests.hasFile(filename);
}
@Override
public String code() {
return "external";
}
@Override
public String version() throws JsonException, IOException {
return txtests.loadVersion();
}
@Override
public String testFileName() {
return txtests.testFileName();
}
}

View File

@ -62,7 +62,7 @@ public class LocalTerminologyServiceTests implements ITxTesterLoader {
@Parameters(name = "{index}: id {0}")
public static Iterable<Object[]> data() throws IOException {
txtests = TxTestData.loadTestDataFromPackage("dev");
txtests = TxTestData.loadTestDataFromPackage("hl7.fhir.uv.tx-ecosystem#dev");
String contents = txtests.load("test-cases.json");
externals = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(txtests.load("messages-tx.fhir.org.json"));
@ -95,7 +95,8 @@ public class LocalTerminologyServiceTests implements ITxTesterLoader {
private String version = "5.0.0";
private static TxTester tester;
private List<String> modes = new ArrayList<>();
private boolean error = false;
private static int error = 0;
private static int count = 0;
private static TxTestData txtests;
public LocalTerminologyServiceTests(String name, JsonObjectPair setup) {
@ -115,17 +116,23 @@ public class LocalTerminologyServiceTests implements ITxTesterLoader {
return;
}
if (setup == null) {
if (!error) {
System.out.println("tx.fhir.org passed all HL7 terminology service tests (mode 'tx.fhir.org', tests v"+loadVersion()+", runner v"+VersionUtil.getBaseVersion()+")");
count++;
if (error == 0) {
System.out.println("tx.fhir.org passed all "+count+" HL7 terminology service tests (mode 'tx.fhir.org', tests v"+loadVersion()+", runner v"+VersionUtil.getBaseVersion()+")");
} else {
System.out.println("tx.fhir.org failed "+error+" of "+count+" HL7 terminology service tests (mode 'tx.fhir.org', tests v"+loadVersion()+", runner v"+VersionUtil.getBaseVersion()+")");
}
Assertions.assertTrue(!error);
Assertions.assertTrue(error == 0);
} else {
count++;
if (SERVER != null) {
if (tester == null) {
tester = new TxTester(this, SERVER, true, externals);
}
String err = tester.executeTest(setup.suite, setup.test, modes);
error = error || err != null;
String err = tester.executeTest(this, setup.suite, setup.test, modes);
if (err != null) {
error++;
}
Assertions.assertTrue(err == null, err);
} else {
Assertions.assertTrue(true);
@ -175,10 +182,6 @@ public class LocalTerminologyServiceTests implements ITxTesterLoader {
throw new FHIRException("unknown version " + version);
}
}
// org.hl7.fhir.r4.model.Resource r4 = VersionConvertorFactory_40_50.convertResource(res);
// String p = Utilities.path(FhirSettings.getFhirTestCasesPath(), "tx", "r4", filename);
// Utilities.createDirectory(Utilities.getDirectoryForFile(p));
// new org.hl7.fhir.r4.formats.JsonParser().compose(ManagedFileAccess.outStream(p), r4);
return res;
}
@ -196,4 +199,21 @@ public class LocalTerminologyServiceTests implements ITxTesterLoader {
public boolean hasContent(String filename) throws IOException {
return txtests.hasFile(filename);
}
@Override
public String code() {
return "local";
}
@Override
public String version() throws JsonException, IOException {
return txtests.loadVersion();
}
@Override
public String testFileName() {
return txtests.testFileName();
}
}

View File

@ -20,6 +20,7 @@ import org.hl7.fhir.r5.model.Constants;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
import org.hl7.fhir.utilities.json.JsonException;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.settings.FhirSettings;
import org.hl7.fhir.utilities.tests.TestConfig;
@ -54,12 +55,10 @@ public class OntoserverTests implements ITxTesterLoader {
return ManagedFileAccess.file("/Users/grahamegrieve/work/server/server").exists();
}
@Parameters(name = "{index}: id {0}")
public static Iterable<Object[]> data() throws IOException {
txtests = TxTestData.loadTestDataFromPackage("dev");
txtests = TxTestData.loadTestDataFromPackage("hl7.fhir.uv.tx-ecosystem#dev");
String contents = txtests.load("test-cases.json");
externals = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(txtests.load("messages-ontoserver.csiro.au.json"));
@ -117,7 +116,7 @@ public class OntoserverTests implements ITxTesterLoader {
if (tester == null) {
tester = new TxTester(this, SERVER, false, externals);
}
String err = tester.executeTest(setup.suite, setup.test, modes);
String err = tester.executeTest(this, setup.suite, setup.test, modes);
if (err != null) {
System.out.println(err);
}
@ -161,10 +160,6 @@ public class OntoserverTests implements ITxTesterLoader {
throw new FHIRException("unknown version " + version);
}
}
// org.hl7.fhir.r4.model.Resource r4 = VersionConvertorFactory_40_50.convertResource(res);
// String p = Utilities.path(FhirSettings.getFhirTestCasesPath(), "tx", "r4", filename);
// Utilities.createDirectory(Utilities.getDirectoryForFile(p));
// new org.hl7.fhir.r4.formats.JsonParser().compose(ManagedFileAccess.outStream(p), r4);
return res;
}
@ -182,4 +177,21 @@ public class OntoserverTests implements ITxTesterLoader {
public boolean hasContent(String filename) throws IOException {
return txtests.hasFile(filename);
}
@Override
public String code() {
return "onto";
}
@Override
public String version() throws JsonException, IOException {
return txtests.loadVersion();
}
@Override
public String testFileName() {
return txtests.testFileName();
}
}

View File

@ -59,7 +59,7 @@ private static TxTestData testData;
@Parameters(name = "{index}: id {0}")
public static Iterable<Object[]> data() throws IOException {
testData = TxTestData.loadTestDataFromPackage("dev");
testData = TxTestData.loadTestDataFromPackage("hl7.fhir.uv.tx-ecosystem#dev");
return testData.getTestData();
}