Skip to content

Commit 0f7893d

Browse files
authored
[2.x] docs: Document incOptions.properties in scripted tests (#1756)
1 parent f500646 commit 0f7893d

3 files changed

Lines changed: 148 additions & 0 deletions

File tree

CONTRIBUTING.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ implementation can be found in `internal/compiler-interface` and `internal/compi
9898
while general infrastructure, sbt internal APIs and high-level compiler APIs for
9999
Zinc are available in the rest of projects inside `internal`.
100100

101+
Incremental-compilation behaviour is covered end to end by the scripted tests in
102+
`zinc/src/sbt-test`. See [Scripted tests](contributing-docs/scripted_tests.md) for the test layout,
103+
the `test` script syntax, and the `incOptions.properties` file used to configure Zinc per test.
104+
101105
Zinc also has a JMH benchmark suite. This benchmark suite can benchmark
102106
any project that runs on 2.12.x/2.11.x. The Zinc team uses it
103107
to make sure that there's not a performance regression in the Zinc compiler phases.
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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.

zinc/src/sbt-test/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Scripted tests
2+
==============
3+
4+
End-to-end incremental-compilation scenarios, run with `sbt scripted`.
5+
6+
See [contributing-docs/scripted_tests.md](../../../contributing-docs/scripted_tests.md) for the test
7+
layout, the `test` script syntax, and the `incOptions.properties` format.

0 commit comments

Comments
 (0)