Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Selective Execution based on input file and code changes #4091

Merged
merged 39 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/modules/ROOT/pages/depth/large-builds.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ or resource usage, build files are incrementally re-compiled when modified, and
lazily loaded and initialized only when needed. So you are encouraged to break up your project
into modules to manage the layering of your codebase or benefit from parallelism.

== Selective Execution


include::partial$example/depth/large/9-selective-execution.adoc[]

== Multi-file Builds

include::partial$example/depth/large/10-multi-file-builds.adoc[]
Expand Down
10 changes: 5 additions & 5 deletions docs/modules/ROOT/pages/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ JVM build tools have a reputation for being sluggish and confusing. Mill tries t
offer a better alternative, letting your build system take full advantage of the
Java platform's performance and usability:

* *Performance*: Mill's xref:fundamentals/tasks.adoc[build graph] automatically
* *Performance*: Mill automatically
xref:depth/evaluation-model.adoc#_caching_at_each_layer_of_the_evaluation_model[caches]
and xref:cli/flags.adoc#_jobs_j[parallelizes] build
tasks, keeping your workflows fast and responsive. Mill adds minimal overhead over
the logic necessary to build your project, and avoids the long configuration
times often seen in other build tools like Gradle or SBT
and xref:cli/flags.adoc#_jobs_j[parallelizes] build tasks to keep local development fast,
and avoids the long configuration times seen in other tools like Gradle or SBT.
xref:depth/large-builds.adoc#_selective_execution[Selective execution] keeps
CI validation times short by only running the tests necessary to validate a code change.

* *Maintainability*: Mill's config and xref:javalib/intro.adoc#_custom_build_logic[custom logic]
is written in xref:depth/why-scala.adoc[concise type-checked Scala code],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package bar;

public class Bar {
public static String generateHtml(String text) {
return "<h1>" + text + "</h1>";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package bar;

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class BarTests {

@Test
public void simple() {
String result = Bar.generateHtml("hello");
assertEquals("<h1>hello</h1>", result);
}
}
100 changes: 100 additions & 0 deletions example/depth/large/9-selective-execution/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Mill allows you to filter the tests and other tasks you execute by limiting them
// to those affected by a code change. This is useful in managing large codebases where
// running the entire test suite in CI is often very slow, so you only want to run the
// tests or tasks that are affected by the changes you are making.
//
// This is done via the following commands:
//
// * `mill selective.prepare <selector>`: run on the codebase before the code change,
// stores a snapshot of task inputs and implementations
//
// * `mill selective.run <selector>`: run on the codebase after the code change,
// runs tasks in the given `<selector>` which are affected by the code changes
// that have happen since `selective.prepare` was run
//
// * `mill selective.resolve <selector>`: a dry-run version of `selective.run`, prints
// out the tasks in `<selector>` that are affected by the code changes and would have
// run, without actually tunning them.
//
// For example, if you want to run all tests related to the code changes in a pull
// request branch, you can do that as follows:
//
// ```bash
// > git checkout main # start from the target branch of the PR
//
// > ./mill selective.prepare __.test
//
// > git checkout pull-request-branch # go to the pull request branch
//
// > ./mill selective.run __.test
// ```
//
// The example below demonstrates selective test execution on a small 3-module Java build,
// where `bar` depends on `foo` but `qux` is standalone:

package build
import mill._, javalib._

trait MyModule extends JavaModule {
object test extends JavaTests with TestModule.Junit4
}

object foo extends MyModule {
def moduleDeps = Seq(bar)
}

object bar extends MyModule

object qux extends MyModule

// In this example, `qux.test` starts off failing with an error, while `foo.test` and
// `bar.test` pass successfully. Normally, running `__.test` will run all three test
// suites and show both successes and the one failure:

/** Usage

> mill __.test
error: Test run foo.FooTests finished: 0 failed, 0 ignored, 1 total, ...
Test run bar.BarTests finished: 0 failed, 0 ignored, 1 total, ...
Test run qux.QuxTests finished: 1 failed, 0 ignored, 1 total, ...

*/

// However, this is not always what you want. For example:
//
// * If you are validating a pull request
// in CI that only touches `bar/`, you do not want the failure in `qux.test` to fail
// your tests, because you know that `qux.test` does not depend on `bar/` and thus the
// failure cannot be related to your changes.
//
// * Even if `qux.test` wasn't failing, running it on a pull request that changes `bar/` is
// wasteful, taking up compute resources to run tests that could not possibly be affected
// by the code change in question.
//
// To solve this, you can run `selective.prepare` before the code change, then `selective.run`
// after the code change, to only run the tests downstream of the change (below, `foo.test` and `bar.test`):

/** Usage

> mill selective.prepare __.test

> echo '//' >> bar/src/bar/Bar.java # emulate the code change

> mill selective.resolve __.test # dry-run selective execution to show what would get run
foo.test.test
bar.test.test

> mill selective.run __.test
Test run foo.FooTests finished: 0 failed, 0 ignored, 1 total, ...
Test run bar.BarTests finished: 0 failed, 0 ignored, 1 total, ...

*/

// Similarly, if we make a change `qux/`, using selective execution will only run tests
// in `qux.test`, and skip those in `foo.test` and `bar.test`.
// These examples all use `__.test` to selectively run tasks named `.test`, but you can
// use selective execution on any subset of tasks by specifying them in the selector.
//
// Selective execution is very useful for larger codebases, where you are usually changing
// only small parts of it, and thus only want to run the tests related to your changes.
// This keeps CI times fast and prevents unrelated breakages from affecting your CI runs.
Comment on lines +98 to +100
Copy link
Member

@jodersky jodersky Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is the right place, but it might be useful to give a blueprint on how this could be implemented in CI, e.g. with github actions.

If I understand correctly, you'd want to save mill-selective-execution.json in the main branch and fetch it for every pull request CI run. Is that correct?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs are meant to explain how to make use of it. Not sure if I did a good job tho

Copy link
Member

@jodersky jodersky Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they're pretty clear! I guess what my comment boils down to, is that there's no mention of mill-selective-execution.json in the docs, which, if I understand correctly, is the file that needs to be created on CI-main runs and used in CI-PR runs.

But it's a detail and we can ignore it, maybe adding examples later on.

11 changes: 11 additions & 0 deletions example/depth/large/9-selective-execution/foo/src/foo/Foo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package foo;

public class Foo {

public static final String VALUE = "hello";

public static void mainFunction(String fooText, String barText) {
System.out.println("Foo.value: " + Foo.VALUE);
System.out.println("Bar.value: " + bar.Bar.generateHtml(barText));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package foo;

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class FooTests {
@Test
public void simple() {
assertEquals(Foo.VALUE, "hello");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package qux;

public class Qux {
public static String generateHtml(String text) {
return "<p>" + text + "</p>";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package qux;

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class QuxTests {

@Test
public void simple() {
String result = Qux.generateHtml("world");
assertEquals("<p>world!</p>", result);
}
}
3 changes: 2 additions & 1 deletion integration/ide/bsp-modules/src/BspModulesTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ object BspModulesTests extends UtestIntegrationTestSuite {
"HelloBsp.test",
"proj1",
"proj2",
"proj3"
"proj3",
"selective"
).sorted
assert(readModules == expectedModules)
}
Expand Down
Loading
Loading