-
Notifications
You must be signed in to change notification settings - Fork 0
Opportunistic Compilation
In the previous chapter we set up two checkpoints' test suites. Your grading machine will likely have a copy of all test suites available. Students, however, will probably not be given the Checkpoint 1 test suite or documentation until after the time appointed for Checkpoint 0. That is, they won't be expected to (or be able to) add classes or members required by later checkpoints, which would cause compilation of those later test suites to fail during official grading.
Let's simulate this locally. Try commenting out the entire ComplexPolynomial class we created for Checkpoint 1 in the main source set. It's not required at all by the Checkpoint 0 test suite, so try running Checkpoint0Test. Unfortunately the build fails with compilation errors in Checkpoint1Test.
This problem is why eMPire offers opportunistic compilation. We can set it to make the compilation of the different test suite Java files independent of each other. But first we need to tell eMPire more about the structure of our project. The excludedSrcPath property inside the eMPire block in build.gradle.kts is required if doing anything with eMPire at all. It can be thought of as the package containing all our app's sources except with forward slashes in place of dots:
excludedSrcPath = "com/example/mp"
The opportunisticCompile block inside eMPire enables opportunistic compilation:
opportunisticCompile {
classes("Checkpoint0Test", "Checkpoint1Test")
dependentTasks("testDebugUnitTest", "testReleaseUnitTest", "compileDebugUnitTestSources", "compileReleaseUnitTestSources")
javaRoot = project.file("src/test/java")
javacTasks("compileDebugUnitTestJavaWithJavac", "compileReleaseUnitTestJavaWithJavac")
}classes adds files to be opportunistically compiled. You can add more if you have more checkpoints. The other options there will likely be the same in all projects. Details for the curious:
-
dependentTasksadds Gradle tasks that need compilation to have been attempted for the opportunistically compiled classes before they can run. Thetest[Variant]UnitTesttasks are the ones that actually run the tests, so it is important that opportunistic compilation happens before them. -
javaRootis the path to the Java source set that contains the opportunistically compiled classes.project.fileis relative to the app module. -
javacTasksadds Gradle tasks responsible for compiling files not subject to opportunistic compilation. The opportunistic-compile tasks will run after—and take their configuration from—whichever one of these actually runs.
Opportunistically compiled classes can depend on normally compiled files in the test sources, but they are not allowed to depend on each other. Normally compiled files are not allowed to depend on opportunistically compiled files. If there are parts of your app introduced in later checkpoints, it is important that no normally compiled test files have compile-time dependencies on them.
For the best view, do Build | Clean Project now. Then try running the Checkpoint 0 test suite again. We get test results! Switch to the Build tab. If you don't see a big list of tasks with green checks, click the eye icon and choose Show Successful Steps. Notice that about halfway through, a :app:configForOpportunisticCompileDebugUnitTestJavaWithJavac task ran. This task was generated by eMPire to obtain the compilation configuration from the standard :app:compileDebugUnitTestJavaWithJavac task and exclude the two opportunistically compiled files from normal compilation. Later, :app:compileDebugUnitTestJavaWithJavac itself ran, though it probably has a gray "no" icon because there were no normally compiled test Java files left. Right after that, :app:tryCompileCheckpoint0Test and :app:tryCompileCheckpoint1Test ran, one independent opportunistic compilation task per opportunistically compiled file. Compiler errors in Checkpoint1Test might appear at the end, but as you can see from the Checkpoint 0 tests running, they did not interfere with Checkpoint0Test's compilation.
On the official grading machine, you might be grading only one checkpoint at a time and need only one test suite class at once. It would therefore be a waste of time to try to compile all opportunistically compiled classes. eMPire allows you to limit opportunistic compilation to the class(es) needed by the current checkpoint.
Until now we haven't actually told eMPire about what functionality belongs to each checkpoint. The test suite classes are named sequentially, but it would have been totally legitimate to name them ArithmeticTest and PolynomialTest instead. To define checkpoints, create a checkpoints block inside the eMPire block. To register a checkpoint, call the register function inside checkpoints, passing a string name and a configuration block. When configuring a checkpoint, call limitOpportunisticCompile and pass the filename(s) for which opportunistic compilation should be attempted. After adding that, here is our eMPire configuration so far:
eMPire {
studentConfig = rootProject.file("grade.yaml")
excludedSrcPath = "com/example/mp"
opportunisticCompile {
classes("Checkpoint0Test", "Checkpoint1Test")
dependentTasks("testDebugUnitTest", "testReleaseUnitTest", "compileDebugUnitTestSources", "compileReleaseUnitTestSources")
javaRoot = project.file("src/test/java")
javacTasks("compileDebugUnitTestJavaWithJavac", "compileReleaseUnitTestJavaWithJavac")
}
checkpoints {
register("0") {
limitOpportunisticCompile("Checkpoint0Test")
}
register("1") {
limitOpportunisticCompile("Checkpoint1Test")
}
}
}The student will need to edit grade.yaml to specify the checkpoint they're working on. Amend grade.yaml with a checkpoint property:
checkpoint: 0
useProvided: falseThe value of the checkpoint property refers to a checkpoint registered inside the checkpoints block.
Android Studio won't prompt you, but you should always do a Gradle sync (File | Sync Project with Gradle Files) after editing the student configuration file. Occasionally, especially if adding features to the eMPire block in build.gradle.kts, Gradle gets very stuck and requires a sync-clean-sync sequence.
Run Checkpoint0Test again. You'll get the same test results as last time. But now when you look in the Build tab at the tasks that ran, there's no :app:tryCompileCheckpoint1Test task—only the normal compilation task then :app:tryCompileCheckpoint0Test—and no compilation errors whatsoever. Try uncommenting ComplexPolynomial and Checkpoint1Test still won't be compiled. Conversely, now that the other student class is uncommented, try setting checkpoint in the student configuration file to 1. Checkpoint1Test will be able to compile and run. Compilation of Checkpoint0Test would not be attempted, so after a clean, running Checkpoint0Test will fail with "class not found."
It is entirely reasonable to use eMPire just for the opportunistic compilation feature. If the checkpoints of your app are independent and you don't want to provide swappable components, you can call it a day, stop here, and go watch some Star Trek reruns. Otherwise, we'll continue in the next chapter with provided components.