add cross version snapshot generation tests

This commit is contained in:
Grahame Grieve 2020-03-17 11:15:53 +11:00
parent 335b5587b3
commit 298b39e615
3 changed files with 1112 additions and 0 deletions

View File

@ -0,0 +1,12 @@
package org.hl7.fhir.conversion.tests;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({
SnapShotGenerationTestsX.class})
public class CrossVersionLibraryTests {
}

View File

@ -0,0 +1,544 @@
package org.hl7.fhir.conversion.tests;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.NotImplementedException;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.r5.conformance.ProfileUtilities;
import org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.r5.model.ExpressionNode.CollectionStatus;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.r5.model.TypeDetails;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.r5.utils.IResourceValidator;
import org.hl7.fhir.r5.utils.NarrativeGenerator;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.xml.XMLUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import junit.framework.Assert;
@RunWith(Parameterized.class)
public class SnapShotGenerationTestsX {
public enum TestFetchMode {
INPUT,
OUTPUT,
INCLUDE
}
public static class Rule {
private String description;
private String expression;
public Rule(String description, String expression) {
super();
this.description = description;
this.expression = expression;
}
public Rule(Element rule) {
super();
this.description = rule.getAttribute("text");
this.expression = rule.getAttribute("fhirpath");
}
public String getDescription() {
return description;
}
public String getExpression() {
return expression;
}
}
public static class TestDetails {
private String id;
private String include;
private String register;
private String regex;
private boolean gen;
private boolean sort;
private boolean fail;
private boolean newSliceProcessing;
private boolean debug;
private String version;
private List<Rule> rules = new ArrayList<>();
private StructureDefinition source;
private StructureDefinition included;
private StructureDefinition expected;
private StructureDefinition output;
public TestDetails(Element test) {
super();
gen = "true".equals(test.getAttribute("gen"));
sort = "true".equals(test.getAttribute("sort"));
fail = "true".equals(test.getAttribute("fail"));
newSliceProcessing = !"false".equals(test.getAttribute("new-slice-processing"));
debug = "true".equals(test.getAttribute("debug"));
id = test.getAttribute("id");
include = test.getAttribute("include");
register = test.getAttribute("register");
regex = test.getAttribute("regex");
version = test.getAttribute("version");
Element rule = XMLUtil.getFirstChild(test);
while (rule != null && rule.getNodeName().equals("rule")) {
rules.add(new Rule(rule));
rule = XMLUtil.getNextSibling(rule);
}
}
public String getId() {
return id;
}
public boolean isSort() {
return sort;
}
public boolean isGen() {
return gen;
}
public String getInclude() {
return include;
}
public boolean isFail() {
return fail;
}
public StructureDefinition getIncluded() {
return included;
}
public List<Rule> getRules() {
return rules;
}
public StructureDefinition getSource() {
return source;
}
public void setSource(StructureDefinition source) {
this.source = source;
}
public StructureDefinition getExpected() {
return expected;
}
public void setExpected(StructureDefinition expected) {
this.expected = expected;
}
public StructureDefinition getOutput() {
return output;
}
public void setOutput(StructureDefinition output) {
this.output = output;
}
public void load() throws FHIRFormatError, FileNotFoundException, IOException {
if (TestingUtilitiesX.findTestResource("rX", "snapshot-generation", id+"-input.json"))
source = (StructureDefinition) new JsonParser().parse(TestingUtilitiesX.loadTestResourceStream("rX", "snapshot-generation", id+"-input.json"));
else
source = (StructureDefinition) new XmlParser().parse(TestingUtilitiesX.loadTestResourceStream("rX", "snapshot-generation", id+"-input.xml"));
if (!fail)
expected = (StructureDefinition) new XmlParser().parse(TestingUtilitiesX.loadTestResourceStream("rX", "snapshot-generation", id+"-expected.xml"));
if (!Utilities.noString(include))
included = (StructureDefinition) new XmlParser().parse(TestingUtilitiesX.loadTestResourceStream("rX", "snapshot-generation", include+".xml"));
if (!Utilities.noString(register)) {
if (TestingUtilitiesX.findTestResource("rX", "snapshot-generation", register+".xml")) {
included = (StructureDefinition) new XmlParser().parse(TestingUtilitiesX.loadTestResourceStream("rX", "snapshot-generation", register+".xml"));
} else {
included = (StructureDefinition) new JsonParser().parse(TestingUtilitiesX.loadTestResourceStream("rX", "snapshot-generation", register+".json"));
}
}
}
public boolean isNewSliceProcessing() {
return newSliceProcessing;
}
public boolean isDebug() {
return debug;
}
}
public class TestPKP implements ProfileKnowledgeProvider {
@Override
public boolean isDatatype(String name) {
StructureDefinition sd = TestingUtilitiesX.context(version).fetchTypeDefinition(name);
return (sd != null) && (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) && (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE || sd.getKind() == StructureDefinitionKind.COMPLEXTYPE);
}
@Override
public boolean isResource(String typeSimple) {
StructureDefinition sd = TestingUtilitiesX.context(version).fetchTypeDefinition(typeSimple);
return (sd != null) && (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) && (sd.getKind() == StructureDefinitionKind.RESOURCE);
}
@Override
public boolean hasLinkFor(String typeSimple) {
return isDatatype(typeSimple);
}
@Override
public String getLinkFor(String corePath, String typeSimple) {
return Utilities.pathURL(corePath, "datatypes.html#"+typeSimple);
}
@Override
public BindingResolution resolveBinding(StructureDefinition def, ElementDefinitionBindingComponent binding, String path) throws FHIRException {
BindingResolution br = new BindingResolution();
br.url = path+"/something.html";
br.display = "something";
return br;
}
@Override
public BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException {
BindingResolution br = new BindingResolution();
br.url = path+"/something.html";
br.display = "something";
return br;
}
@Override
public String getLinkForProfile(StructureDefinition profile, String url) {
StructureDefinition sd = TestingUtilitiesX.context(version).fetchResource(StructureDefinition.class, url);
if (sd == null)
return url+"|"+url;
else
return sd.getId()+".html|"+sd.present();
}
@Override
public boolean prependLinks() {
return false;
}
@Override
public String getLinkForUrl(String corePath, String s) {
// TODO Auto-generated method stub
return null;
}
}
private static class SnapShotGenerationTestsContext implements IEvaluationContext {
public List<TestDetails> tests = new ArrayList<>();
public Resource fetchFixture(String id) {
TestFetchMode mode = TestFetchMode.INPUT;
if (id.equals("patient"))
return TestingUtilitiesX.context(version).fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/Patient");
if (id.equals("valueset"))
return TestingUtilitiesX.context(version).fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/ValueSet");
if (id.equals("organization"))
return TestingUtilitiesX.context(version).fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/Organization");
if (id.equals("operationoutcome"))
return TestingUtilitiesX.context(version).fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/OperationOutcome");
if (id.equals("parameters"))
return TestingUtilitiesX.context(version).fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/Parameters");
if (id.contains("-")) {
String[] p = id.split("\\-");
id = p[0];
if (p[1].equals("output"))
mode = TestFetchMode.OUTPUT;
else if (p[1].equals("include"))
mode = TestFetchMode.INCLUDE;
}
for (TestDetails td : tests) {
if (td.getId().equals(id))
switch (mode) {
case INPUT: return td.getSource();
case OUTPUT: if (td.getOutput() == null)
throw new FHIRException("Not generated yet");
else
return td.getOutput();
case INCLUDE:
return td.getIncluded();
default:
throw new FHIRException("Not done yet");
}
}
return null;
}
// FHIRPath methods
@Override
public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
throw new Error("Not implemented yet");
}
@Override
public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException {
throw new Error("Not implemented yet");
}
@Override
public boolean log(String argument, List<Base> focus) {
System.out.println(argument+": "+fp.convertToString(focus));
return true;
}
@Override
public FunctionDetails resolveFunction(String functionName) {
if ("fixture".equals(functionName))
return new FunctionDetails("Access a fixture defined in the testing context", 0, 1);
return null;
}
@Override
public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException {
if ("fixture".equals(functionName))
return new TypeDetails(CollectionStatus.SINGLETON, TestingUtilitiesX.context(version).getResourceNamesAsSet());
return null;
}
@Override
public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) {
if ("fixture".equals(functionName)) {
String id = fp.convertToString(parameters.get(0));
Resource res = fetchFixture(id);
if (res != null) {
List<Base> list = new ArrayList<Base>();
list.add(res);
return list;
}
throw new Error("Could not resolve "+id);
}
throw new Error("Not implemented yet");
}
@Override
public Base resolveReference(Object appContext, String url, Base refContext) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException {
IResourceValidator val = TestingUtilitiesX.context(version).newValidator();
List<ValidationMessage> valerrors = new ArrayList<ValidationMessage>();
if (item instanceof Resource) {
val.validate(appContext, valerrors, (Resource) item, url);
boolean ok = true;
for (ValidationMessage v : valerrors)
ok = ok && v.getLevel().isError();
return ok;
}
throw new NotImplementedException("Not done yet (IGPublisherHostServices.SnapShotGenerationTestsContext), when item is element");
}
public StructureDefinition getByUrl(String url) {
if (url == null)
return null;
for (TestDetails t : tests) {
if (t.expected != null && url.equals(t.expected.getUrl()))
return t.expected;
if (t.included != null && url.equals(t.included.getUrl()))
return t.included;
}
return null;
}
@Override
public ValueSet resolveValueSet(Object appContext, String url) {
throw new Error("Not implemented yet");
}
}
private static FHIRPathEngine fp;
@Parameters(name = "{index}: file {0}")
public static Iterable<Object[]> data() throws ParserConfigurationException, IOException, FHIRFormatError, SAXException {
SnapShotGenerationTestsContext context = new SnapShotGenerationTestsContext();
Document tests = XMLUtil.parseToDom(TestingUtilitiesX.loadTestResource("rX", "snapshot-generation", "manifest.xml"));
Element test = XMLUtil.getFirstChild(tests.getDocumentElement());
List<Object[]> objects = new ArrayList<Object[]>();
while (test != null && test.getNodeName().equals("test")) {
TestDetails t = new TestDetails(test);
context.tests.add(t);
t.load();
objects.add(new Object[] {t.getId(), t, context });
test = XMLUtil.getNextSibling(test);
}
return objects;
}
private final TestDetails test;
private SnapShotGenerationTestsContext context;
private List<ValidationMessage> messages;
private static String version;
public SnapShotGenerationTestsX(String id, TestDetails test, SnapShotGenerationTestsContext context) {
this.test = test;
this.context = context;
}
@SuppressWarnings("deprecation")
@Test
public void test() throws Exception {
version = test.version;
if (fp == null)
fp = new FHIRPathEngine(TestingUtilitiesX.context(version));
fp.setHostServices(context);
messages = new ArrayList<ValidationMessage>();
if (test.isFail()) {
try {
if (test.isGen())
testGen(true);
else
testSort();
Assert.assertTrue("Should have failed", false);
} catch (Throwable e) {
System.out.println("Error running test: "+e.getMessage());
if (!Utilities.noString(test.regex)) {
Assert.assertTrue("correct error message", e.getMessage().matches(test.regex));
} else if ("Should have failed".equals(e.getMessage())) {
throw e;
} else {
Assert.assertTrue("all ok", true);
}
}
} else if (test.isGen())
testGen(false);
else
testSort();
for (Rule r : test.getRules()) {
StructureDefinition sdn = new StructureDefinition();
boolean ok = fp.evaluateToBoolean(sdn, sdn, sdn, r.expression);
Assert.assertTrue(r.description, ok);
}
}
private void testSort() throws DefinitionException, FHIRException, IOException {
StructureDefinition base = getSD(test.getSource().getBaseDefinition());
test.setOutput(test.getSource().copy());
ProfileUtilities pu = new ProfileUtilities(TestingUtilitiesX.context(version), null, null);
pu.setIds(test.getSource(), false);
List<String> errors = new ArrayList<String>();
pu.sortDifferential(base, test.getOutput(), test.getOutput().getUrl(), errors, false);
if (!errors.isEmpty())
throw new FHIRException(errors.get(0));
IOUtils.copy(TestingUtilitiesX.loadTestResourceStream("rX", "snapshot-generation", test.getId()+"-expected.xml"), new FileOutputStream(TestingUtilitiesX.tempFile("snapshot", test.getId()+"-expected.xml")));
new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(TestingUtilitiesX.tempFile("snapshot", test.getId()+"-actual.xml")), test.getOutput());
Assert.assertTrue("Output does not match expected", test.expected.equalsDeep(test.output));
}
private void testGen(boolean fail) throws Exception {
if (!Utilities.noString(test.register)) {
List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
ProfileUtilities pu = new ProfileUtilities(TestingUtilitiesX.context(version), messages, null);
pu.setNewSlicingProcessing(true);
pu.setIds(test.included, false);
StructureDefinition base = TestingUtilitiesX.context(version).fetchResource(StructureDefinition.class, test.included.getBaseDefinition());
if (base != null) {
pu.generateSnapshot(base, test.included, test.included.getUrl(), "http://test.org/profile", test.included.getName());
}
if (!TestingUtilitiesX.context(version).hasResource(StructureDefinition.class, test.included.getUrl()))
TestingUtilitiesX.context(version).cacheResource(test.included);
int ec = 0;
for (ValidationMessage vm : messages) {
if (vm.getLevel() == IssueSeverity.ERROR) {
System.out.println(vm.summary());
ec++;
}
}
if (ec > 0)
throw new FHIRException("register gen failed: "+messages.toString());
}
StructureDefinition base = getSD(test.getSource().getBaseDefinition());
if (!base.getUrl().equals(test.getSource().getBaseDefinition()))
throw new Exception("URL mismatch on base: "+base.getUrl()+" wanting "+test.getSource().getBaseDefinition());
StructureDefinition output = test.getSource().copy();
ProfileUtilities pu = new ProfileUtilities(TestingUtilitiesX.context(version), messages , new TestPKP());
pu.setNewSlicingProcessing(test.isNewSliceProcessing());
pu.setThrowException(false);
pu.setDebug(test.isDebug());
pu.setIds(test.getSource(), false);
if (test.isSort()) {
List<String> errors = new ArrayList<String>();
int lastCount = output.getDifferential().getElement().size();
pu.sortDifferential(base, output, test.getSource().getName(), errors, false);
if (errors.size() > 0)
throw new FHIRException("Sort failed: "+errors.toString());
}
try {
messages.clear();
pu.generateSnapshot(base, output, test.getSource().getUrl(), "http://test.org/profile", test.getSource().getName());
List<ValidationMessage> ml = new ArrayList<>();
for (ValidationMessage vm : messages) {
if (vm.getLevel() == IssueSeverity.ERROR) {
ml.add(vm);
}
}
if (ml.size() > 0) {
throw new FHIRException("Snapshot Generation failed: "+ml.toString());
}
} catch (Throwable e) {
System.out.println("\r\nException: "+e.getMessage());
throw e;
}
if (output.getDifferential().hasElement())
new NarrativeGenerator("", "http://hl7.org/fhir", TestingUtilitiesX.context(version)).setPkp(new TestPKP()).generate(output, null);
if (!fail) {
test.output = output;
TestingUtilitiesX.context(version).cacheResource(output);
File dst = new File(TestingUtilitiesX.tempFile("snapshot", test.getId()+"-expected.xml"));
if (dst.exists())
dst.delete();
IOUtils.copy(TestingUtilitiesX.loadTestResourceStream("rX", "snapshot-generation", test.getId()+"-expected.xml"), new FileOutputStream(dst));
new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(TestingUtilitiesX.tempFile("snapshot", test.getId()+"-actual.xml")), output);
StructureDefinition t1 = test.expected.copy();
t1.setText(null);
StructureDefinition t2 = test.output.copy();
t2.setText(null);
Assert.assertTrue("Output does not match expected", t1.equalsDeep(t2));
}
}
private StructureDefinition getSD(String url) throws DefinitionException, FHIRException, IOException {
StructureDefinition sd = context.getByUrl(url);
if (sd == null)
sd = TestingUtilitiesX.context(version).fetchResource(StructureDefinition.class, url);
if (!sd.hasSnapshot()) {
StructureDefinition base = getSD(sd.getBaseDefinition());
ProfileUtilities pu = new ProfileUtilities(TestingUtilitiesX.context(version), messages , new TestPKP());
pu.setNewSlicingProcessing(true);
List<String> errors = new ArrayList<String>();
pu.sortDifferential(base, sd, url, errors, false);
if (!errors.isEmpty())
throw new FHIRException(errors.get(0));
pu.setIds(sd, false);
pu.generateSnapshot(base, sd, sd.getUrl(), "http://test.org/profile", sd.getName());
}
return sd;
}
}

