|
| 1 | +Scripted tests |
| 2 | +============== |
| 3 | + |
| 4 | +End-to-end incremental-compilation scenarios. Each test drives a real compile, edits sources, and |
| 5 | +recompiles, asserting on what Zinc decided to recompile. The engine is a custom re-implementation in |
| 6 | +[`internal/zinc-scripted`](../internal/zinc-scripted), not sbt's `scripted` plugin. |
| 7 | + |
| 8 | +Layout |
| 9 | +------ |
| 10 | + |
| 11 | +``` |
| 12 | +zinc/src/sbt-test/<group>/<name>/ |
| 13 | + build.json # optional; project list for multi-project tests |
| 14 | + *.scala, *.java # the sources being compiled |
| 15 | + changes/ # edited versions, copied over the originals mid-test |
| 16 | + incOptions.properties # optional; see below |
| 17 | + test # the script of steps |
| 18 | +``` |
| 19 | + |
| 20 | +Naming the script `pending` instead of `test` marks the scenario as known-failing: it still runs, but |
| 21 | +a failure is tolerated, and *passing* is what fails the build (a reminder to rename it back). A |
| 22 | +`pending` file takes precedence over a `test` file in the same directory. |
| 23 | + |
| 24 | +Groups: `source-dependencies`, `apiinfo`, `macros`, `pipelining`, `profiler`, `reporter`, `general`. |
| 25 | + |
| 26 | +Without a `build.json` the test is a single project named `root` rooted at the test directory. With |
| 27 | +one, each entry declares a `name` and optional `dependsOn`, `in`, and `scalaVersion`. A project's |
| 28 | +base directory is `in` when given, otherwise the subdirectory named after the project |
| 29 | +([IncHandler.scala:124](../internal/zinc-scripted/src/test/scala/sbt/internal/inc/IncHandler.scala#L124)). |
| 30 | + |
| 31 | +The `test` script is one step per line: |
| 32 | + |
| 33 | +- `> compile` runs a task on the root project, `> use/compile` on the `use` project. |
| 34 | +- `-> compile` expects the task to fail. |
| 35 | +- `$ copy-file changes/A.scala A.scala` runs a file command (`copy-file`, `delete`, `exists`, |
| 36 | + `absent`, `newer`, `touch`, `sleep`, `pause`). |
| 37 | +- Assertions are tasks too: `checkRecompilations`, `checkIterations`, `checkProducts`, |
| 38 | + `checkDependencies`, `checkClasses`, `checkWarnings`, `checkErrors`, `checkSame`, and others. |
| 39 | + |
| 40 | +The full task list is the `commands` map in |
| 41 | +[`IncHandler.scala`](../internal/zinc-scripted/src/test/scala/sbt/internal/inc/IncHandler.scala#L229); |
| 42 | +file commands come from `ZincFileCommands` and sbt's `FileCommands`. |
| 43 | + |
| 44 | +Running |
| 45 | +------- |
| 46 | + |
| 47 | +```bash |
| 48 | +sbt scripted # all tests |
| 49 | +sbt "scripted source-dependencies/abstract-class-to-trait" # one test |
| 50 | +sbt "scripted source-dependencies/*" # one group |
| 51 | +``` |
| 52 | + |
| 53 | +`incOptions.properties` |
| 54 | +----------------------- |
| 55 | + |
| 56 | +Scripted tests have no build definition rich enough to configure Zinc, so this file is how a test |
| 57 | +sets a few [`IncOptions`](../internal/compiler-interface/src/main/contraband-java/xsbti/compile/IncOptions.java) |
| 58 | +fields and the scalac options for a project. It is read by |
| 59 | +[`IncHandler.loadIncProperties`](../internal/zinc-scripted/src/test/scala/sbt/internal/inc/IncHandler.scala#L809) |
| 60 | +and parsed by |
| 61 | +[`IncOptionsUtil.fromStringMap`](../internal/zinc-core/src/main/java/xsbti/compile/IncOptionsUtil.java#L78). |
| 62 | + |
| 63 | +Place it in the **project** base directory: the test root for a single-project test, or each |
| 64 | +subproject directory (`dep/`, `use/`) for a multi-project one. Both `incoptions.properties` and |
| 65 | +`incOptions.properties` are accepted, lowercase taking precedence. |
| 66 | + |
| 67 | +Standard `java.util.Properties` syntax (`#` comments, `key = value`). Omitting a key leaves the |
| 68 | +option at the effective scripted default shown below, which for some keys is *not* the `IncOptions` |
| 69 | +default. |
| 70 | + |
| 71 | +```properties |
| 72 | +# source-dependencies/same-source-transitive-invalidation |
| 73 | +transitiveStep = 1 |
| 74 | +``` |
| 75 | + |
| 76 | +### Supported keys |
| 77 | + |
| 78 | +These take effect. "Default" is the value you get when the key is absent from a scripted test, which |
| 79 | +is what a test author needs; where that differs from the `IncOptions` default, the difference is |
| 80 | +noted. |
| 81 | + |
| 82 | +| Key | Value | Default | |
| 83 | +| --- | --- | --- | |
| 84 | +| `transitiveStep` | int: invalidation cycles before falling back to the transitive closure | 3 | |
| 85 | +| `recompileAllFraction` | double: fraction of sources invalidated that triggers a full recompile | **1.0** (`IncOptions` uses 0.5) | |
| 86 | +| `relationsDebug` | boolean: verbose invalidation diagnostics, including the pruned relations and detected API changes | false | |
| 87 | +| `apiDebug` | boolean: log API diffs | **true** (`IncOptions` uses false) | |
| 88 | +| `apiDiffContextSize` | int: context lines in API diffs | 5 | |
| 89 | +| `recompileOnMacroDef` | boolean, or `NOTHING` to leave unset | unset, which behaves as true | |
| 90 | +| `logRecompileOnMacro` | boolean | true | |
| 91 | +| `useOptimizedSealed` | boolean: use the optimized sealed-children invalidation | false | |
| 92 | +| `storeApis` | boolean: persist extracted APIs in the analysis | true | |
| 93 | +| `pipelining` | boolean: pipelined compilation | **true** (`IncOptions` uses false) | |
| 94 | +| `scalac.options` | **space-separated** options passed to the compiler; `[basedir]` expands to the project's absolute base directory. Handled by `IncHandler`, not `IncOptionsUtil` | none | |
| 95 | +| `incOptions.storeApis` | boolean; same as `storeApis`, applied after parsing. Handled by `IncHandler` | true | |
| 96 | + |
| 97 | +Two wrinkles in this table: |
| 98 | + |
| 99 | +- `recompileAllFraction = 0.5` cannot be expressed. The engine detects "not set" by comparing the |
| 100 | + parsed value against `IncOptions.defaultRecompileAllFraction()` rather than checking whether the |
| 101 | + key is present |
| 102 | + ([IncHandler.scala:837](../internal/zinc-scripted/src/test/scala/sbt/internal/inc/IncHandler.scala#L837)), |
| 103 | + so writing the default explicitly still yields 1.0. Any other value is honoured. |
| 104 | +- With `pipelining` on, `-Ypickle-java -Ypickle-write <earlyOutput>` is appended to `scalac.options` |
| 105 | + automatically. Java-heavy tests set `pipelining = false` to opt out. |
| 106 | + |
| 107 | +### Recognized but ineffective keys |
| 108 | + |
| 109 | +`IncOptionsUtil` parses these, so they look supported, but nothing observes them in a scripted run. |
| 110 | + |
| 111 | +| Key | Why | |
| 112 | +| --- | --- | |
| 113 | +| `classfileManagerType` | Always overwritten with a transactional manager rooted at `target/classes.bak` ([IncHandler.scala:363](../internal/zinc-scripted/src/test/scala/sbt/internal/inc/IncHandler.scala#L363)) | |
| 114 | +| `transactionalManagerBaseDirectory` | Only feeds the manager built from `classfileManagerType`, which is then discarded | |
| 115 | +| `allowMachinePath` | Not consulted; the engine hardcodes `true` when it builds the `MappedFileConverter` ([IncHandler.scala:117](../internal/zinc-scripted/src/test/scala/sbt/internal/inc/IncHandler.scala#L117)) | |
| 116 | +| `apiDumpDirectory` | Unimplemented in Zinc itself, as `incremental.contra` notes | |
| 117 | +| `ignoredScalacOptions` | Only affects whether a *change* in scalac options forces a full recompile ([MiniSetupUtil.scala:156](../internal/zinc-core/src/main/scala/sbt/internal/inc/MiniSetupUtil.scala#L156)), and scalac options cannot change during a scripted run, see caveats | |
| 118 | + |
| 119 | +### Unsupported `IncOptions` fields |
| 120 | + |
| 121 | +`IncOptionsUtil` recognizes only the keys listed above. Other `IncOptions` fields, including |
| 122 | +`strictMode`, `enabled`, `useCustomizedFileManager`, `auxiliaryClassFiles`, `extra`, and |
| 123 | +`externalHooks`, have no property key and cannot be set from this file. |
| 124 | + |
| 125 | +### Caveats |
| 126 | + |
| 127 | +1. **The file is read once, at project initialization.** `incOptions` and `scalacOptions` are `val`s |
| 128 | + on `ProjectStructure` |
| 129 | + ([IncHandler.scala:350](../internal/zinc-scripted/src/test/scala/sbt/internal/inc/IncHandler.scala#L350)) |
| 130 | + and every compilation reuses them |
| 131 | + ([IncHandler.scala:723](../internal/zinc-scripted/src/test/scala/sbt/internal/inc/IncHandler.scala#L723)), |
| 132 | + so copying `changes/incOptions.properties` over the live file partway through a `test` script has |
| 133 | + no effect. This is also why `ignoredScalacOptions` is inert: a test cannot vary its scalac options |
| 134 | + between compilations, so the comparison that key relaxes never sees a difference. |
| 135 | + `source-dependencies/scalac-options` is written as if it did work, and passes for other reasons. |
| 136 | +2. **Unknown keys are silently ignored.** There is no validation, so a typo, or one of the |
| 137 | + unsupported fields above, looks exactly like a working setting. |
0 commit comments