Merge pull request #1640 from hapifhir/do-20240122-base-engine
Maintain a map of pre-built ValidationEngine instances
This commit is contained in:
commit
53ab71f118
|
@ -164,4 +164,17 @@ class I18nBaseTest {
|
|||
assertNull(enLocale.getRootKeyFromPlural(rootKey + "_many"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessagesChangeWhenLocaleDoes() {
|
||||
I18nTestClass i18nInstance = new I18nTestClass();
|
||||
i18nInstance.setLocale(Locale.forLanguageTag("de"));
|
||||
|
||||
String deMessage = i18nInstance.formatMessage(I18nConstants.ERROR_PARSING_JSON_, "test");
|
||||
assertEquals("Fehler beim Parsen von JSON: test", deMessage);
|
||||
|
||||
i18nInstance.setLocale(Locale.forLanguageTag("en"));
|
||||
String enMessage = i18nInstance.formatMessage(I18nConstants.ERROR_PARSING_JSON_, "test");
|
||||
assertEquals("Error parsing JSON: test", enMessage);
|
||||
|
||||
}
|
||||
}
|
|
@ -316,7 +316,7 @@ public class IgLoader implements IValidationEngineLoader {
|
|||
|
||||
public void scanForIgVersion(String src,
|
||||
boolean recursive,
|
||||
VersionSourceInformation versions) throws Exception {
|
||||
VersionSourceInformation versions) throws IOException {
|
||||
Map<String, ByteProvider> source = loadIgSourceForVersion(src, recursive, true, versions);
|
||||
if (source != null) {
|
||||
if (source.containsKey("version.info")) {
|
||||
|
|
|
@ -105,7 +105,7 @@ public class ValidatorCli {
|
|||
public static final String JAVA_DISABLED_PROXY_SCHEMES = "jdk.http.auth.proxying.disabledSchemes";
|
||||
public static final String JAVA_USE_SYSTEM_PROXIES = "java.net.useSystemProxies";
|
||||
|
||||
private static ValidationService validationService = new ValidationService();
|
||||
private final static ValidationService validationService = new ValidationService();
|
||||
|
||||
protected ValidationService myValidationService;
|
||||
|
||||
|
@ -408,8 +408,4 @@ public class ValidatorCli {
|
|||
return validationEngine;
|
||||
}
|
||||
|
||||
protected void validateScan(CliContext cliContext, ValidationEngine validator) throws Exception {
|
||||
Scanner validationScanner = new Scanner(validator.getContext(), validator.getValidator(null), validator.getIgLoader(), validator.getFhirPathEngine());
|
||||
validationScanner.validateScan(cliContext.getOutput(), cliContext.getSources());
|
||||
}
|
||||
}
|
|
@ -24,6 +24,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||
*/
|
||||
public class CliContext {
|
||||
|
||||
@JsonProperty("baseEngine")
|
||||
private String baseEngine = null;
|
||||
@JsonProperty("doNative")
|
||||
private boolean doNative = false;
|
||||
@JsonProperty("hintAboutNonMustSupport")
|
||||
|
@ -161,6 +163,16 @@ public class CliContext {
|
|||
@JsonProperty("bestPracticeLevel")
|
||||
private BestPracticeWarningLevel bestPracticeLevel = BestPracticeWarningLevel.Warning;
|
||||
|
||||
@JsonProperty("baseEngine")
|
||||
public String getBaseEngine() {
|
||||
return baseEngine;
|
||||
}
|
||||
|
||||
@JsonProperty("baseEngine")
|
||||
public CliContext setBaseEngine(String baseEngine) {
|
||||
this.baseEngine = baseEngine;
|
||||
return this;
|
||||
}
|
||||
|
||||
@JsonProperty("map")
|
||||
public String getMap() {
|
||||
|
@ -771,7 +783,8 @@ public class CliContext {
|
|||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
CliContext that = (CliContext) o;
|
||||
return doNative == that.doNative &&
|
||||
return Objects.equals(baseEngine, that.baseEngine) &&
|
||||
doNative == that.doNative &&
|
||||
hintAboutNonMustSupport == that.hintAboutNonMustSupport &&
|
||||
recursive == that.recursive &&
|
||||
doDebug == that.doDebug &&
|
||||
|
@ -824,7 +837,7 @@ public class CliContext {
|
|||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(doNative, extensions, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching,
|
||||
return Objects.hash(baseEngine, doNative, extensions, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching,
|
||||
noExtensibleBindingMessages, noInvariants, displayWarnings, wantInvariantsInMessages, map, output, outputSuffix, htmlOutput, txServer, sv, txLog, txCache, mapLog, lang, srcLang, tgtLang, fhirpath, snomedCT,
|
||||
targetVer, igs, questionnaireMode, level, profiles, sources, inputs, mode, locale, locations, crumbTrails, forPublication, showTimes, allowExampleUrls, outputStyle, jurisdiction, noUnicodeBiDiControlChars, watchMode, watchScanDelay, watchSettleTime, bestPracticeLevel,
|
||||
htmlInMarkdownCheck, allowDoubleQuotesInFHIRPath, checkIPSCodes);
|
||||
|
@ -833,7 +846,8 @@ public class CliContext {
|
|||
@Override
|
||||
public String toString() {
|
||||
return "CliContext{" +
|
||||
"doNative=" + doNative +
|
||||
"baseEngine=" + baseEngine +
|
||||
", doNative=" + doNative +
|
||||
", extensions=" + extensions +
|
||||
", hintAboutNonMustSupport=" + hintAboutNonMustSupport +
|
||||
", recursive=" + recursive +
|
||||
|
|
|
@ -117,6 +117,11 @@ public class PassiveExpiringSessionCache implements SessionCache {
|
|||
return cachedSessions.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanUp() {
|
||||
removeExpiredSessions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Session ids generated internally are UUID {@link String}.
|
||||
* @return A new {@link String} session id.
|
||||
|
|
|
@ -23,9 +23,6 @@ public interface SessionCache {
|
|||
*/
|
||||
String cacheSession(String sessionId, ValidationEngine validationEngine);
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the passed in {@link String} id exists in the set of stored session id.
|
||||
* @param sessionId The {@link String} id to search for.
|
||||
|
@ -46,4 +43,9 @@ public interface SessionCache {
|
|||
*/
|
||||
Set<String> getSessionIds();
|
||||
|
||||
/**
|
||||
* Performs any pending maintenance operations needed by the cache.
|
||||
* */
|
||||
public void cleanUp();
|
||||
|
||||
}
|
|
@ -2,7 +2,6 @@ package org.hl7.fhir.validation.cli.services;
|
|||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
|
@ -11,6 +10,7 @@ import java.lang.management.MemoryMXBean;
|
|||
import java.net.URISyntaxException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
@ -75,25 +75,41 @@ public class ValidationService {
|
|||
private final SessionCache sessionCache;
|
||||
private String runDate;
|
||||
|
||||
private final Map<String, ValidationEngine> baseEngines = new ConcurrentHashMap<>();
|
||||
|
||||
public void putBaseEngine(String key, CliContext cliContext) throws IOException, URISyntaxException {
|
||||
if (cliContext.getSv() == null) {
|
||||
throw new IllegalArgumentException("Cannot create a base engine without an explicit version");
|
||||
}
|
||||
String definitions = VersionUtilities.packageForVersion(cliContext.getSv()) + "#" + VersionUtilities.getCurrentVersion(cliContext.getSv());
|
||||
|
||||
ValidationEngine baseEngine = buildValidationEngine(cliContext, definitions, new TimeTracker());
|
||||
baseEngines.put(key, baseEngine);
|
||||
}
|
||||
|
||||
public ValidationEngine getBaseEngine(String key) {
|
||||
return baseEngines.get(key);
|
||||
}
|
||||
|
||||
public Set<String> getBaseEngineKeys() { return baseEngines.keySet(); }
|
||||
|
||||
public boolean hasBaseEngineForKey(String key) { return baseEngines.containsKey(key); }
|
||||
|
||||
public ValidationService() {
|
||||
sessionCache = new PassiveExpiringSessionCache();
|
||||
runDate = new SimpleDateFormat("hh:mm:ss", new Locale("en", "US")).format(new Date());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public ValidationService(SessionCache cache) {
|
||||
this.sessionCache = cache;
|
||||
}
|
||||
|
||||
public ValidationResponse validateSources(ValidationRequest request) throws Exception {
|
||||
if (request.getCliContext().getSv() == null) {
|
||||
String sv = determineVersion(request.getCliContext(), request.sessionId);
|
||||
request.getCliContext().setSv(sv);
|
||||
}
|
||||
|
||||
String definitions = VersionUtilities.packageForVersion(request.getCliContext().getSv()) + "#" + VersionUtilities.getCurrentVersion(request.getCliContext().getSv());
|
||||
|
||||
TimeTracker timeTracker = new TimeTracker();
|
||||
String sessionId = initializeValidator(request.getCliContext(), definitions, timeTracker, request.sessionId);
|
||||
String sessionId = initializeValidator(request.getCliContext(), null, timeTracker, request.sessionId);
|
||||
ValidationEngine validator = sessionCache.fetchSessionValidatorEngine(sessionId);
|
||||
|
||||
if (request.getCliContext().getProfiles().size() > 0) {
|
||||
|
@ -180,7 +196,7 @@ public class ValidationService {
|
|||
return outcome;
|
||||
}
|
||||
|
||||
public VersionSourceInformation scanForVersions(CliContext cliContext) throws Exception {
|
||||
public VersionSourceInformation scanForVersions(CliContext cliContext) throws IOException {
|
||||
VersionSourceInformation versions = new VersionSourceInformation();
|
||||
IgLoader igLoader = new IgLoader(
|
||||
new FilesystemPackageCacheManager.Builder().build(),
|
||||
|
@ -473,16 +489,42 @@ public class ValidationService {
|
|||
if (sessionId != null) {
|
||||
System.out.println("No such cached session exists for session id " + sessionId + ", re-instantiating validator.");
|
||||
}
|
||||
System.out.println("Building new validator engine from CliContext");
|
||||
ValidationEngine validator = buildValidationEngine(cliContext, definitions, tt);
|
||||
sessionId = sessionCache.cacheSession(validator);
|
||||
sessionCache.cleanUp();
|
||||
if (cliContext.getSv() == null) {
|
||||
String sv = determineVersion(cliContext);
|
||||
cliContext.setSv(sv);
|
||||
}
|
||||
final String engineDefinitions = definitions != null ? definitions : VersionUtilities.packageForVersion(cliContext.getSv()) + "#" + VersionUtilities.getCurrentVersion(cliContext.getSv());
|
||||
|
||||
ValidationEngine validationEngine = getValidationEngineFromCliContext(cliContext, engineDefinitions, tt);
|
||||
sessionId = sessionCache.cacheSession(validationEngine);
|
||||
System.out.println("Cached new session. Cache size = " + sessionCache.getSessionIds().size());
|
||||
|
||||
} else {
|
||||
System.out.println("Cached session exists for session id " + sessionId + ", returning stored validator session id. Cache size = " + sessionCache.getSessionIds().size());
|
||||
}
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
private ValidationEngine getValidationEngineFromCliContext(CliContext cliContext, String definitions, TimeTracker tt) throws Exception {
|
||||
ValidationEngine validationEngine;
|
||||
if (cliContext.getBaseEngine() != null && hasBaseEngineForKey(cliContext.getBaseEngine())) {
|
||||
validationEngine = new ValidationEngine(getBaseEngine(cliContext.getBaseEngine()));
|
||||
/* As a service, it wouldn't be efficient to have a base validation engine
|
||||
* for every language. So we just use the baseEngine and set the language
|
||||
* manually afterward.
|
||||
*/
|
||||
validationEngine.setLanguage(cliContext.getLang());
|
||||
validationEngine.setLocale(cliContext.getLocale());
|
||||
} else {
|
||||
if (definitions == null) {
|
||||
throw new IllegalArgumentException("Cannot create a validator engine (definitions == null)");
|
||||
}
|
||||
validationEngine = buildValidationEngine(cliContext, definitions, tt);
|
||||
}
|
||||
return validationEngine;
|
||||
}
|
||||
|
||||
protected ValidationEngine.ValidationEngineBuilder getValidationEngineBuilder() {
|
||||
return new ValidationEngine.ValidationEngineBuilder();
|
||||
}
|
||||
|
@ -564,12 +606,7 @@ public class ValidationService {
|
|||
System.out.println(" Package Summary: "+ validationEngine.getContext().loadedPackageSummary());
|
||||
}
|
||||
|
||||
|
||||
public String determineVersion(CliContext cliContext) throws Exception {
|
||||
return determineVersion(cliContext, null);
|
||||
}
|
||||
|
||||
public String determineVersion(CliContext cliContext, String sessionId) throws Exception {
|
||||
public String determineVersion(CliContext cliContext) throws IOException {
|
||||
if (cliContext.getMode() != EngineMode.VALIDATION && cliContext.getMode() != EngineMode.INSTALL) {
|
||||
return "5.0";
|
||||
}
|
||||
|
@ -588,7 +625,7 @@ public class ValidationService {
|
|||
System.out.println("-> use version " + versions.version());
|
||||
return versions.version();
|
||||
}
|
||||
throw new Exception("-> Multiple versions found. Specify a particular version using the -version parameter");
|
||||
throw new IllegalArgumentException("-> Multiple versions found. Specify a particular version using the -version parameter");
|
||||
}
|
||||
|
||||
public void generateSpreadsheet(CliContext cliContext, ValidationEngine validator) throws Exception {
|
||||
|
|
|
@ -44,7 +44,7 @@ import org.junit.jupiter.api.Test;
|
|||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
class ValidationServiceTest {
|
||||
class ValidationServiceTests {
|
||||
|
||||
final String DUMMY_SOURCE = "dummySource";
|
||||
final String DUMMY_SOURCE1 = "dummySource1";
|
||||
|
@ -54,34 +54,74 @@ class ValidationServiceTest {
|
|||
|
||||
final String DUMMY_SV = "1.2.3";
|
||||
|
||||
@DisplayName("Test validation session persists in session cache")
|
||||
@Test
|
||||
void validateSources() throws Exception {
|
||||
void validationSessionTest() throws Exception {
|
||||
TestingUtilities.injectCorePackageLoader();
|
||||
SessionCache sessionCache = Mockito.spy(new PassiveExpiringSessionCache());
|
||||
ValidationService myService = new ValidationService(sessionCache);
|
||||
ValidationService myService = Mockito.spy(new ValidationService(sessionCache));
|
||||
|
||||
String resource = IOUtils.toString(getFileFromResourceAsStream("detected_issues.json"), StandardCharsets.UTF_8);
|
||||
List<FileInfo> filesToValidate = new ArrayList<>();
|
||||
filesToValidate.add(new FileInfo().setFileName("test_resource.json").setFileContent(resource).setFileType(Manager.FhirFormat.JSON.getExtension()));
|
||||
List<FileInfo> filesToValidate = getFilesToValidate();
|
||||
|
||||
ValidationRequest request = new ValidationRequest().setCliContext(new CliContext().setTxServer(FhirSettings.getTxFhirDevelopment()).setTxCache(getTerminologyCacheDirectory("validationService"))).setFilesToValidate(filesToValidate);
|
||||
// Validation run 1...nothing cached yet
|
||||
myService.validateSources(request);
|
||||
verify(sessionCache, Mockito.times(1)).cacheSession(ArgumentMatchers.any(ValidationEngine.class));
|
||||
|
||||
verify(sessionCache, Mockito.times(1)).cleanUp();
|
||||
verify(myService, Mockito.times(1)).buildValidationEngine(any(), any(), any());
|
||||
Set<String> sessionIds = sessionCache.getSessionIds();
|
||||
if (sessionIds.stream().findFirst().isPresent()) {
|
||||
// Verify that after 1 run there is only one entry within the cache
|
||||
Assertions.assertEquals(1, sessionIds.size());
|
||||
myService.validateSources(request);
|
||||
// Verify that the cache has been called on once with the id created in the first run
|
||||
verify(sessionCache, Mockito.times(1)).fetchSessionValidatorEngine(sessionIds.stream().findFirst().get());
|
||||
myService.validateSources(request.setSessionId(sessionIds.stream().findFirst().get()));
|
||||
// Verify that the cache has been called on twice with the id created in the first run
|
||||
verify(sessionCache, Mockito.times(2)).fetchSessionValidatorEngine(sessionIds.stream().findFirst().get());
|
||||
verify(sessionCache, Mockito.times(1)).cleanUp();
|
||||
verify(myService, Mockito.times(1)).buildValidationEngine(any(), any(), any());
|
||||
} else {
|
||||
// If no sessions exist within the cache after a run, we auto-fail.
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("Test validation session will inherit a base validation engine")
|
||||
@Test
|
||||
void validationSessionBaseEngineTest() throws Exception {
|
||||
TestingUtilities.injectCorePackageLoader();
|
||||
|
||||
ValidationService myService = Mockito.spy(new ValidationService());
|
||||
|
||||
CliContext baseContext = new CliContext().setBaseEngine("myDummyKey").setSv("4.0.1").setTxServer(FhirSettings.getTxFhirDevelopment()).setTxCache(getTerminologyCacheDirectory("validationService"));
|
||||
myService.putBaseEngine("myDummyKey", baseContext);
|
||||
verify(myService, Mockito.times(1)).buildValidationEngine(any(), any(), any());
|
||||
|
||||
{
|
||||
final List<FileInfo> filesToValidate = getFilesToValidate();
|
||||
final ValidationRequest request = new ValidationRequest().setCliContext(new CliContext().setSv("4.0.1")).setFilesToValidate(filesToValidate);
|
||||
myService.validateSources(request);
|
||||
|
||||
verify(myService, Mockito.times(0)).getBaseEngine("myDummyKey");
|
||||
verify(myService, Mockito.times(2)).buildValidationEngine(any(), any(), any());
|
||||
}
|
||||
|
||||
{
|
||||
final List<FileInfo> filesToValidate = getFilesToValidate();
|
||||
final ValidationRequest request = new ValidationRequest().setCliContext(new CliContext().setBaseEngine("myDummyKey")).setFilesToValidate(filesToValidate);
|
||||
myService.validateSources(request);
|
||||
|
||||
verify(myService, Mockito.times(1)).getBaseEngine("myDummyKey");
|
||||
verify(myService, Mockito.times(2)).buildValidationEngine(any(), any(), any());
|
||||
}
|
||||
}
|
||||
|
||||
private List<FileInfo> getFilesToValidate() throws IOException {
|
||||
List<FileInfo> filesToValidate = new ArrayList<>();
|
||||
String resource = IOUtils.toString(getFileFromResourceAsStream("detected_issues.json"), StandardCharsets.UTF_8);
|
||||
|
||||
filesToValidate.add(new FileInfo().setFileName("test_resource.json").setFileContent(resource).setFileType(Manager.FhirFormat.JSON.getExtension()));
|
||||
return filesToValidate;
|
||||
}
|
||||
|
||||
private InputStream getFileFromResourceAsStream(String fileName) {
|
||||
// The class loader that loaded the class
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
|
@ -1,5 +1,6 @@
|
|||
package org.hl7.fhir.validation.tests;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -18,6 +19,7 @@ import org.hl7.fhir.validation.IgLoader;
|
|||
import org.hl7.fhir.validation.ValidationEngine;
|
||||
import org.hl7.fhir.validation.tests.utilities.TestUtilities;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ValidationEngineTests {
|
||||
|
@ -27,6 +29,88 @@ public class ValidationEngineTests {
|
|||
|
||||
public static boolean inbuild;
|
||||
|
||||
@Test
|
||||
@DisplayName("A ValidationEngine copied from another validation engine shouldn't interfere with the original during validations")
|
||||
void validateWithParallelCopiedEngine() throws Exception {
|
||||
|
||||
final String INPUT_1 = "patient-duplicate.json";
|
||||
final String INPUT_2 = "patient-lang1.json";
|
||||
final String INPUT_3 = "patient-id-bad-1.json";
|
||||
|
||||
final String[] ISSUE_CODES_1 = { "invalid" };
|
||||
final String[] ISSUE_CODES_2 = {"business-rule"};
|
||||
final String[] ISSUE_CODES_3 = {"invalid", "invariant"};
|
||||
|
||||
ValidationEngine originalEngine = TestUtilities.getValidationEngine("hl7.fhir.r4.core#4.0.1", DEF_TX, FhirPublication.R4, "4.0.1");
|
||||
|
||||
final ValidationEngine[] validationEngines = new ValidationEngine[10];
|
||||
validationEngines[0] = originalEngine;
|
||||
|
||||
final OperationOutcome[] outcomes = new OperationOutcome[validationEngines.length];
|
||||
|
||||
for (int i = 1; i < validationEngines.length; i++) {
|
||||
validationEngines[i] = new ValidationEngine(originalEngine);
|
||||
}
|
||||
|
||||
final String[] testInputs = {
|
||||
INPUT_1,
|
||||
INPUT_1,
|
||||
INPUT_2,
|
||||
INPUT_3,
|
||||
INPUT_1,
|
||||
INPUT_2,
|
||||
INPUT_3,
|
||||
INPUT_1,
|
||||
INPUT_2,
|
||||
INPUT_3
|
||||
};
|
||||
// Pick 3 validation cases
|
||||
final String[][] testCodes = {
|
||||
ISSUE_CODES_1,
|
||||
ISSUE_CODES_1,
|
||||
ISSUE_CODES_2,
|
||||
ISSUE_CODES_3,
|
||||
ISSUE_CODES_1,
|
||||
ISSUE_CODES_2,
|
||||
ISSUE_CODES_3,
|
||||
ISSUE_CODES_1,
|
||||
ISSUE_CODES_2,
|
||||
ISSUE_CODES_3
|
||||
};
|
||||
|
||||
|
||||
List<Thread> threads = new ArrayList<>();
|
||||
for (int i = 0; i < validationEngines.length; i++) {
|
||||
final int index = i;
|
||||
Thread t = new Thread(() -> {
|
||||
try {
|
||||
final String testInput = testInputs[index];
|
||||
outcomes[index] = validationEngines[index].validate(FhirFormat.JSON, TestingUtilities.loadTestResourceStream("validator", testInput), null);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.err.println("Thread " + index + " failed");
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
threads.add(t);
|
||||
}
|
||||
threads.forEach(t -> {
|
||||
try {
|
||||
t.join();
|
||||
} catch (InterruptedException e) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
for (int i = 0; i < outcomes.length; i++) {
|
||||
assertEquals(testCodes[i].length, outcomes[i].getIssue().size());
|
||||
for (int j = 0; j < outcomes[i].getIssue().size(); j++) {
|
||||
System.out.print(i + " " + j);
|
||||
assertEquals(testCodes[i][j], outcomes[i].getIssue().get(j).getCode().toCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test401Xml() throws Exception {
|
||||
if (!TestUtilities.silent)
|
||||
|
|
Loading…
Reference in New Issue