View File

@ -0,0 +1,556 @@
package org.hl7.fhir.conversion.tests;
/*-
* #%L
* org.hl7.fhir.r5
* %%
* Copyright (C) 2014 - 2019 Health Level 7
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.fhir.ucum.UcumEssenceService;
import org.hl7.fhir.convertors.R2016MayToR5Loader;
import org.hl7.fhir.convertors.R2ToR5Loader;
import org.hl7.fhir.convertors.R3ToR5Loader;
import org.hl7.fhir.convertors.R4ToR5Loader;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.context.SimpleWorkerContext;
import org.hl7.fhir.r5.context.SimpleWorkerContext.IContextResourceLoader;
import org.hl7.fhir.r5.model.Parameters;
import org.hl7.fhir.utilities.CSFile;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.cache.PackageCacheManager;
import org.hl7.fhir.utilities.cache.ToolsVersion;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
public class TestingUtilitiesX {
private static final boolean SHOW_DIFF = true;
static public Map<String, IWorkerContext> fcontexts;
public static IWorkerContext context(String version) {
if (fcontexts == null) {
fcontexts = new HashMap<>();
}
if (!fcontexts.containsKey(version)) {
PackageCacheManager pcm;
try {
pcm = new PackageCacheManager(true, ToolsVersion.TOOLS_VERSION);
IWorkerContext fcontext = SimpleWorkerContext.fromPackage(pcm.loadPackage(VersionUtilities.packageForVersion(version), version), loaderForVersion(version));
fcontext.setUcumService(new UcumEssenceService(TestingUtilitiesX.loadTestResourceStream("ucum", "ucum-essence.xml")));
fcontext.setExpansionProfile(new Parameters());
fcontexts.put(version, fcontext);
} catch (Exception e) {
throw new Error(e);
}
}
return fcontexts.get(version);
}
private static IContextResourceLoader loaderForVersion(String version) {
if (Utilities.noString(version))
return null;
if (version.startsWith("1.0"))
return new R2ToR5Loader(new String[] { "Conformance", "StructureDefinition", "ValueSet", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"});
if (version.startsWith("1.4"))
return new R2016MayToR5Loader(new String[] { "Conformance", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"}); // special case
if (version.startsWith("3.0"))
return new R3ToR5Loader(new String[] { "CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"});
if (version.startsWith("4.0"))
return new R4ToR5Loader(new String[] { "CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"});
return null;
}
static public boolean silent;
static public String fixedpath;
static public String contentpath;
public static String home() {
if (fixedpath != null)
return fixedpath;
String s = System.getenv("FHIR_HOME");
if (!Utilities.noString(s))
return s;
s = "C:\\work\\org.hl7.fhir\\build";
// FIXME: change this back
s = "/Users/jamesagnew/git/fhir";
if (new File(s).exists())
return s;
throw new Error("FHIR Home directory not configured");
}
public static String content() throws IOException {
if (contentpath != null)
return contentpath;
String s = "R:\\fhir\\publish";
if (new File(s).exists())
return s;
return Utilities.path(home(), "publish");
}
// diretory that contains all the US implementation guides
public static String us() {
if (fixedpath != null)
return fixedpath;
String s = System.getenv("FHIR_HOME");
if (!Utilities.noString(s))
return s;
s = "C:\\work\\org.hl7.fhir.us";
if (new File(s).exists())
return s;
throw new Error("FHIR US directory not configured");
}
public static String checkXMLIsSame(InputStream f1, InputStream f2) throws Exception {
String result = compareXml(f1, f2);
return result;
}
public static String checkXMLIsSame(String f1, String f2) throws Exception {
String result = compareXml(f1, f2);
if (result != null && SHOW_DIFF) {
String diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge", "WinMergeU.exe");
List<String> command = new ArrayList<String>();
command.add("\"" + diff + "\" \"" + f1 + "\" \"" + f2 + "\"");
ProcessBuilder builder = new ProcessBuilder(command);
builder.directory(new CSFile("c:\\temp"));
builder.start();
}
return result;
}
private static String compareXml(InputStream f1, InputStream f2) throws Exception {
return compareElements("", loadXml(f1).getDocumentElement(), loadXml(f2).getDocumentElement());
}
private static String compareXml(String f1, String f2) throws Exception {
return compareElements("", loadXml(f1).getDocumentElement(), loadXml(f2).getDocumentElement());
}
private static String compareElements(String path, Element e1, Element e2) {
if (!e1.getNamespaceURI().equals(e2.getNamespaceURI()))
return "Namespaces differ at "+path+": "+e1.getNamespaceURI()+"/"+e2.getNamespaceURI();
if (!e1.getLocalName().equals(e2.getLocalName()))
return "Names differ at "+path+": "+e1.getLocalName()+"/"+e2.getLocalName();
path = path + "/"+e1.getLocalName();
String s = compareAttributes(path, e1.getAttributes(), e2.getAttributes());
if (!Utilities.noString(s))
return s;
s = compareAttributes(path, e2.getAttributes(), e1.getAttributes());
if (!Utilities.noString(s))
return s;
Node c1 = e1.getFirstChild();
Node c2 = e2.getFirstChild();
c1 = skipBlankText(c1);
c2 = skipBlankText(c2);
while (c1 != null && c2 != null) {
if (c1.getNodeType() != c2.getNodeType())
return "node type mismatch in children of "+path+": "+Integer.toString(e1.getNodeType())+"/"+Integer.toString(e2.getNodeType());
if (c1.getNodeType() == Node.TEXT_NODE) {
if (!normalise(c1.getTextContent()).equals(normalise(c2.getTextContent())))
return "Text differs at "+path+": "+normalise(c1.getTextContent()) +"/"+ normalise(c2.getTextContent());
}
else if (c1.getNodeType() == Node.ELEMENT_NODE) {
s = compareElements(path, (Element) c1, (Element) c2);
if (!Utilities.noString(s))
return s;
}
c1 = skipBlankText(c1.getNextSibling());
c2 = skipBlankText(c2.getNextSibling());
}
if (c1 != null)
return "node mismatch - more nodes in source in children of "+path;
if (c2 != null)
return "node mismatch - more nodes in target in children of "+path;
return null;
}
private static Object normalise(String text) {
String result = text.trim().replace('\r', ' ').replace('\n', ' ').replace('\t', ' ');
while (result.contains(" "))
result = result.replace(" ", " ");
return result;
}
private static String compareAttributes(String path, NamedNodeMap src, NamedNodeMap tgt) {
for (int i = 0; i < src.getLength(); i++) {
Node sa = src.item(i);
String sn = sa.getNodeName();
if (! (sn.equals("xmlns") || sn.startsWith("xmlns:"))) {
Node ta = tgt.getNamedItem(sn);
if (ta == null)
return "Attributes differ at "+path+": missing attribute "+sn;
if (!normalise(sa.getTextContent()).equals(normalise(ta.getTextContent()))) {
byte[] b1 = unBase64(sa.getTextContent());
byte[] b2 = unBase64(ta.getTextContent());
if (!sameBytes(b1, b2))
return "Attributes differ at "+path+": value "+normalise(sa.getTextContent()) +"/"+ normalise(ta.getTextContent());
}
}
}
return null;
}
private static boolean sameBytes(byte[] b1, byte[] b2) {
if (b1.length == 0 || b2.length == 0)
return false;
if (b1.length != b2.length)
return false;
for (int i = 0; i < b1.length; i++)
if (b1[i] != b2[i])
return false;
return true;
}
private static byte[] unBase64(String text) {
return Base64.decodeBase64(text);
}
private static Node skipBlankText(Node node) {
while (node != null && (((node.getNodeType() == Node.TEXT_NODE) && Utilities.isWhitespace(node.getTextContent())) || (node.getNodeType() == Node.COMMENT_NODE)))
node = node.getNextSibling();
return node;
}
private static Document loadXml(String fn) throws Exception {
return loadXml(new FileInputStream(fn));
}
private static Document loadXml(InputStream fn) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(fn);
}
public static String checkJsonSrcIsSame(String s1, String s2) throws JsonSyntaxException, FileNotFoundException, IOException {
return checkJsonSrcIsSame(s1,s2,true);
}
public static String checkJsonSrcIsSame(String s1, String s2, boolean showDiff) throws JsonSyntaxException, FileNotFoundException, IOException {
String result = compareJsonSrc(s1, s2);
if (result != null && SHOW_DIFF && showDiff) {
String diff = null;
if (System.getProperty("os.name").contains("Linux"))
diff = Utilities.path("/", "usr", "bin", "meld");
else {
if (Utilities.checkFile("WinMerge", Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge"), "\\WinMergeU.exe", null))
diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge", "WinMergeU.exe");
else if (Utilities.checkFile("WinMerge", Utilities.path(System.getenv("ProgramFiles(X86)"), "Meld"), "\\Meld.exe", null))
diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "Meld", "Meld.exe");
}
if (diff == null || diff.isEmpty())
return result;
List<String> command = new ArrayList<String>();
String f1 = Utilities.path("[tmp]", "input" + s1.hashCode() + ".json");
String f2 = Utilities.path("[tmp]", "output" + s2.hashCode() + ".json");
TextFile.stringToFile(s1, f1);
TextFile.stringToFile(s2, f2);
command.add(diff);
if (diff.toLowerCase().contains("meld"))
command.add("--newtab");
command.add(f1);
command.add(f2);
ProcessBuilder builder = new ProcessBuilder(command);
builder.directory(new CSFile(Utilities.path("[tmp]")));
builder.start();
}
return result;
}
public static String checkJsonIsSame(String f1, String f2) throws JsonSyntaxException, FileNotFoundException, IOException {
String result = compareJson(f1, f2);
if (result != null && SHOW_DIFF) {
String diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge", "WinMergeU.exe");
List<String> command = new ArrayList<String>();
command.add("\"" + diff + "\" \"" + f1 + "\" \"" + f2 + "\"");
ProcessBuilder builder = new ProcessBuilder(command);
builder.directory(new CSFile("c:\\temp"));
builder.start();
}
return result;
}
private static String compareJsonSrc(String f1, String f2) throws JsonSyntaxException, FileNotFoundException, IOException {
JsonObject o1 = (JsonObject) new com.google.gson.JsonParser().parse(f1);
JsonObject o2 = (JsonObject) new com.google.gson.JsonParser().parse(f2);
return compareObjects("", o1, o2);
}
private static String compareJson(String f1, String f2) throws JsonSyntaxException, FileNotFoundException, IOException {
JsonObject o1 = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(f1));
JsonObject o2 = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(f2));
return compareObjects("", o1, o2);
}
private static String compareObjects(String path, JsonObject o1, JsonObject o2) {
for (Map.Entry<String, JsonElement> en : o1.entrySet()) {
String n = en.getKey();
if (!n.equals("fhir_comments")) {
if (o2.has(n)) {
String s = compareNodes(path+'.'+n, en.getValue(), o2.get(n));
if (!Utilities.noString(s))
return s;
}
else
return "properties differ at "+path+": missing property "+n;
}
}
for (Map.Entry<String, JsonElement> en : o2.entrySet()) {
String n = en.getKey();
if (!n.equals("fhir_comments")) {
if (!o1.has(n))
return "properties differ at "+path+": missing property "+n;
}
}
return null;
}
private static String compareNodes(String path, JsonElement n1, JsonElement n2) {
if (n1.getClass() != n2.getClass())
return "properties differ at "+path+": type "+n1.getClass().getName()+"/"+n2.getClass().getName();
else if (n1 instanceof JsonPrimitive) {
JsonPrimitive p1 = (JsonPrimitive) n1;
JsonPrimitive p2 = (JsonPrimitive) n2;
if (p1.isBoolean() && p2.isBoolean()) {
if (p1.getAsBoolean() != p2.getAsBoolean())
return "boolean property values differ at "+path+": type "+p1.getAsString()+"/"+p2.getAsString();
} else if (p1.isString() && p2.isString()) {
String s1 = p1.getAsString();
String s2 = p2.getAsString();
if (!(s1.contains("<div") && s2.contains("<div")))
if (!s1.equals(s2))
if (!sameBytes(unBase64(s1), unBase64(s2)))
return "string property values differ at "+path+": type "+s1+"/"+s2;
} else if (p1.isNumber() && p2.isNumber()) {
if (!p1.getAsString().equals(p2.getAsString()))
return "number property values differ at "+path+": type "+p1.getAsString()+"/"+p2.getAsString();
} else
return "property types differ at "+path+": type "+p1.getAsString()+"/"+p2.getAsString();
}
else if (n1 instanceof JsonObject) {
String s = compareObjects(path, (JsonObject) n1, (JsonObject) n2);
if (!Utilities.noString(s))
return s;
} else if (n1 instanceof JsonArray) {
JsonArray a1 = (JsonArray) n1;
JsonArray a2 = (JsonArray) n2;
if (a1.size() != a2.size())
return "array properties differ at "+path+": count "+Integer.toString(a1.size())+"/"+Integer.toString(a2.size());
for (int i = 0; i < a1.size(); i++) {
String s = compareNodes(path+"["+Integer.toString(i)+"]", a1.get(i), a2.get(i));
if (!Utilities.noString(s))
return s;
}
}
else if (n1 instanceof JsonNull) {
} else
return "unhandled property "+n1.getClass().getName();
return null;
}
public static String temp() {
if (new File("c:\\temp").exists())
return "c:\\temp";
return System.getProperty("java.io.tmpdir");
}
public static String checkTextIsSame(String s1, String s2) throws JsonSyntaxException, FileNotFoundException, IOException {
return checkTextIsSame(s1,s2,true);
}
public static String checkTextIsSame(String s1, String s2, boolean showDiff) throws JsonSyntaxException, FileNotFoundException, IOException {
String result = compareText(s1, s2);
if (result != null && SHOW_DIFF && showDiff) {
String diff = null;
if (System.getProperty("os.name").contains("Linux"))
diff = Utilities.path("/", "usr", "bin", "meld");
else {
if (Utilities.checkFile("WinMerge", Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge"), "\\WinMergeU.exe", null))
diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge", "WinMergeU.exe");
else if (Utilities.checkFile("WinMerge", Utilities.path(System.getenv("ProgramFiles(X86)"), "Meld"), "\\Meld.exe", null))
diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "Meld", "Meld.exe");
}
if (diff == null || diff.isEmpty())
return result;
List<String> command = new ArrayList<String>();
String f1 = Utilities.path("[tmp]", "input" + s1.hashCode() + ".json");
String f2 = Utilities.path("[tmp]", "output" + s2.hashCode() + ".json");
TextFile.stringToFile(s1, f1);
TextFile.stringToFile(s2, f2);
command.add(diff);
if (diff.toLowerCase().contains("meld"))
command.add("--newtab");
command.add(f1);
command.add(f2);
ProcessBuilder builder = new ProcessBuilder(command);
builder.directory(new CSFile(Utilities.path("[tmp]")));
builder.start();
}
return result;
}
private static String compareText(String s1, String s2) {
for (int i = 0; i < Integer.min(s1.length(), s2.length()); i++) {
if (s1.charAt(i) != s2.charAt(i))
return "Strings differ at character "+Integer.toString(i)+": '"+s1.charAt(i) +"' vs '"+s2.charAt(i)+"'";
}
if (s1.length() != s2.length())
return "Strings differ in length: "+Integer.toString(s1.length())+" vs "+Integer.toString(s2.length())+" but match to the end of the shortest";
return null;
}
public static boolean findTestResource(String... paths) throws IOException {
if (new File("../../fhir-test-cases").exists() && isTryToLoadFromFileSystem()) {
String n = Utilities.path(System.getProperty("user.dir"), "..", "..", "fhir-test-cases", Utilities.path(paths));
return new File(n).exists();
} else {
String classpath = ("/org/hl7/fhir/testcases/"+ Utilities.pathURL(paths));
try {
InputStream inputStream = TestingUtilitiesX.class.getResourceAsStream(classpath);
return inputStream != null;
} catch (Throwable t) {
return false;
}
}
}
// TODO: JA need to figure out how to detect that we're running in maven
private static boolean isTryToLoadFromFileSystem() {
return !"true".equals(System.getProperty("dont_load_from_filesystem"));
}
public static String loadTestResource(String... paths) throws IOException {
if (new File("../../fhir-test-cases").exists() && isTryToLoadFromFileSystem()) {
String n = Utilities.path(System.getProperty("user.dir"), "..", "..", "fhir-test-cases", Utilities.path(paths));
// ok, we'll resolve this locally
return TextFile.fileToString(new File(n));
} else {
// resolve from the package
String contents;
String classpath = ("/org/hl7/fhir/testcases/"+ Utilities.pathURL(paths));
try (InputStream inputStream = TestingUtilitiesX.class.getResourceAsStream(classpath)) {
if (inputStream == null) {
throw new IOException("Can't find file on classpath: " + classpath);
}
contents = IOUtils.toString(inputStream, java.nio.charset.StandardCharsets.UTF_8);
}
return contents;
}
}
public static InputStream loadTestResourceStream(String... paths) throws IOException {
if (new File("../../fhir-test-cases").exists() && isTryToLoadFromFileSystem()) {
String n = Utilities.path(System.getProperty("user.dir"), "..", "..", "fhir-test-cases", Utilities.path(paths));
return new FileInputStream(n);
} else {
String classpath = ("/org/hl7/fhir/testcases/"+ Utilities.pathURL(paths));
InputStream s = TestingUtilitiesX.class.getResourceAsStream(classpath);
if (s == null) {
throw new Error("unable to find resource "+classpath);
}
return s;
}
}
public static byte[] loadTestResourceBytes(String... paths) throws IOException {
if (new File("../../fhir-test-cases").exists() && isTryToLoadFromFileSystem()) {
String n = Utilities.path(System.getProperty("user.dir"), "..", "..", "fhir-test-cases", Utilities.path(paths));
return TextFile.fileToBytes(n);
} else {
String classpath = ("/org/hl7/fhir/testcases/"+ Utilities.pathURL(paths));
InputStream s = TestingUtilitiesX.class.getResourceAsStream(classpath);
if (s == null) {
throw new Error("unable to find resource "+classpath);
}
return TextFile.streamToBytes(s);
}
}
public static String tempFile(String folder, String name) throws IOException {
String tmp = tempFolder(folder);
return Utilities.path(tmp, name);
}
public static String tempFolder(String name) throws IOException {
File tmp = new File("C:\\temp");
if (tmp.exists() && tmp.isDirectory()) {
String path = Utilities.path("C:\\temp", name);
Utilities.createDirectory(path);
return path;
} else if (new File("/tmp").exists()) {
String path = Utilities.path("/tmp", name);
Utilities.createDirectory(path);
return path;
} else {
String path = Utilities.path(System.getProperty("java.io.tmpdir"), name);
Utilities.createDirectory(path);
return path;
}
}
}