@@ -6,6 +6,8 @@ import mill.define.Cached
66import mill .define .SelectMode
77import ujson .Value
88
9+ import scala .concurrent .duration .*
10+
911/**
1012 * Helper meant for executing Mill integration tests, which runs Mill in a subprocess
1113 * against a folder with a `build.mill` and project files. Provides APIs such as [[eval ]]
@@ -41,6 +43,32 @@ object IntegrationTester {
4143 def isSuccess = exitCode == 0
4244 }
4345
46+ /** An [[Impl.eval ]] that is prepared for execution but haven't been executed yet. Run it with [[run ]]. */
47+ case class PreparedEval (
48+ cmd : os.Shellable ,
49+ env : Map [String , String ],
50+ cwd : os.Path ,
51+ timeout : Duration ,
52+ check : Boolean ,
53+ propagateEnv : Boolean = true ,
54+ shutdownGracePeriod : Long = 100 ,
55+ run : () => EvalResult
56+ ) {
57+
58+ /** Clues to use for with [[withTestClues ]]. */
59+ def clues : Seq [utest.TestValue ] = Seq (
60+ // Copy-pastable shell command that you can run in bash/zsh/whatever
61+ asTestValue(" cmd" , cmd.value.iterator.map(pprint.Util .literalize(_)).mkString(" " )),
62+ asTestValue(" cmd.shellable" , cmd),
63+ asTestValue(env),
64+ asTestValue(cwd),
65+ asTestValue(timeout),
66+ asTestValue(check),
67+ asTestValue(propagateEnv),
68+ asTestValue(shutdownGracePeriod)
69+ ).map(tv => tv.copy(name = " eval." + tv.name))
70+ }
71+
4472 trait Impl extends AutoCloseable with IntegrationTesterBase {
4573
4674 def millExecutable : os.Path
@@ -51,12 +79,11 @@ object IntegrationTester {
5179 def debugLog = false
5280
5381 /**
54- * Evaluates a Mill command. Essentially the same as `os.call`, except it
55- * provides the Mill executable and some test flags and environment variables
56- * for you, and wraps the output in a [[IntegrationTester.EvalResult ]] for
57- * convenience.
82+ * Prepares to evaluate a Mill command. Run it with [[IntegrationTester.PreparedEval.run ]].
83+ *
84+ * Useful when you need the [[IntegrationTester.PreparedEval.clues ]].
5885 */
59- def eval (
86+ def prepEval (
6087 cmd : os.Shellable ,
6188 env : Map [String , String ] = Map .empty,
6289 cwd : os.Path = workspacePath,
@@ -68,17 +95,72 @@ object IntegrationTester {
6895 check : Boolean = false ,
6996 propagateEnv : Boolean = true ,
7097 timeoutGracePeriod : Long = 100
71- ): IntegrationTester .EvalResult = {
98+ ): IntegrationTester .PreparedEval = {
7299 val serverArgs = Option .when(! daemonMode)(" --no-daemon" )
73100
74101 val debugArgs = Option .when(debugLog)(" --debug" )
75102
76103 val shellable : os.Shellable =
77104 (millExecutable, serverArgs, " --ticker" , " false" , debugArgs, cmd)
78105
79- val res0 = os.call(
106+ val callEnv = millTestSuiteEnv ++ env
107+
108+ def run () = {
109+ val res0 = os.call(
110+ cmd = shellable,
111+ env = callEnv,
112+ cwd = cwd,
113+ stdin = stdin,
114+ stdout = stdout,
115+ stderr = stderr,
116+ mergeErrIntoOut = mergeErrIntoOut,
117+ timeout = timeout,
118+ check = check,
119+ propagateEnv = propagateEnv,
120+ shutdownGracePeriod = timeoutGracePeriod
121+ )
122+
123+ IntegrationTester .EvalResult (
124+ res0.exitCode,
125+ fansi.Str (res0.out.text(), errorMode = fansi.ErrorMode .Strip ).plainText.trim,
126+ fansi.Str (res0.err.text(), errorMode = fansi.ErrorMode .Strip ).plainText.trim
127+ )
128+ }
129+
130+ PreparedEval (
80131 cmd = shellable,
81- env = millTestSuiteEnv ++ env,
132+ env = callEnv,
133+ cwd = cwd,
134+ timeout = if (timeout == - 1 ) Duration .Inf else timeout.millis,
135+ check = check,
136+ propagateEnv = propagateEnv,
137+ shutdownGracePeriod = timeoutGracePeriod,
138+ run = run
139+ )
140+ }
141+
142+ /**
143+ * Evaluates a Mill command. Essentially the same as `os.call`, except it
144+ * provides the Mill executable and some test flags and environment variables
145+ * for you, and wraps the output in a [[IntegrationTester.EvalResult ]] for
146+ * convenience.
147+ */
148+ def eval (
149+ cmd : os.Shellable ,
150+ env : Map [String , String ] = Map .empty,
151+ cwd : os.Path = workspacePath,
152+ stdin : os.ProcessInput = os.Pipe ,
153+ stdout : os.ProcessOutput = os.Pipe ,
154+ stderr : os.ProcessOutput = os.Pipe ,
155+ mergeErrIntoOut : Boolean = false ,
156+ timeout : Long = - 1 ,
157+ check : Boolean = false ,
158+ propagateEnv : Boolean = true ,
159+ timeoutGracePeriod : Long = 100
160+ ): IntegrationTester .EvalResult = {
161+ prepEval(
162+ cmd = cmd,
163+ env = env,
82164 cwd = cwd,
83165 stdin = stdin,
84166 stdout = stdout,
@@ -87,14 +169,8 @@ object IntegrationTester {
87169 timeout = timeout,
88170 check = check,
89171 propagateEnv = propagateEnv,
90- shutdownGracePeriod = timeoutGracePeriod
91- )
92-
93- IntegrationTester .EvalResult (
94- res0.exitCode,
95- fansi.Str (res0.out.text(), errorMode = fansi.ErrorMode .Strip ).plainText.trim,
96- fansi.Str (res0.err.text(), errorMode = fansi.ErrorMode .Strip ).plainText.trim
97- )
172+ timeoutGracePeriod = timeoutGracePeriod
173+ ).run()
98174 }
99175
100176 /**
0 commit comments