# HG changeset patch # User darcy # Date 1360298826 28800 # Node ID 5125b9854d079314a4a4075e3588f8e7593bfd3a # Parent b386b8c453874415ca5a30d98b6afc36ce772180 7195131: Update 2 compiler combo tests for repeating annotations to include package and default use cases Reviewed-by: darcy Contributed-by: sonali.goel@oracle.com diff -r b386b8c45387 -r 5125b9854d07 test/tools/javac/annotations/repeatingAnnotations/combo/Helper.java --- a/test/tools/javac/annotations/repeatingAnnotations/combo/Helper.java Wed Feb 06 23:10:35 2013 +0000 +++ b/test/tools/javac/annotations/repeatingAnnotations/combo/Helper.java Thu Feb 07 20:47:06 2013 -0800 @@ -40,14 +40,17 @@ IMPORTINHERITED("import java.lang.annotation.Inherited;\n"), IMPORTRETENTION("import java.lang.annotation.Retention;\n" + "\nimport java.lang.annotation.RetentionPolicy;\n"), + IMPORTSTMTS("import java.lang.annotation.*;\n"), REPEATABLE("\n@Repeatable(FooContainer.class)\n"), CONTAINER("@interface FooContainer {\n" +" Foo[] value();\n}\n"), BASE("@interface Foo {}\n"), + BASEANNO("@Foo"), REPEATABLEANNO("\n@Foo() @Foo()"), DEPRECATED("\n@Deprecated"), DOCUMENTED("\n@Documented"), INHERITED("\n@Inherited"), - RETENTION("@Retention(RetentionPolicy.#VAL)\n"); + RETENTION("@Retention(RetentionPolicy.#VAL)\n"), + TARGET("\n@Target(#VAL)\n"); private String val; @@ -69,6 +72,7 @@ public static final String template = "/*PACKAGE*/\n" + "//pkg test;\n\n" + + "/*ANNODATA*/\n" + // import statements, declaration of Foo/FooContainer "/*TYPE*/ //class\n" + "class #ClassName {\n" + " /*FIELD*/ //instance var\n" + @@ -97,7 +101,11 @@ "interface TestInterface {}\n\n" + "/*TYPE*/\n" + "/*ANNOTATION_TYPE*/\n" + - "@interface TestAnnotationType{}\n"; + "@interface TestAnnotationType{}\n" + + "class TestPkg {}\n" + + "class TestTypeAnno {\n" + + " String /*TYPE_USE*/[] arr;\n" + + "}"; // Create and compile FileObject using values for className and contents public static boolean compileCode(String className, String contents, @@ -150,3 +158,4 @@ } } } + diff -r b386b8c45387 -r 5125b9854d07 test/tools/javac/annotations/repeatingAnnotations/combo/TargetAnnoCombo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/tools/javac/annotations/repeatingAnnotations/combo/TargetAnnoCombo.java Thu Feb 07 20:47:06 2013 -0800 @@ -0,0 +1,469 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 7195131 + * @author sogoel + * @summary Combo test for all possible combinations for Target values + * @build Helper + * @compile TargetAnnoCombo.java TestCaseGenerator.java + * @run main TargetAnnoCombo + */ + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaFileObject; + +/* + * TargetAnnoCombo gets a list of test case numbers using TestCaseGenerator. + * For each of the test case number, @Target sets for base and container annotations + * are determined, source files are generated, compiled, and the result is verified + * based on if the @Target set for base and container is a positive or negative combination. + * + * @Target sets for base and container annotations are determined using a bit mapping of + * 10 ElementType enum constants defined in JDK8. + * + * Bit Target value + * 0 "ElementType.ANNOTATION_TYPE" + * 1 "ElementType.CONSTRUCTOR" + * 2 "ElementType.FIELD" + * 3 "ElementType.LOCAL_VARIABLE" + * 4 "ElementType.METHOD" + * 5 "ElementType.TYPE" + * 6 "ElementType.PARAMETER" + * 7 "ElementType.PACKAGE" + * 8 "ElementType.TYPE_USE" + * 9 "ElementType.TYPE_PARAMETER" + * + * Group 1: + * 20 bits mapping, representing a test case number, is used for all target set + * combinations ( 0 to 1048575 ) including empty @Target sets => @Target({}). + * From this 20 bits, 10 bits are for base followed by 10 bits for container + * where each bit maps to an ElementType enum constant defined in JDK8. + * + * Examples: + * Test case number: 4, binary: 100 => container=100, base=[], container=["ElementType.FIELD"] + * Test case number: 1003575, binary: 11110101000000110111 => base=1111010100, container=0000110111; + * base=["ElementType.PARAMETER", "ElementType.TYPE_USE", "ElementType.METHOD", "ElementType.FIELD", "ElementType.PACKAGE", "ElementType.TYPE_PARAMETER"], + * container=["ElementType.TYPE", "ElementType.METHOD", "ElementType.ANNOTATION_TYPE", "ElementType.CONSTRUCTOR", "ElementType.FIELD"] + * + * In the following groups, no @Target set is represented by null. + * Group 2: + * @Target is not defined on base. + * Target sets for container are determined using the 10-bit binary number + * resulting in 1024 test cases, mapping them to test case numbers from + * 1048576 to (1048576 + 1023) => 1048576 to 1049599. + * + * Example: + * Test case number: 1048587 => 1048587 - 1048576 = test case 11 in Group 2, binary: 1011 => + * base = null, + * container = ["ElementType.ANNOTATION_TYPE","ElementType.CONSTRUCTOR","ElementType.LOCAL_VARIABLE"] + * + * Group 3: + * @Target is not defined on container + * Target sets for base are determined using the 10-bit binary number + * resulting in 1024 test cases, mapping them to test case numbers from + * 1049600 to (1049600 + 1023) => 1049600 to 1050623. + * + * Example: + * Test case number: 1049708 => 1049708 - 1049600 = test case 108 in Group 3, binary: 1101100 => + * base = ["ElementType.FIELD", "ElementType.LOCAL_VARIABLE", "ElementType.TYPE", "ElementType.PARAMETER"], + * container = null + * + * For the above group, test case number: 1049855 gives compiler error, JDK-8006547 filed + * + * Group 4: + * @Target not defined for both base and container annotations. + * + * This is the last test and corresponds to test case number 1050624. base=null, container=null + * + * Examples to run this test: + * 1. Run a specific test case number: + * ${JTREG} -DTestCaseNum=10782 -samevm -jdk:${JAVA_TEST} -reportDir ${REPORT} -workDir ${WORK} TargetAnnoCombo.java + * 2. Run specific number of tests: + * ${JTREG} -DNumberOfTests=4 -samevm -jdk:${JAVA_TEST} -reportDir ${REPORT} -workDir ${WORK} TargetAnnoCombo.java + * 3. Run specific number of tests with a seed: + * ${JTREG} -DNumberOfTests=4 -DTestSeed=-972894659 -samevm -jdk:${JAVA_TEST} -reportDir ${REPORT} -workDir ${WORK} TargetAnnoCombo.java + * 4. Run tests in default mode (number of tests = 1000): + * ${JTREG} -DTestMode=DEFAULT -samevm -jdk:${JAVA_TEST} -reportDir ${REPORT} -workDir ${WORK} TargetAnnoCombo.java + * 5. Run all tests (FULL mode): + * ${JTREG} -DTestMode=FULL -samevm -jdk:${JAVA_TEST} -reportDir ${REPORT} -workDir ${WORK} TargetAnnoCombo.java + * + */ + +public class TargetAnnoCombo { + int errors = 0; + static final String TESTPKG = "testpkg"; + /* + * Set it to true to get more debug information including base and + * container target sets for a given test case number + */ + static final boolean DEBUG = false; + + // JDK 5/6/7/8 Targets + static final String[] targetVals = {"ElementType.ANNOTATION_TYPE", + "ElementType.CONSTRUCTOR", "ElementType.FIELD", + "ElementType.LOCAL_VARIABLE", "ElementType.METHOD", + "ElementType.TYPE", "ElementType.PARAMETER", + "ElementType.PACKAGE", "ElementType.TYPE_USE", + "ElementType.TYPE_PARAMETER"}; + + // TYPE_USE and TYPE_PARAMETER (added in JDK8) are not part of default Target set + static final int DEFAULT_TARGET_CNT = 8; + + public static void main(String args[]) throws Exception { + + /* maxTestNum = (base and container combinations of targetVals elems [0 - 1048575 combos]) + * + (combinations where base or container has no Target [1024 combos]) + * + (no -1 even though 1st test is number 0 as last test is where both + * base and container have no target) + */ + + int maxTestNum = (int)Math.pow(2, 2*targetVals.length) + 2*(int)Math.pow(2, targetVals.length); + TestCaseGenerator tcg = new TestCaseGenerator(maxTestNum); + TargetAnnoCombo tac = new TargetAnnoCombo(); + + int testCtr = 0; + int testCase = -1; + while ( (testCase=tcg.getNextTestCase()) != -1 ) { + tac.executeTestCase(testCase, maxTestNum); + testCtr++; + } + + System.out.println("Total tests run: " + testCtr); + if (tac.errors > 0) + throw new Exception(tac.errors + " errors found"); + } + + /* + * For given testCase, determine the base and container annotation Target sets, + * get if testCase should compile, get test source file(s), get compilation result and verify. + * + */ + private void executeTestCase(int testCase, int maxTestNum) { + + // Determine base and container annotation Target sets for the testCase + Set baseAnnoTarget = null; + Set conAnnoTarget = null; + + //Number of base and container combinations [0 - 1048575 combos] + int baseContCombos = (int)Math.pow(2, 2*targetVals.length); + //Number of either base or container combinations when one of them has no @Target [1024 combos] + int targetValsCombos = (int)Math.pow(2, targetVals.length); + + if (testCase >= baseContCombos) { + //Base annotation do not have @Target + if (testCase < baseContCombos + targetValsCombos) { + baseAnnoTarget = null; + conAnnoTarget = getSetFromBitVec(Integer.toBinaryString(testCase - baseContCombos)); + } else if (testCase < baseContCombos + 2*targetValsCombos) { + //Container annotation do not have @Target + baseAnnoTarget = getSetFromBitVec(Integer.toBinaryString(testCase - baseContCombos - targetValsCombos)); + conAnnoTarget = null; + } else { + //Both Base and Container annotation do not have @Target + baseAnnoTarget = null; + conAnnoTarget = null; + } + } else { + //TestCase number is represented as 10-bits for base followed by container bits + String bin = Integer.toBinaryString(testCase); + String base="", cont=bin; + if (bin.length() > targetVals.length){ + base = bin.substring(0, bin.length() - targetVals.length); + cont = bin.substring(bin.length() - targetVals.length,bin.length()); + } + baseAnnoTarget = getSetFromBitVec(base); + conAnnoTarget = getSetFromBitVec(cont); + } + + debugPrint("Test case number = " + testCase + " => binary = " + Integer.toBinaryString(testCase)); + debugPrint(" => baseAnnoTarget = " + baseAnnoTarget); + debugPrint(" => containerAnnoTarget = " + conAnnoTarget); + + // Determine if a testCase should compile or not + String className = "TC" + testCase; + boolean shouldCompile = isValidSubSet(baseAnnoTarget, conAnnoTarget); + + // Get test source file(s) + Iterable files = getFileList(className, baseAnnoTarget, + conAnnoTarget, shouldCompile); + + // Get result of compiling test src file(s) + boolean result = getCompileResult(className, shouldCompile, files); + + // List test src code if test fails + if(!result) { + System.out.println("FAIL: Test " + testCase); + try { + for (JavaFileObject f: files) { + System.out.println("File: " + f.getName() + "\n" + f.getCharContent(true)); + } + } catch (IOException ioe) { + System.out.println("Exception: " + ioe); + } + } else { + debugPrint("PASS: Test " + testCase); + } + } + + // Get a Set based on bits that are set to 1 + public Set getSetFromBitVec(String bitVec) { + Set ret = new HashSet<>(); + char[] bit = bitVec.toCharArray(); + for (int i=bit.length-1, j=0; i>=0; i--, j++){ + if (bit[i] == '1') { + ret.add(targetVals[j]); + } + } + return ret; + } + + // Compile the test source file(s) and return test result + private boolean getCompileResult(String className, boolean shouldCompile, + Iterable files) { + + DiagnosticCollector diagnostics = + new DiagnosticCollector(); + Helper.compileCode(diagnostics, files); + + // Test case pass or fail + boolean ok = false; + + String errMesg = ""; + int numDiags = diagnostics.getDiagnostics().size(); + + if (numDiags == 0) { + if (shouldCompile) { + debugPrint("Test passed, compiled as expected."); + ok = true; + } else { + errMesg = "Test failed, compiled unexpectedly."; + ok = false; + } + } else { + if (shouldCompile) { + // did not compile + errMesg = "Test failed, did not compile."; + ok = false; + } else { + // Error in compilation as expected + String expectedErrKey = "compiler.err.invalid.repeatable." + + "annotation.incompatible.target"; + for (Diagnostic d : diagnostics.getDiagnostics()) { + if((d.getKind() == Diagnostic.Kind.ERROR) && + d.getCode().contains(expectedErrKey)) { + // Error message as expected + debugPrint("Error message as expected."); + ok = true; + break; + } else { + // error message is incorrect + ok = false; + } + } + if (!ok) { + errMesg = "Incorrect error received when compiling " + + className + ", expected: " + expectedErrKey; + } + } + } + + if(!ok) { + error(errMesg); + for (Diagnostic d : diagnostics.getDiagnostics()) + System.out.println(" Diags: " + d); + } + return ok; + } + + private void debugPrint(String string) { + if(DEBUG) + System.out.println(string); + } + + // Create src code and corresponding JavaFileObjects + private Iterable getFileList(String className, + Set baseAnnoTarget, Set conAnnoTarget, + boolean shouldCompile) { + + String srcContent = ""; + String pkgInfoContent = ""; + String template = Helper.template; + String baseTarget = "", conTarget = ""; + + String target = Helper.ContentVars.TARGET.getVal(); + if(baseAnnoTarget != null) { + baseTarget = target.replace("#VAL", baseAnnoTarget.toString()) + .replace("[", "{").replace("]", "}"); + } + if(conAnnoTarget != null) { + conTarget = target.replace("#VAL", conAnnoTarget.toString()) + .replace("[", "{").replace("]", "}"); + } + + String annoData = Helper.ContentVars.IMPORTSTMTS.getVal() + + conTarget + + Helper.ContentVars.CONTAINER.getVal() + + baseTarget + + Helper.ContentVars.REPEATABLE.getVal() + + Helper.ContentVars.BASE.getVal(); + + JavaFileObject pkgInfoFile = null; + + /* + * If shouldCompile = true and no @Target is specified for container annotation, + * then all 8 ElementType enum constants are applicable as targets for + * container annotation. + */ + if(shouldCompile && conAnnoTarget == null) { + //conAnnoTarget = new HashSet(Arrays.asList(targetVals)); + conAnnoTarget = getDefaultTargetSet(); + } + + if(shouldCompile) { + boolean isPkgCasePresent = new ArrayList(conAnnoTarget).contains("ElementType.PACKAGE"); + String repeatableAnno = Helper.ContentVars.BASEANNO.getVal() + " " + Helper.ContentVars.BASEANNO.getVal(); + for(String s: conAnnoTarget) { + s = s.replace("ElementType.",""); + String replaceStr = "/*"+s+"*/"; + if(s.equalsIgnoreCase("PACKAGE")) { + //Create packageInfo file + String pkgInfoName = TESTPKG + "." + "package-info"; + pkgInfoContent = repeatableAnno + "\npackage " + TESTPKG + ";" + annoData; + pkgInfoFile = Helper.getFile(pkgInfoName, pkgInfoContent); + } else { + template = template.replace(replaceStr, repeatableAnno); + //srcContent = template.replace("#ClassName",className); + if(!isPkgCasePresent) { + srcContent = template.replace("/*ANNODATA*/", annoData).replace("#ClassName",className); + } else { + replaceStr = "/*PACKAGE*/"; + srcContent = template.replace(replaceStr, "package " + TESTPKG + ";") + .replace("#ClassName", className); + } + } + } + } else { + // For invalid cases, compilation should fail at declaration site + template = "class #ClassName {}"; + srcContent = annoData + template.replace("#ClassName",className); + } + JavaFileObject srcFile = Helper.getFile(className, srcContent); + Iterable files = null; + if(pkgInfoFile != null) + files = Arrays.asList(pkgInfoFile,srcFile); + else + files = Arrays.asList(srcFile); + return files; + } + + private Set getDefaultTargetSet() { + Set defaultSet = new HashSet<>(); + int ctr = 0; + for(String s : targetVals) { + if(ctr++ < DEFAULT_TARGET_CNT) { + defaultSet.add(s); + } + } + return defaultSet; + } + + private boolean isValidSubSet(Set baseAnnoTarget, Set conAnnoTarget) { + /* + * RULE 1: conAnnoTarget should be a subset of baseAnnoTarget + * RULE 2: For empty @Target ({}) - annotation cannot be applied anywhere + * - Empty sets for both is valid + * - Empty baseTarget set is invalid with non-empty conTarget set + * - Non-empty baseTarget set is valid with empty conTarget set + * RULE 3: For no @Target specified - annotation can be applied to any JDK 7 targets + * - No @Target for both is valid + * - No @Target for baseTarget set with @Target conTarget set is valid + * - @Target for baseTarget set with no @Target for conTarget is invalid + */ + + + /* If baseAnno has no @Target, Foo can be either applied to @Target specified for container annotation + * else will be applicable for all default targets if no @Target is present for container annotation. + * In both cases, the set will be a valid set with no @Target for base annotation + */ + if(baseAnnoTarget == null) { + if(conAnnoTarget == null) return true; + return !(conAnnoTarget.contains("ElementType.TYPE_USE") || conAnnoTarget.contains("ElementType.TYPE_PARAMETER")); + } + + Set tempBaseSet = new HashSet<>(baseAnnoTarget); + // If BaseAnno has TYPE, then ANNOTATION_TYPE is allowed by default + if(baseAnnoTarget.contains("ElementType.TYPE")) { + tempBaseSet.add("ElementType.ANNOTATION_TYPE"); + } + + /* + * If containerAnno has no @Target, only valid case if baseAnnoTarget has all targets defined + * else invalid set + */ + if(conAnnoTarget == null) { + return (tempBaseSet.containsAll(getDefaultTargetSet())); + } + + // At this point, neither conAnnoTarget or baseAnnoTarget are null + if(conAnnoTarget.size() == 0) return true; + + // At this point, conAnnoTarget is non-empty + if (baseAnnoTarget.size() == 0) return false; + + // At this point, neither conAnnoTarget or baseAnnoTarget are empty + return tempBaseSet.containsAll(conAnnoTarget); + } + + void error(String msg) { + System.out.println("ERROR: " + msg); + errors++; + } + + // Lists the start and end range for the given set of target vals + void showGroups() { + //Group 1: All target set combinations ( 0 to 1048575 ) including empty @Target sets => @Target({}) + int grpEnd1 = (int)Math.pow(2, 2*targetVals.length) - 1; + System.out.println("[Group 1]: 0 - " + grpEnd1); + + //Group 2: @Target not defined for base annotation ( 1048576 - 1049599 ). + System.out.print("[Group 2]: " + (grpEnd1 + 1) + " - "); + int grpEnd2 = grpEnd1 + 1 + (int)Math.pow(2, targetVals.length) - 1; + System.out.println(grpEnd2); + + //Group 3: @Target not defined for container annotation ( 1049600 - 1050623 ). + System.out.print("[Group 3]: " + (grpEnd2 + 1) + " - "); + int grpEnd3 = grpEnd2 + 1 + (int)Math.pow(2, targetVals.length) - 1; + System.out.println(grpEnd3); + + //Group 4: @Target not defined for both base and container annotations ( 1050624 ). + System.out.println("[Group 4]: " + (grpEnd3 + 1)); + } +} diff -r b386b8c45387 -r 5125b9854d07 test/tools/javac/annotations/repeatingAnnotations/combo/TestCaseGenerator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/tools/javac/annotations/repeatingAnnotations/combo/TestCaseGenerator.java Thu Feb 07 20:47:06 2013 -0800 @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Random; + +/* System properties: + * NumberOfTests, TestMode, and TestCaseNum are mutually exclusive + * TestSeed will be used only with NumberOfTests or TestMode, otherwise it will be ignored + * -DNumberOfTests=[0 to 2^20+2^11+1] + * -DTestMode=[FULL|DEFAULT] + * -DTestSeed=[seedNumber] + * -DTestCaseNum=[0 to 2^20+2^11+1] + */ +public class TestCaseGenerator { + // Total number of tests to be run + int numberOfTests = -1; + //Single test case + int testCaseNum = -1; + //Seed used to generate test cases + int testSeed; + + int maxTestNum; + Random randNum; + + // used in getNextTestCase + int curTestNum; + int testCompletedCount; + HashSet uniqueTestSet; + + static final int DEFAULT_TEST_COUNT = 250; + + /* + * Get parameter values from command line to set numberOfTests, testCaseNum, + * and testSeed + */ + public TestCaseGenerator(int maxTestNum) { + this.maxTestNum = maxTestNum; + + // Set values for variables based on input from command line + + // TestMode system property + String testModeVal = System.getProperty("TestMode"); + if(testModeVal != null && !testModeVal.isEmpty()) { + switch (testModeVal.toUpperCase()) { + case "FULL": + numberOfTests = maxTestNum; + break; + case "DEFAULT": + numberOfTests = DEFAULT_TEST_COUNT; + break; + default: + System.out.println("Invalid property value " + testModeVal + + " for numberOfTests. Possible range: 0 to " + + maxTestNum + ". Ignoring property"); + numberOfTests = -1; + } + } + + // NumberOfTests system property + String numTestsStr = System.getProperty("NumberOfTests"); + if(numTestsStr != null && !numTestsStr.isEmpty()) { + int numTests = -1; + try { + numTests = Integer.parseInt(numTestsStr); + if (numTests < 0 || numTests > maxTestNum) { + throw new NumberFormatException(); + } + } catch(NumberFormatException nfe) { + System.out.println("Invalid NumberOfTests property value " + + numTestsStr + ". Possible range: 0 to " + maxTestNum + + "Reset to default: " + DEFAULT_TEST_COUNT); + numTests = DEFAULT_TEST_COUNT; + } + + if (numberOfTests != -1 && numTests != -1) { + System.out.println("TestMode and NumberOfTests cannot be set together. Ignoring TestMode."); + } + numberOfTests = numTests; + } + + // TestSeed system property + String seedVal = System.getProperty("TestSeed"); + if(seedVal != null && !seedVal.isEmpty()) { + try { + testSeed = Integer.parseInt(seedVal); + } catch(NumberFormatException nfe) { + Random srand = new Random(); + testSeed = srand.nextInt(); + } + } else { + Random srand = new Random(); + testSeed = srand.nextInt(); + } + + // TestCaseNum system property + String testNumStr = System.getProperty("TestCaseNum"); + if(testNumStr != null && !testNumStr.isEmpty()) { + try { + testCaseNum = Integer.parseInt(testNumStr); + if (testCaseNum < 0 || testCaseNum > maxTestNum) { + throw new NumberFormatException(); + } + } catch(NumberFormatException nfe) { + System.out.println("Invalid TestCaseNumber property value " + + testNumStr + ". Possible value in range: 0 to " + + maxTestNum + ". Defaulting to last test case."); + testCaseNum = maxTestNum; + } + + if ( numberOfTests != -1) { + System.out.println("TestMode or NumberOfTests cannot be set along with TestCaseNum. Ignoring TestCaseNumber."); + testCaseNum = -1; + } + } + + if (numberOfTests == -1 && testCaseNum == -1) { + numberOfTests = DEFAULT_TEST_COUNT; + System.out.println("Setting TestMode to default, will run " + numberOfTests + "tests."); + } + + /* + * By this point in code, we will have: + * - testSeed: as per TestSeed or a Random one + * - numberOfTests to run or -1 to denote not set + * - testCaseNum to run or -1 to denote not set + */ + + /* + * If numberOfTests = maxTestNum, all tests are to be run, + * so no randNum will be required + */ + if (numberOfTests != -1 && numberOfTests < maxTestNum) { + System.out.println("Seed = " + testSeed); + randNum = new Random(testSeed); + uniqueTestSet = new HashSet<>(); + } + + testCompletedCount = 0; + // to be used to keep sequential count when running all tests + curTestNum = 0; + } + + /* + * returns next test case number to run + * returns -1 when there are no more tests to run + */ + public int getNextTestCase() { + if (testCaseNum != -1) { + int nextTC = testCaseNum; + testCaseNum = -1; + return nextTC; + } + if (++testCompletedCount <= numberOfTests) { + if (numberOfTests == maxTestNum) { + //all the tests need to be run, so just return + //next test case sequentially + return curTestNum++; + } else { + int nextTC = -1; + // Ensuring unique test are run + while(!uniqueTestSet.add(nextTC = randNum.nextInt(maxTestNum))) { + } + return nextTC; + } + } + return -1; + } +}