aboutsummaryrefslogtreecommitdiffgithub
diff options
context:
space:
mode:
authorAustin Adams <git@austinjadams.com>2018-09-07 00:24:24 -0400
committerAustin Adams <git@austinjadams.com>2018-09-07 00:24:24 -0400
commit48c98aaccbad6b0e22d94519903435332a33a78f (patch)
tree3c0759cc5c25eecc63fa1577585824aa63e46b26
parente85b204e9efe4022e1ee75b012861eebfe58990f (diff)
downloadcircuitsim-grader-template-48c98aaccbad6b0e22d94519903435332a33a78f.tar.gz
circuitsim-grader-template-48c98aaccbad6b0e22d94519903435332a33a78f.tar.xz
Add Restrictors
This generalizes the idea of a component white/blacklist to other forms of validation. Cleans up the CircuitSimExtension code as well.
-rw-r--r--README.md34
-rw-r--r--src/main/java/edu/gatech/cs2110/circuitsim/api/Restrictor.java104
-rw-r--r--src/main/java/edu/gatech/cs2110/circuitsim/api/SubcircuitTest.java49
-rw-r--r--src/main/java/edu/gatech/cs2110/circuitsim/extension/CircuitSimExtension.java68
-rw-r--r--src/main/java/edu/gatech/cs2110/circuitsim/tests/ToyALUTests.java11
5 files changed, 196 insertions, 70 deletions
diff --git a/README.md b/README.md
index fc30fef..638e1c3 100644
--- a/README.md
+++ b/README.md
@@ -160,22 +160,35 @@ the same name as the test.
You can see the finished product in
`src/main/java/edu/gatech/cs2110/circuitsim/tests/ToyALU.java`.
-##### Banning components
+#### Restrictors (Banning Components)
-Suppose we wanted students to build the XOR themselves, without using a XOR
-gate component. Then we can change the `@SubcircuitTest` annotation as follows:
+Suppose in the previous example, we wanted students to build the XOR
+themselves, without using a XOR gate component. Then we can change the
+test as follows:
```java
+@DisplayName("Toy ALU")
+@ExtendWith(CircuitSimExtension.class)
@SubcircuitTest(file="toy-alu.sim", subcircuit="ALU",
- blacklistedComponents={"XOR"})
+ restrictors={ToyALUTests.BannedGates.class})
+public class ToyALUTests {
+ public static class BannedGates extends Restrictor {
+ @Override
+ public void validate(Subcircuit subcircuit) throws AssertionError {
+ blacklistComponents(subcircuit, "XOR");
+ }
+ }
+ // ...
```
-You can write either components or component categories, like "Wiring."
-There is also a complementary but mutually exclusive flag,
-`whitelistedComponents`, which sets the only components allowed. To
-avoid code duplication, it allows Input Pins, Output Pins, Constants,
-Tunnels, Probes, and Text automatically. But I would go ahead and write
-`whitelistedComponents={"Wiring"}` so they can use probes, for example.
+That is, we can provide `restrictors` to `@SubcircuitTest`. The fact
+that it's a class is useful for when you have a bunch of subcircuits
+with the same restrictions. In such a case, you can reduce code
+duplication by simply referencing the same `Restrictor` class in all of
+the tests.
+
+Consult [the Restrictor documentation][8] for more information on
+restrictions. There is also support for whitelists.
#### Testing sequential logic
@@ -403,3 +416,4 @@ in `grading-files/` in the zucchini assignment repository.
[5]: https://ausbin.github.io/circuitsim-grader-template/edu/gatech/cs2110/circuitsim/api/MockRegister.html#getD()
[6]: https://github.com/zucchini/zucchini
[7]: https://github.com/zucchini/zucchini/blob/master/zucchini/graders/circuitsim_grader.py
+[8]: https://ausbin.github.io/circuitsim-grader-template/edu/gatech/cs2110/circuitsim/api/Restrictor.html
diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/api/Restrictor.java b/src/main/java/edu/gatech/cs2110/circuitsim/api/Restrictor.java
new file mode 100644
index 0000000..b976089
--- /dev/null
+++ b/src/main/java/edu/gatech/cs2110/circuitsim/api/Restrictor.java
@@ -0,0 +1,104 @@
+package edu.gatech.cs2110.circuitsim.api;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Validates a subcircuit by checking for issues such as banned
+ * components. Example:
+ * <pre>
+ * {@literal @}DisplayName("Toy ALU")
+ * {@literal @}ExtendWith(CircuitSimExtension.class)
+ * {@literal @}SubcircuitTest(file="toy-alu.sim", subcircuit="ALU",
+ * restrictors={ToyALUTests.BannedGates.class})
+ * public class ToyALUTests {
+ * public static class BannedGates extends Restrictor {
+ * {@literal @}Override
+ * public void validate(Subcircuit subcircuit) throws AssertionError {
+ * blacklistComponents(subcircuit, "XOR");
+ * }
+ * }
+ *
+ * // ...
+ * }
+ * </pre>
+ *
+ * @see SubcircuitTest#restrictors
+ */
+public abstract class Restrictor {
+ /**
+ * Validates this subcircuit, throwing an {@code AssertionError} if
+ * any issues are found. (The subcircuit name will automatically be
+ * included in the exception, so don't worry about that.) Subclasses
+ * should override this method with one that calls the protected
+ * helper methods in this class as needed.
+ *
+ * @param subcircuit the subcircuit to validate
+ * @throws AssertionError on validation error. Do not worry about
+ * including the subcircuit name
+ */
+ public abstract void validate(Subcircuit subcircuit) throws AssertionError;
+
+ /**
+ * Fail the whole test if the subcircuit contains any components other than
+ * these components or categories.
+ * <p>
+ * Automatically includes Input Pins, Output Pins, Constants,
+ * Tunnels, Probes, and Text. But other components will be allowed only if
+ * you specify them here, including other Wiring components. Please
+ * consider starting off with {@code "Wiring", "Text"}, or you will
+ * risk frustrating students.
+ *
+ * @param subcircuit Subcircuit to validate
+ * @param componentNames component names or category names to whitelist
+ */
+ protected void whitelistComponents(Subcircuit subcircuit, String... componentNames)
+ throws AssertionError {
+ checkForBannedComponents(subcircuit, Arrays.asList(componentNames), true);
+ }
+
+ /**
+ * Fail the whole test if the subcircuit contains any of these components
+ * or component categories.
+ *
+ * @param subcircuit Subcircuit to validate
+ * @param componentNames component names or category names to blacklist
+ */
+ protected void blacklistComponents(Subcircuit subcircuit, String... componentNames)
+ throws AssertionError {
+ checkForBannedComponents(subcircuit, Arrays.asList(componentNames), false);
+ }
+
+ private void checkForBannedComponents(Subcircuit subcircuit,
+ Collection<String> componentNames,
+ boolean isWhitelist) {
+ // These should always be included, but TAs might not think to
+ // include them.
+ if (isWhitelist) {
+ List<String> componentNamesFixed = new LinkedList<>(componentNames);
+ componentNamesFixed.add("Input Pin");
+ componentNamesFixed.add("Output Pin");
+ componentNamesFixed.add("Constant");
+ componentNamesFixed.add("Tunnel");
+ componentNamesFixed.add("Text");
+ componentNamesFixed.add("Probe");
+ componentNames = componentNamesFixed;
+ }
+
+ Set<String> violatingComponentNames =
+ subcircuit.lookupComponents(componentNames, isWhitelist);
+
+ if (!violatingComponentNames.isEmpty()) {
+ throw new AssertionError(String.format(
+ "contains banned components: %s. It " +
+ "could contain these banned components indirectly; double-check " +
+ "subcircuits placed in it as well.",
+ violatingComponentNames.stream().map(name -> String.format("`%s'", name))
+ .collect(Collectors.joining(", "))));
+ }
+ }
+}
diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/api/SubcircuitTest.java b/src/main/java/edu/gatech/cs2110/circuitsim/api/SubcircuitTest.java
index 7497b4e..267c83e 100644
--- a/src/main/java/edu/gatech/cs2110/circuitsim/api/SubcircuitTest.java
+++ b/src/main/java/edu/gatech/cs2110/circuitsim/api/SubcircuitTest.java
@@ -29,30 +29,41 @@ public @interface SubcircuitTest {
*/
String subcircuit();
- /**
- * Fail the whole test if the subcircuit contains any of these components
- * or component categories.
- * Mutually exclusive with {@link #whitelistedComponents()}.
- *
- * @return list of banned component names or component category names
- */
- String[] blacklistedComponents() default {};
/**
- * Fail the whole test if the subcircuit contains any components other than
- * these components or categories.
- * Mutually exclusive with {@link #blacklistedComponents()}.
+ * Validate the subcircuit with these {@link Restrictor}s before
+ * running any tests. Useful for checking for banned gates.
* <p>
- * Automatically includes Input Pins, Output Pins, Constants,
- * Tunnels, Probes, and Text. But other components will be allowed only if
- * you specify them here, including other Wiring components. Please
- * consider starting off with {@code
- * whitelistedComponents={"Wiring", "Text"}}, or you will risk
- * frustrating students.
+ * The idiom is to subclass {@link Restrictor} inside your test
+ * class and then call {@link Restrictor} methods as needed inside
+ * its {@code validate()}, such as
+ * {@link Restrictor#whitelistComponents
+ * Restrictor.whitelistComponents()} or
+ * {@link Restrictor#blacklistComponents Restrictor.blacklistComponents()}
+ * as follows:
+ * <pre>
+ * {@literal @}DisplayName("Toy ALU")
+ * {@literal @}ExtendWith(CircuitSimExtension.class)
+ * {@literal @}SubcircuitTest(file="toy-alu.sim", subcircuit="ALU",
+ * restrictors={ToyALUTests.BannedGates.class})
+ * public class ToyALUTests {
+ * public static class BannedGates extends Restrictor {
+ * {@literal @}Override
+ * public void validate(Subcircuit subcircuit) throws AssertionError {
+ * blacklistComponents(subcircuit, "XOR");
+ * }
+ * }
+ *
+ * // ...
+ * }
+ * </pre>
+ *
+ * The default is an empty array, so to perform no validation.
*
- * @return list of required component names or component category names
+ * @return restrictor classes to use to validate the subcircuit
+ * @see Restrictor
*/
- String[] whitelistedComponents() default {};
+ Class<? extends Restrictor>[] restrictors() default {};
/**
* Reset simulation between tests.
diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/extension/CircuitSimExtension.java b/src/main/java/edu/gatech/cs2110/circuitsim/extension/CircuitSimExtension.java
index f507769..875c04e 100644
--- a/src/main/java/edu/gatech/cs2110/circuitsim/extension/CircuitSimExtension.java
+++ b/src/main/java/edu/gatech/cs2110/circuitsim/extension/CircuitSimExtension.java
@@ -1,11 +1,11 @@
package edu.gatech.cs2110.circuitsim.extension;
import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
-import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.extension.BeforeAllCallback;
@@ -17,6 +17,7 @@ import edu.gatech.cs2110.circuitsim.api.BasePin;
import edu.gatech.cs2110.circuitsim.api.InputPin;
import edu.gatech.cs2110.circuitsim.api.MockRegister;
import edu.gatech.cs2110.circuitsim.api.OutputPin;
+import edu.gatech.cs2110.circuitsim.api.Restrictor;
import edu.gatech.cs2110.circuitsim.api.Subcircuit;
import edu.gatech.cs2110.circuitsim.api.SubcircuitPin;
import edu.gatech.cs2110.circuitsim.api.SubcircuitRegister;
@@ -47,7 +48,11 @@ public class CircuitSimExtension implements Extension, BeforeAllCallback, Before
resetSimulationBetween = subcircuitAnnotation.resetSimulationBetween();
subcircuit = Subcircuit.fromPath(subcircuitAnnotation.file(),
subcircuitAnnotation.subcircuit());
- checkForBannedComponents(subcircuitAnnotation);
+
+ for (Class<? extends Restrictor> restrictor : subcircuitAnnotation.restrictors()) {
+ runRestrictor(subcircuit, restrictor);
+ }
+
fieldInjections = new LinkedList<>();
fieldInjections.addAll(generatePinFieldInjections(testClass));
fieldInjections.addAll(generateRegFieldInjections(testClass));
@@ -66,46 +71,29 @@ public class CircuitSimExtension implements Extension, BeforeAllCallback, Before
}
}
- private void checkForBannedComponents(SubcircuitTest subcircuitAnnotation) {
- boolean hasComponentBlacklist =
- subcircuitAnnotation.blacklistedComponents().length > 0;
- boolean hasComponentWhitelist =
- subcircuitAnnotation.whitelistedComponents().length > 0;
-
- if (hasComponentBlacklist && hasComponentWhitelist) {
- throw new IllegalArgumentException(
- "blacklistedComponents and whitelistedComponents are mutually exclusive");
+ private void runRestrictor(
+ Subcircuit subcircuit, Class<? extends Restrictor> restrictorClass)
+ throws AssertionError {
+ Restrictor restrictor;
+ // Lord Gosling, thank you for this boilerplate, amen
+ try {
+ restrictor = restrictorClass.getConstructor().newInstance();
+ } catch (NoSuchMethodException err) {
+ throw new IllegalStateException(String.format(
+ "restrictor class %s needs a no-args constructor",
+ restrictorClass), err);
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException err) {
+ throw new IllegalStateException(String.format(
+ "could not instantiate restrictor class %s",
+ restrictorClass), err);
}
- if (hasComponentBlacklist || hasComponentWhitelist) {
- List<String> restrictedComponents = new LinkedList<>(Arrays.asList(
- hasComponentBlacklist? subcircuitAnnotation.blacklistedComponents()
- : subcircuitAnnotation.whitelistedComponents()));
-
- // These should always be included, but TAs might not think to
- // include them.
- if (hasComponentWhitelist) {
- restrictedComponents.add("Input Pin");
- restrictedComponents.add("Output Pin");
- restrictedComponents.add("Constant");
- restrictedComponents.add("Tunnel");
- restrictedComponents.add("Text");
- restrictedComponents.add("Probe");
- }
-
- Set<String> violatingComponentNames =
- subcircuit.lookupComponents(restrictedComponents,
- hasComponentWhitelist);
-
- if (!violatingComponentNames.isEmpty()) {
- throw new IllegalArgumentException(String.format(
- "The subcircuit `%s' contains banned components: %s. It " +
- "could contain these banned components indirectly; double-check " +
- "subcircuits placed in it as well.",
- subcircuit.getName(),
- violatingComponentNames.stream().map(name -> String.format("`%s'", name))
- .collect(Collectors.joining(", "))));
- }
+ try {
+ restrictor.validate(subcircuit);
+ } catch (AssertionError err) {
+ throw new AssertionError(String.format(
+ "validation error with subcircuit `%s': %s",
+ subcircuit.getName(), err.getMessage()), err);
}
}
diff --git a/src/main/java/edu/gatech/cs2110/circuitsim/tests/ToyALUTests.java b/src/main/java/edu/gatech/cs2110/circuitsim/tests/ToyALUTests.java
index ccb04f5..dd9a35f 100644
--- a/src/main/java/edu/gatech/cs2110/circuitsim/tests/ToyALUTests.java
+++ b/src/main/java/edu/gatech/cs2110/circuitsim/tests/ToyALUTests.java
@@ -18,6 +18,8 @@ import org.junit.jupiter.params.provider.MethodSource;
import edu.gatech.cs2110.circuitsim.api.InputPin;
import edu.gatech.cs2110.circuitsim.api.OutputPin;
+import edu.gatech.cs2110.circuitsim.api.Restrictor;
+import edu.gatech.cs2110.circuitsim.api.Subcircuit;
import edu.gatech.cs2110.circuitsim.api.SubcircuitPin;
import edu.gatech.cs2110.circuitsim.api.SubcircuitTest;
import edu.gatech.cs2110.circuitsim.extension.CircuitSimExtension;
@@ -26,8 +28,15 @@ import edu.gatech.cs2110.circuitsim.extension.BasesConverter;
@DisplayName("Toy ALU")
@ExtendWith(CircuitSimExtension.class)
@SubcircuitTest(file="toy-alu.sim", subcircuit="ALU",
- blacklistedComponents={"XOR"})
+ restrictors={ToyALUTests.BannedGates.class})
public class ToyALUTests {
+ public static class BannedGates extends Restrictor {
+ @Override
+ public void validate(Subcircuit subcircuit) throws AssertionError {
+ blacklistComponents(subcircuit, "XOR");
+ }
+ }
+
@SubcircuitPin(bits=4)
private InputPin a;