|
ChangeDetectionAndTesting
sbt has movedsbt has now completely moved to GitHub. See https://github.com/harrah/xsbt/wiki. IntroductionA general guideline for a build tool is that you should be able to trust its outputs. This is especially relevant to compiling Scala sources because the Scala compiler is slow and so there is a sizable amount of code in sbt to speed up compilation times. One simple way sbt speeds up compilation is to run the compiler in the same virtual machine each time. (Note that this is not quite the same as fsc, which reuses the same compiler instance.) This approach results in a speedup by a factor of 2 after the first compile. A second way to speed up compilation times is to only recompile sources that are out of date. This approach requires some work to do properly. A sign of failing to do it properly is users running clean as part of their normal development cycle. The motivation behind sbt's scripted test framework is to try to find bugs in sbt's partial recompilation so that compilation works as expected and clean is something you don't need to do to get correct outputs. The basic steps to create a scripted test are:
The first part discusses partial recompilation in sbt and the second part describes scripted tests. Partial RecompilationYou have a set of Scala source files that you would like to compile. After the initial compilation, you typically only modify some of those files before recompiling. This section describes how sbt determines which files have been changed and which files need to be recompiled. The steps to partial recompilation are generally:
There are a few indications that a source has changed:
Once a source is detected as out of date, there are three recompilation strategies:
Finally, when a source is out of date (directly or indirectly), its classes should be deleted. Through version 0.3.6, sbt used methods 1, 3, 5, and 6 combined to detect changes and recompilation strategy 2. Version 0.3.7 and higher of sbt substitutes change detection method 4 (comparing hashes) for method 3 (comparing last modified times) because of the following problems with method 3:
The last issue was especially a problem when implementing the scripted tests. An automated test script could setup, compile, update, and compile again within a second. The second compile wouldn't detect any changes because the last modified times were the same. One problem with hashing, though, is that it reads in every source file to calculate its hash. As a rough idea, this might take about 1 second per 10 MB of sources on a local filesystem. This mainly affects how long it takes to run compile on a project without any changes. Of course, change detection is configurable, so you can use the last modified method if desired. ExampleAs an example, consider the following two definitions: A.scala object A {
val x = B.y
}B.scala object B {
val y = 5
}Now, consider what happens if you were to delete B.scala but do not update A.scala. When you recompile, you should get an error because B no longer exists for A to reference. The first problem occurs if you do not recompile A.scala. This would happen if you do not take source dependencies into account and you only recompile directly modified sources (here, A.scala is out of date because it depends on B.scala, but A.scala is not directly modified). A solution for a build system without source dependency tracking would be to recompile all sources. Alternatively, it could omit A.scala from recompilation and consequently require the user to do a full clean and then compile in order to get a proper build. The second problem is that if you do not delete the classes for B, the compiler will still find the classes for B in the output directory. So, there will not be a compiler error even though you have recompiled A.scala. This shows that it is necessary to track the classes generated from a source file. You want to delete the classes for the sources being recompiled but not delete the classes for the sources not being recompiled. TestingThe scripted test framework is used to verify that sbt handles cases such as that described above. The steps to create a test are:
The directory structure for a test that verifies correctness in the case mentioned in the previous section might look like: src/sbt-test/change-detection/remove-test/
project/
build.properties
src/main/scala/
A.scala
B.scala
testThe scripted action runs the test by copying the directory to the temporary directory for your system, loads the project, and runs the test script. For example, on a unix system, the above test might be run from /tmp/sbt_f723ecf/remove-test/, where the f723ecf part is randomly generated. The next section describes scripts and provides an example of the test script. ScriptsSyntaxscript ::= (comment | statement)+ comment ::= '#' comment-text EOL statement ::= expected-result (action | command) action ::= '>' name command ::= '$' name argument* expected-result ::= '' | '-' Example> compile $ delete src/main/scala/B.scala -> compile CommandsAll paths are relative to the project directory. copy-file fromPath toPath Copies the file given by fromPath to toPath.copy fromPath+ toDirectoryPath Copies the files given by fromPaths to the toDirectoryPath directory. The directory structure relative to the project directory is preserved.sync fromDirectory toDirectory Synchronizes fromDirectory and toDirectory so that the contents of toDirectory are identical to that of fromDirectory.delete path+ Deletes the files given by paths.touch path+ Creates or updates the last modified time of the given paths.exists path+ Succeeds if the given paths exist, fails otherwise.absent path+ Succeeds if the given paths do not exist, fails otherwise.exec command args* Executes the given command in a separate process.pause Pauses until enter is pressed. It is useful for inspecting the current test state. As noted above, the project directory for tests is copied to the temporary directory and run from there.sleep time Calls Thread.sleep(time).newer source target Succeeds if the last modification time of source is more recent than that of target or if target does not exist. Fails otherwise.mkdir path+ Creates directories at the given paths. Further ExamplesSee the existing tests for more examples. They are in the src/sbt-test/ directory. Running Tests
| |