--- a/langtools/test/tools/javac/annotations/repeatingAnnotations/combo/Helper.java Wed Feb 06 23:10:35 2013 +0000
+++ b/langtools/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 </*TYPE_PARAMETER*/ T extends Object> {\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 @@
}
}
}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/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<String> baseAnnoTarget = null;
+ Set<String> 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<? extends JavaFileObject> 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<String> based on bits that are set to 1
+ public Set<String> getSetFromBitVec(String bitVec) {
+ Set<String> 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<? extends JavaFileObject> files) {
+
+ DiagnosticCollector<JavaFileObject> diagnostics =
+ new DiagnosticCollector<JavaFileObject>();
+ 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<? extends JavaFileObject> getFileList(String className,
+ Set<String> baseAnnoTarget, Set<String> 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<String>(Arrays.asList(targetVals));
+ conAnnoTarget = getDefaultTargetSet();
+ }
+
+ if(shouldCompile) {
+ boolean isPkgCasePresent = new ArrayList<String>(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<? extends JavaFileObject> files = null;
+ if(pkgInfoFile != null)
+ files = Arrays.asList(pkgInfoFile,srcFile);
+ else
+ files = Arrays.asList(srcFile);
+ return files;
+ }
+
+ private Set<String> getDefaultTargetSet() {
+ Set<String> defaultSet = new HashSet<>();
+ int ctr = 0;
+ for(String s : targetVals) {
+ if(ctr++ < DEFAULT_TARGET_CNT) {
+ defaultSet.add(s);
+ }
+ }
+ return defaultSet;
+ }
+
+ private boolean isValidSubSet(Set<String> baseAnnoTarget, Set<String> 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<String> 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));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/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<Integer> 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;
+ }
+}