blob: f41d61b49f6dc49111f4e8b2092375bb86c20eaf [file] [log] [blame]
Colin Crossfeec25b2019-01-30 17:32:39 -08001// Copyright 2019 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package android
16
17import (
Colin Cross758290d2019-02-01 16:42:32 -080018 "fmt"
Colin Crossfeec25b2019-01-30 17:32:39 -080019 "path/filepath"
Colin Cross3d680512020-11-13 16:23:53 -080020 "regexp"
Colin Cross758290d2019-02-01 16:42:32 -080021 "strings"
Colin Crossfeec25b2019-01-30 17:32:39 -080022 "testing"
Dan Willemsen633c5022019-04-12 11:11:38 -070023
24 "github.com/google/blueprint"
25
26 "android/soong/shared"
Colin Crossfeec25b2019-01-30 17:32:39 -080027)
28
Colin Crossf1a035e2020-11-16 17:32:30 -080029func builderContext() BuilderContext {
30 return BuilderContextForTesting(TestConfig("out", nil, "", map[string][]byte{
Colin Cross98be1bb2019-12-13 20:41:13 -080031 "ld": nil,
32 "a.o": nil,
33 "b.o": nil,
34 "cp": nil,
35 "a": nil,
36 "b": nil,
37 "ls": nil,
Jingwen Chence679d22020-09-23 04:30:02 +000038 "ln": nil,
Colin Cross98be1bb2019-12-13 20:41:13 -080039 "turbine": nil,
40 "java": nil,
41 "javac": nil,
42 }))
Colin Cross69f59a32019-02-15 10:39:37 -080043}
44
Colin Cross758290d2019-02-01 16:42:32 -080045func ExampleRuleBuilder() {
Colin Crossf1a035e2020-11-16 17:32:30 -080046 ctx := builderContext()
Colin Cross758290d2019-02-01 16:42:32 -080047
Colin Crossf1a035e2020-11-16 17:32:30 -080048 rule := NewRuleBuilder(pctx, ctx)
Colin Cross69f59a32019-02-15 10:39:37 -080049
50 rule.Command().
51 Tool(PathForSource(ctx, "ld")).
52 Inputs(PathsForTesting("a.o", "b.o")).
53 FlagWithOutput("-o ", PathForOutput(ctx, "linked"))
Colin Cross758290d2019-02-01 16:42:32 -080054 rule.Command().Text("echo success")
55
56 // To add the command to the build graph:
Colin Crossf1a035e2020-11-16 17:32:30 -080057 // rule.Build("link", "link")
Colin Cross758290d2019-02-01 16:42:32 -080058
59 fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && "))
60 fmt.Printf("tools: %q\n", rule.Tools())
61 fmt.Printf("inputs: %q\n", rule.Inputs())
62 fmt.Printf("outputs: %q\n", rule.Outputs())
63
64 // Output:
Colin Cross69f59a32019-02-15 10:39:37 -080065 // commands: "ld a.o b.o -o out/linked && echo success"
Colin Cross758290d2019-02-01 16:42:32 -080066 // tools: ["ld"]
67 // inputs: ["a.o" "b.o"]
Colin Cross69f59a32019-02-15 10:39:37 -080068 // outputs: ["out/linked"]
Colin Cross758290d2019-02-01 16:42:32 -080069}
70
Jingwen Chence679d22020-09-23 04:30:02 +000071func ExampleRuleBuilder_SymlinkOutputs() {
Colin Crossf1a035e2020-11-16 17:32:30 -080072 ctx := builderContext()
Jingwen Chence679d22020-09-23 04:30:02 +000073
Colin Crossf1a035e2020-11-16 17:32:30 -080074 rule := NewRuleBuilder(pctx, ctx)
Jingwen Chence679d22020-09-23 04:30:02 +000075
76 rule.Command().
77 Tool(PathForSource(ctx, "ln")).
78 FlagWithInput("-s ", PathForTesting("a.o")).
79 SymlinkOutput(PathForOutput(ctx, "a"))
80 rule.Command().Text("cp out/a out/b").
81 ImplicitSymlinkOutput(PathForOutput(ctx, "b"))
82
83 fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && "))
84 fmt.Printf("tools: %q\n", rule.Tools())
85 fmt.Printf("inputs: %q\n", rule.Inputs())
86 fmt.Printf("outputs: %q\n", rule.Outputs())
87 fmt.Printf("symlink_outputs: %q\n", rule.SymlinkOutputs())
88
89 // Output:
90 // commands: "ln -s a.o out/a && cp out/a out/b"
91 // tools: ["ln"]
92 // inputs: ["a.o"]
93 // outputs: ["out/a" "out/b"]
94 // symlink_outputs: ["out/a" "out/b"]
95}
96
Colin Cross5cb5b092019-02-02 21:25:18 -080097func ExampleRuleBuilder_Temporary() {
Colin Crossf1a035e2020-11-16 17:32:30 -080098 ctx := builderContext()
Colin Cross5cb5b092019-02-02 21:25:18 -080099
Colin Crossf1a035e2020-11-16 17:32:30 -0800100 rule := NewRuleBuilder(pctx, ctx)
Colin Cross69f59a32019-02-15 10:39:37 -0800101
102 rule.Command().
103 Tool(PathForSource(ctx, "cp")).
104 Input(PathForSource(ctx, "a")).
105 Output(PathForOutput(ctx, "b"))
106 rule.Command().
107 Tool(PathForSource(ctx, "cp")).
108 Input(PathForOutput(ctx, "b")).
109 Output(PathForOutput(ctx, "c"))
110 rule.Temporary(PathForOutput(ctx, "b"))
Colin Cross5cb5b092019-02-02 21:25:18 -0800111
112 fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && "))
113 fmt.Printf("tools: %q\n", rule.Tools())
114 fmt.Printf("inputs: %q\n", rule.Inputs())
115 fmt.Printf("outputs: %q\n", rule.Outputs())
116
117 // Output:
Colin Cross69f59a32019-02-15 10:39:37 -0800118 // commands: "cp a out/b && cp out/b out/c"
Colin Cross5cb5b092019-02-02 21:25:18 -0800119 // tools: ["cp"]
120 // inputs: ["a"]
Colin Cross69f59a32019-02-15 10:39:37 -0800121 // outputs: ["out/c"]
Colin Cross5cb5b092019-02-02 21:25:18 -0800122}
123
124func ExampleRuleBuilder_DeleteTemporaryFiles() {
Colin Crossf1a035e2020-11-16 17:32:30 -0800125 ctx := builderContext()
Colin Cross5cb5b092019-02-02 21:25:18 -0800126
Colin Crossf1a035e2020-11-16 17:32:30 -0800127 rule := NewRuleBuilder(pctx, ctx)
Colin Cross69f59a32019-02-15 10:39:37 -0800128
129 rule.Command().
130 Tool(PathForSource(ctx, "cp")).
131 Input(PathForSource(ctx, "a")).
132 Output(PathForOutput(ctx, "b"))
133 rule.Command().
134 Tool(PathForSource(ctx, "cp")).
135 Input(PathForOutput(ctx, "b")).
136 Output(PathForOutput(ctx, "c"))
137 rule.Temporary(PathForOutput(ctx, "b"))
Colin Cross5cb5b092019-02-02 21:25:18 -0800138 rule.DeleteTemporaryFiles()
139
140 fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && "))
141 fmt.Printf("tools: %q\n", rule.Tools())
142 fmt.Printf("inputs: %q\n", rule.Inputs())
143 fmt.Printf("outputs: %q\n", rule.Outputs())
144
145 // Output:
Colin Cross69f59a32019-02-15 10:39:37 -0800146 // commands: "cp a out/b && cp out/b out/c && rm -f out/b"
Colin Cross5cb5b092019-02-02 21:25:18 -0800147 // tools: ["cp"]
148 // inputs: ["a"]
Colin Cross69f59a32019-02-15 10:39:37 -0800149 // outputs: ["out/c"]
Colin Cross5cb5b092019-02-02 21:25:18 -0800150}
151
Colin Crossdeabb942019-02-11 14:11:09 -0800152func ExampleRuleBuilder_Installs() {
Colin Crossf1a035e2020-11-16 17:32:30 -0800153 ctx := builderContext()
Colin Crossdeabb942019-02-11 14:11:09 -0800154
Colin Crossf1a035e2020-11-16 17:32:30 -0800155 rule := NewRuleBuilder(pctx, ctx)
Colin Cross69f59a32019-02-15 10:39:37 -0800156
157 out := PathForOutput(ctx, "linked")
158
159 rule.Command().
160 Tool(PathForSource(ctx, "ld")).
161 Inputs(PathsForTesting("a.o", "b.o")).
162 FlagWithOutput("-o ", out)
163 rule.Install(out, "/bin/linked")
164 rule.Install(out, "/sbin/linked")
Colin Crossdeabb942019-02-11 14:11:09 -0800165
166 fmt.Printf("rule.Installs().String() = %q\n", rule.Installs().String())
167
168 // Output:
Colin Cross69f59a32019-02-15 10:39:37 -0800169 // rule.Installs().String() = "out/linked:/bin/linked out/linked:/sbin/linked"
Colin Crossdeabb942019-02-11 14:11:09 -0800170}
171
Colin Cross758290d2019-02-01 16:42:32 -0800172func ExampleRuleBuilderCommand() {
Colin Crossf1a035e2020-11-16 17:32:30 -0800173 ctx := builderContext()
Colin Cross758290d2019-02-01 16:42:32 -0800174
Colin Crossf1a035e2020-11-16 17:32:30 -0800175 rule := NewRuleBuilder(pctx, ctx)
Colin Cross69f59a32019-02-15 10:39:37 -0800176
Colin Cross758290d2019-02-01 16:42:32 -0800177 // chained
Colin Cross69f59a32019-02-15 10:39:37 -0800178 rule.Command().
179 Tool(PathForSource(ctx, "ld")).
180 Inputs(PathsForTesting("a.o", "b.o")).
181 FlagWithOutput("-o ", PathForOutput(ctx, "linked"))
Colin Cross758290d2019-02-01 16:42:32 -0800182
183 // unchained
184 cmd := rule.Command()
Colin Cross69f59a32019-02-15 10:39:37 -0800185 cmd.Tool(PathForSource(ctx, "ld"))
186 cmd.Inputs(PathsForTesting("a.o", "b.o"))
187 cmd.FlagWithOutput("-o ", PathForOutput(ctx, "linked"))
Colin Cross758290d2019-02-01 16:42:32 -0800188
189 // mixed:
Colin Cross69f59a32019-02-15 10:39:37 -0800190 cmd = rule.Command().Tool(PathForSource(ctx, "ld"))
191 cmd.Inputs(PathsForTesting("a.o", "b.o"))
192 cmd.FlagWithOutput("-o ", PathForOutput(ctx, "linked"))
Colin Cross758290d2019-02-01 16:42:32 -0800193}
194
195func ExampleRuleBuilderCommand_Flag() {
Colin Crossf1a035e2020-11-16 17:32:30 -0800196 ctx := builderContext()
197 fmt.Println(NewRuleBuilder(pctx, ctx).Command().
Colin Cross69f59a32019-02-15 10:39:37 -0800198 Tool(PathForSource(ctx, "ls")).Flag("-l"))
Colin Cross758290d2019-02-01 16:42:32 -0800199 // Output:
200 // ls -l
201}
202
Colin Cross92b7d582019-03-29 15:32:51 -0700203func ExampleRuleBuilderCommand_Flags() {
Colin Crossf1a035e2020-11-16 17:32:30 -0800204 ctx := builderContext()
205 fmt.Println(NewRuleBuilder(pctx, ctx).Command().
Colin Cross92b7d582019-03-29 15:32:51 -0700206 Tool(PathForSource(ctx, "ls")).Flags([]string{"-l", "-a"}))
207 // Output:
208 // ls -l -a
209}
210
Colin Cross758290d2019-02-01 16:42:32 -0800211func ExampleRuleBuilderCommand_FlagWithArg() {
Colin Crossf1a035e2020-11-16 17:32:30 -0800212 ctx := builderContext()
213 fmt.Println(NewRuleBuilder(pctx, ctx).Command().
Colin Cross69f59a32019-02-15 10:39:37 -0800214 Tool(PathForSource(ctx, "ls")).
Colin Cross758290d2019-02-01 16:42:32 -0800215 FlagWithArg("--sort=", "time"))
216 // Output:
217 // ls --sort=time
218}
219
Colin Crossc7ed0042019-02-11 14:11:09 -0800220func ExampleRuleBuilderCommand_FlagForEachArg() {
Colin Crossf1a035e2020-11-16 17:32:30 -0800221 ctx := builderContext()
222 fmt.Println(NewRuleBuilder(pctx, ctx).Command().
Colin Cross69f59a32019-02-15 10:39:37 -0800223 Tool(PathForSource(ctx, "ls")).
Colin Crossc7ed0042019-02-11 14:11:09 -0800224 FlagForEachArg("--sort=", []string{"time", "size"}))
225 // Output:
226 // ls --sort=time --sort=size
227}
228
Colin Cross758290d2019-02-01 16:42:32 -0800229func ExampleRuleBuilderCommand_FlagForEachInput() {
Colin Crossf1a035e2020-11-16 17:32:30 -0800230 ctx := builderContext()
231 fmt.Println(NewRuleBuilder(pctx, ctx).Command().
Colin Cross69f59a32019-02-15 10:39:37 -0800232 Tool(PathForSource(ctx, "turbine")).
233 FlagForEachInput("--classpath ", PathsForTesting("a.jar", "b.jar")))
Colin Cross758290d2019-02-01 16:42:32 -0800234 // Output:
235 // turbine --classpath a.jar --classpath b.jar
236}
237
238func ExampleRuleBuilderCommand_FlagWithInputList() {
Colin Crossf1a035e2020-11-16 17:32:30 -0800239 ctx := builderContext()
240 fmt.Println(NewRuleBuilder(pctx, ctx).Command().
Colin Cross69f59a32019-02-15 10:39:37 -0800241 Tool(PathForSource(ctx, "java")).
242 FlagWithInputList("-classpath=", PathsForTesting("a.jar", "b.jar"), ":"))
Colin Cross758290d2019-02-01 16:42:32 -0800243 // Output:
244 // java -classpath=a.jar:b.jar
245}
246
247func ExampleRuleBuilderCommand_FlagWithInput() {
Colin Crossf1a035e2020-11-16 17:32:30 -0800248 ctx := builderContext()
249 fmt.Println(NewRuleBuilder(pctx, ctx).Command().
Colin Cross69f59a32019-02-15 10:39:37 -0800250 Tool(PathForSource(ctx, "java")).
251 FlagWithInput("-classpath=", PathForSource(ctx, "a")))
Colin Cross758290d2019-02-01 16:42:32 -0800252 // Output:
253 // java -classpath=a
254}
255
256func ExampleRuleBuilderCommand_FlagWithList() {
Colin Crossf1a035e2020-11-16 17:32:30 -0800257 ctx := builderContext()
258 fmt.Println(NewRuleBuilder(pctx, ctx).Command().
Colin Cross69f59a32019-02-15 10:39:37 -0800259 Tool(PathForSource(ctx, "ls")).
Colin Cross758290d2019-02-01 16:42:32 -0800260 FlagWithList("--sort=", []string{"time", "size"}, ","))
261 // Output:
262 // ls --sort=time,size
263}
264
Colin Cross0cb0d7b2019-07-11 10:59:15 -0700265func ExampleRuleBuilderCommand_FlagWithRspFileInputList() {
Colin Crossf1a035e2020-11-16 17:32:30 -0800266 ctx := builderContext()
267 fmt.Println(NewRuleBuilder(pctx, ctx).Command().
Colin Cross0cb0d7b2019-07-11 10:59:15 -0700268 Tool(PathForSource(ctx, "javac")).
Colin Cross70c47412021-03-12 17:48:14 -0800269 FlagWithRspFileInputList("@", PathForOutput(ctx, "foo.rsp"), PathsForTesting("a.java", "b.java")).
270 String())
Colin Cross0cb0d7b2019-07-11 10:59:15 -0700271 // Output:
Colin Cross70c47412021-03-12 17:48:14 -0800272 // javac @out/foo.rsp
Colin Cross0cb0d7b2019-07-11 10:59:15 -0700273}
274
275func ExampleRuleBuilderCommand_String() {
Colin Crossf1a035e2020-11-16 17:32:30 -0800276 ctx := builderContext()
277 fmt.Println(NewRuleBuilder(pctx, ctx).Command().
Colin Cross0cb0d7b2019-07-11 10:59:15 -0700278 Text("FOO=foo").
279 Text("echo $FOO").
280 String())
281 // Output:
282 // FOO=foo echo $FOO
283}
284
Colin Crossfeec25b2019-01-30 17:32:39 -0800285func TestRuleBuilder(t *testing.T) {
Colin Cross69f59a32019-02-15 10:39:37 -0800286 fs := map[string][]byte{
Colin Crossda71eda2020-02-21 16:55:19 -0800287 "dep_fixer": nil,
288 "input": nil,
289 "Implicit": nil,
290 "Input": nil,
291 "OrderOnly": nil,
292 "OrderOnlys": nil,
293 "Tool": nil,
294 "input2": nil,
295 "tool2": nil,
296 "input3": nil,
Colin Cross69f59a32019-02-15 10:39:37 -0800297 }
298
Colin Crossab020a72021-03-12 17:52:23 -0800299 pathCtx := PathContextForTesting(TestConfig("out_local", nil, "", fs))
Colin Crossf1a035e2020-11-16 17:32:30 -0800300 ctx := builderContextForTests{
301 PathContext: pathCtx,
302 }
Colin Cross69f59a32019-02-15 10:39:37 -0800303
Dan Willemsen633c5022019-04-12 11:11:38 -0700304 addCommands := func(rule *RuleBuilder) {
305 cmd := rule.Command().
Colin Crossab020a72021-03-12 17:52:23 -0800306 DepFile(PathForOutput(ctx, "module/DepFile")).
Dan Willemsen633c5022019-04-12 11:11:38 -0700307 Flag("Flag").
308 FlagWithArg("FlagWithArg=", "arg").
Colin Crossab020a72021-03-12 17:52:23 -0800309 FlagWithDepFile("FlagWithDepFile=", PathForOutput(ctx, "module/depfile")).
Dan Willemsen633c5022019-04-12 11:11:38 -0700310 FlagWithInput("FlagWithInput=", PathForSource(ctx, "input")).
Colin Crossab020a72021-03-12 17:52:23 -0800311 FlagWithOutput("FlagWithOutput=", PathForOutput(ctx, "module/output")).
312 FlagWithRspFileInputList("FlagWithRspFileInputList=", PathForOutput(ctx, "rsp"),
313 Paths{
314 PathForSource(ctx, "RspInput"),
315 PathForOutput(ctx, "other/RspOutput2"),
316 }).
Dan Willemsen633c5022019-04-12 11:11:38 -0700317 Implicit(PathForSource(ctx, "Implicit")).
Colin Crossab020a72021-03-12 17:52:23 -0800318 ImplicitDepFile(PathForOutput(ctx, "module/ImplicitDepFile")).
319 ImplicitOutput(PathForOutput(ctx, "module/ImplicitOutput")).
Dan Willemsen633c5022019-04-12 11:11:38 -0700320 Input(PathForSource(ctx, "Input")).
Colin Crossab020a72021-03-12 17:52:23 -0800321 Output(PathForOutput(ctx, "module/Output")).
Colin Crossda71eda2020-02-21 16:55:19 -0800322 OrderOnly(PathForSource(ctx, "OrderOnly")).
Colin Crossab020a72021-03-12 17:52:23 -0800323 SymlinkOutput(PathForOutput(ctx, "module/SymlinkOutput")).
324 ImplicitSymlinkOutput(PathForOutput(ctx, "module/ImplicitSymlinkOutput")).
Dan Willemsen633c5022019-04-12 11:11:38 -0700325 Text("Text").
326 Tool(PathForSource(ctx, "Tool"))
Colin Crossfeec25b2019-01-30 17:32:39 -0800327
Dan Willemsen633c5022019-04-12 11:11:38 -0700328 rule.Command().
329 Text("command2").
Colin Crossab020a72021-03-12 17:52:23 -0800330 DepFile(PathForOutput(ctx, "module/depfile2")).
Dan Willemsen633c5022019-04-12 11:11:38 -0700331 Input(PathForSource(ctx, "input2")).
Colin Crossab020a72021-03-12 17:52:23 -0800332 Output(PathForOutput(ctx, "module/output2")).
Colin Crossda71eda2020-02-21 16:55:19 -0800333 OrderOnlys(PathsForSource(ctx, []string{"OrderOnlys"})).
Dan Willemsen633c5022019-04-12 11:11:38 -0700334 Tool(PathForSource(ctx, "tool2"))
Colin Crossfeec25b2019-01-30 17:32:39 -0800335
Dan Willemsen633c5022019-04-12 11:11:38 -0700336 // Test updates to the first command after the second command has been started
337 cmd.Text("after command2")
338 // Test updating a command when the previous update did not replace the cmd variable
339 cmd.Text("old cmd")
Colin Crossfeec25b2019-01-30 17:32:39 -0800340
Dan Willemsen633c5022019-04-12 11:11:38 -0700341 // Test a command that uses the output of a previous command as an input
342 rule.Command().
343 Text("command3").
344 Input(PathForSource(ctx, "input3")).
Colin Crossab020a72021-03-12 17:52:23 -0800345 Input(PathForOutput(ctx, "module/output2")).
346 Output(PathForOutput(ctx, "module/output3")).
347 Text(cmd.PathForInput(PathForSource(ctx, "input3"))).
348 Text(cmd.PathForOutput(PathForOutput(ctx, "module/output2")))
Colin Crossfeec25b2019-01-30 17:32:39 -0800349 }
Colin Cross1d2cf042019-03-29 15:33:06 -0700350
Colin Cross69f59a32019-02-15 10:39:37 -0800351 wantInputs := PathsForSource(ctx, []string{"Implicit", "Input", "input", "input2", "input3"})
Colin Crossab020a72021-03-12 17:52:23 -0800352 wantRspFileInputs := Paths{PathForSource(ctx, "RspInput"),
353 PathForOutput(ctx, "other/RspOutput2")}
354 wantOutputs := PathsForOutput(ctx, []string{
355 "module/ImplicitOutput", "module/ImplicitSymlinkOutput", "module/Output", "module/SymlinkOutput",
356 "module/output", "module/output2", "module/output3"})
357 wantDepFiles := PathsForOutput(ctx, []string{
358 "module/DepFile", "module/depfile", "module/ImplicitDepFile", "module/depfile2"})
Colin Cross69f59a32019-02-15 10:39:37 -0800359 wantTools := PathsForSource(ctx, []string{"Tool", "tool2"})
Colin Crossda71eda2020-02-21 16:55:19 -0800360 wantOrderOnlys := PathsForSource(ctx, []string{"OrderOnly", "OrderOnlys"})
Colin Crossab020a72021-03-12 17:52:23 -0800361 wantSymlinkOutputs := PathsForOutput(ctx, []string{
362 "module/ImplicitSymlinkOutput", "module/SymlinkOutput"})
Colin Crossfeec25b2019-01-30 17:32:39 -0800363
Dan Willemsen633c5022019-04-12 11:11:38 -0700364 t.Run("normal", func(t *testing.T) {
Colin Crossf1a035e2020-11-16 17:32:30 -0800365 rule := NewRuleBuilder(pctx, ctx)
Dan Willemsen633c5022019-04-12 11:11:38 -0700366 addCommands(rule)
Colin Cross1d2cf042019-03-29 15:33:06 -0700367
Dan Willemsen633c5022019-04-12 11:11:38 -0700368 wantCommands := []string{
Colin Crossab020a72021-03-12 17:52:23 -0800369 "out_local/module/DepFile Flag FlagWithArg=arg FlagWithDepFile=out_local/module/depfile " +
370 "FlagWithInput=input FlagWithOutput=out_local/module/output FlagWithRspFileInputList=out_local/rsp " +
371 "Input out_local/module/Output out_local/module/SymlinkOutput Text Tool after command2 old cmd",
372 "command2 out_local/module/depfile2 input2 out_local/module/output2 tool2",
373 "command3 input3 out_local/module/output2 out_local/module/output3 input3 out_local/module/output2",
Dan Willemsen633c5022019-04-12 11:11:38 -0700374 }
Colin Cross1d2cf042019-03-29 15:33:06 -0700375
Colin Crossab020a72021-03-12 17:52:23 -0800376 wantDepMergerCommand := "out_local/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer " +
377 "out_local/module/DepFile out_local/module/depfile out_local/module/ImplicitDepFile out_local/module/depfile2"
378
379 wantRspFileContent := "$in"
Dan Willemsen633c5022019-04-12 11:11:38 -0700380
Paul Duffind250ff62021-03-16 21:51:29 +0000381 AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
Dan Willemsen633c5022019-04-12 11:11:38 -0700382
Paul Duffind250ff62021-03-16 21:51:29 +0000383 AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs())
Colin Crossab020a72021-03-12 17:52:23 -0800384 AssertDeepEquals(t, "rule.RspfileInputs()", wantRspFileInputs, rule.RspFileInputs())
Paul Duffind250ff62021-03-16 21:51:29 +0000385 AssertDeepEquals(t, "rule.Outputs()", wantOutputs, rule.Outputs())
386 AssertDeepEquals(t, "rule.SymlinkOutputs()", wantSymlinkOutputs, rule.SymlinkOutputs())
387 AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles())
388 AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools())
389 AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
Dan Willemsen633c5022019-04-12 11:11:38 -0700390
Paul Duffind250ff62021-03-16 21:51:29 +0000391 AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
Colin Crossab020a72021-03-12 17:52:23 -0800392
393 AssertSame(t, "rule.composeRspFileContent()", wantRspFileContent, rule.composeRspFileContent(rule.RspFileInputs()))
Dan Willemsen633c5022019-04-12 11:11:38 -0700394 })
395
396 t.Run("sbox", func(t *testing.T) {
Colin Crossab020a72021-03-12 17:52:23 -0800397 rule := NewRuleBuilder(pctx, ctx).Sbox(PathForOutput(ctx, "module"),
Colin Crosse16ce362020-11-12 08:29:30 -0800398 PathForOutput(ctx, "sbox.textproto"))
Dan Willemsen633c5022019-04-12 11:11:38 -0700399 addCommands(rule)
400
401 wantCommands := []string{
Colin Crossab020a72021-03-12 17:52:23 -0800402 "__SBOX_SANDBOX_DIR__/out/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_SANDBOX_DIR__/out/depfile " +
403 "FlagWithInput=input FlagWithOutput=__SBOX_SANDBOX_DIR__/out/output " +
404 "FlagWithRspFileInputList=out_local/rsp Input __SBOX_SANDBOX_DIR__/out/Output " +
405 "__SBOX_SANDBOX_DIR__/out/SymlinkOutput Text Tool after command2 old cmd",
Colin Crosse16ce362020-11-12 08:29:30 -0800406 "command2 __SBOX_SANDBOX_DIR__/out/depfile2 input2 __SBOX_SANDBOX_DIR__/out/output2 tool2",
Colin Crossab020a72021-03-12 17:52:23 -0800407 "command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3 input3 __SBOX_SANDBOX_DIR__/out/output2",
Dan Willemsen633c5022019-04-12 11:11:38 -0700408 }
409
Colin Crossab020a72021-03-12 17:52:23 -0800410 wantDepMergerCommand := "out_local/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
411
412 wantRspFileContent := "$in"
Dan Willemsen633c5022019-04-12 11:11:38 -0700413
Paul Duffind250ff62021-03-16 21:51:29 +0000414 AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
Dan Willemsen633c5022019-04-12 11:11:38 -0700415
Paul Duffind250ff62021-03-16 21:51:29 +0000416 AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs())
Colin Crossab020a72021-03-12 17:52:23 -0800417 AssertDeepEquals(t, "rule.RspfileInputs()", wantRspFileInputs, rule.RspFileInputs())
Paul Duffind250ff62021-03-16 21:51:29 +0000418 AssertDeepEquals(t, "rule.Outputs()", wantOutputs, rule.Outputs())
Colin Crossab020a72021-03-12 17:52:23 -0800419 AssertDeepEquals(t, "rule.SymlinkOutputs()", wantSymlinkOutputs, rule.SymlinkOutputs())
Paul Duffind250ff62021-03-16 21:51:29 +0000420 AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles())
421 AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools())
422 AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
Dan Willemsen633c5022019-04-12 11:11:38 -0700423
Paul Duffind250ff62021-03-16 21:51:29 +0000424 AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
Colin Crossab020a72021-03-12 17:52:23 -0800425
426 AssertSame(t, "rule.composeRspFileContent()", wantRspFileContent, rule.composeRspFileContent(rule.RspFileInputs()))
Dan Willemsen633c5022019-04-12 11:11:38 -0700427 })
Colin Crossba9e4032020-11-24 16:32:22 -0800428
429 t.Run("sbox tools", func(t *testing.T) {
Colin Crossab020a72021-03-12 17:52:23 -0800430 rule := NewRuleBuilder(pctx, ctx).Sbox(PathForOutput(ctx, "module"),
Colin Crossba9e4032020-11-24 16:32:22 -0800431 PathForOutput(ctx, "sbox.textproto")).SandboxTools()
432 addCommands(rule)
433
434 wantCommands := []string{
Colin Crossab020a72021-03-12 17:52:23 -0800435 "__SBOX_SANDBOX_DIR__/out/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_SANDBOX_DIR__/out/depfile " +
436 "FlagWithInput=input FlagWithOutput=__SBOX_SANDBOX_DIR__/out/output " +
437 "FlagWithRspFileInputList=out_local/rsp Input __SBOX_SANDBOX_DIR__/out/Output " +
438 "__SBOX_SANDBOX_DIR__/out/SymlinkOutput Text __SBOX_SANDBOX_DIR__/tools/src/Tool after command2 old cmd",
Colin Crossba9e4032020-11-24 16:32:22 -0800439 "command2 __SBOX_SANDBOX_DIR__/out/depfile2 input2 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/tools/src/tool2",
Colin Crossab020a72021-03-12 17:52:23 -0800440 "command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3 input3 __SBOX_SANDBOX_DIR__/out/output2",
Colin Crossba9e4032020-11-24 16:32:22 -0800441 }
442
443 wantDepMergerCommand := "__SBOX_SANDBOX_DIR__/tools/out/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
444
Colin Crossab020a72021-03-12 17:52:23 -0800445 wantRspFileContent := "$in"
446
Paul Duffind250ff62021-03-16 21:51:29 +0000447 AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
Colin Crossba9e4032020-11-24 16:32:22 -0800448
Paul Duffind250ff62021-03-16 21:51:29 +0000449 AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs())
Colin Crossab020a72021-03-12 17:52:23 -0800450 AssertDeepEquals(t, "rule.RspfileInputs()", wantRspFileInputs, rule.RspFileInputs())
Paul Duffind250ff62021-03-16 21:51:29 +0000451 AssertDeepEquals(t, "rule.Outputs()", wantOutputs, rule.Outputs())
Colin Crossab020a72021-03-12 17:52:23 -0800452 AssertDeepEquals(t, "rule.SymlinkOutputs()", wantSymlinkOutputs, rule.SymlinkOutputs())
Paul Duffind250ff62021-03-16 21:51:29 +0000453 AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles())
454 AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools())
455 AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
Colin Crossba9e4032020-11-24 16:32:22 -0800456
Paul Duffind250ff62021-03-16 21:51:29 +0000457 AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
Colin Crossab020a72021-03-12 17:52:23 -0800458
459 AssertSame(t, "rule.composeRspFileContent()", wantRspFileContent, rule.composeRspFileContent(rule.RspFileInputs()))
Colin Crossba9e4032020-11-24 16:32:22 -0800460 })
Colin Crossfeec25b2019-01-30 17:32:39 -0800461}
462
463func testRuleBuilderFactory() Module {
464 module := &testRuleBuilderModule{}
465 module.AddProperties(&module.properties)
466 InitAndroidModule(module)
467 return module
468}
469
470type testRuleBuilderModule struct {
471 ModuleBase
472 properties struct {
Colin Cross3d680512020-11-13 16:23:53 -0800473 Srcs []string
Dan Willemsen633c5022019-04-12 11:11:38 -0700474
475 Restat bool
476 Sbox bool
Colin Crossfeec25b2019-01-30 17:32:39 -0800477 }
478}
479
480func (t *testRuleBuilderModule) GenerateAndroidBuildActions(ctx ModuleContext) {
Colin Cross3d680512020-11-13 16:23:53 -0800481 in := PathsForSource(ctx, t.properties.Srcs)
Colin Crosse16ce362020-11-12 08:29:30 -0800482 out := PathForModuleOut(ctx, "gen", ctx.ModuleName())
483 outDep := PathForModuleOut(ctx, "gen", ctx.ModuleName()+".d")
484 outDir := PathForModuleOut(ctx, "gen")
485 manifestPath := PathForModuleOut(ctx, "sbox.textproto")
Colin Crossfeec25b2019-01-30 17:32:39 -0800486
Colin Crosse16ce362020-11-12 08:29:30 -0800487 testRuleBuilder_Build(ctx, in, out, outDep, outDir, manifestPath, t.properties.Restat, t.properties.Sbox)
Colin Cross786cd6d2019-02-01 16:41:11 -0800488}
489
490type testRuleBuilderSingleton struct{}
491
492func testRuleBuilderSingletonFactory() Singleton {
493 return &testRuleBuilderSingleton{}
494}
495
496func (t *testRuleBuilderSingleton) GenerateBuildActions(ctx SingletonContext) {
497 in := PathForSource(ctx, "bar")
Colin Crosse16ce362020-11-12 08:29:30 -0800498 out := PathForOutput(ctx, "singleton/gen/baz")
499 outDep := PathForOutput(ctx, "singleton/gen/baz.d")
500 outDir := PathForOutput(ctx, "singleton/gen")
501 manifestPath := PathForOutput(ctx, "singleton/sbox.textproto")
502 testRuleBuilder_Build(ctx, Paths{in}, out, outDep, outDir, manifestPath, true, false)
Colin Cross786cd6d2019-02-01 16:41:11 -0800503}
504
Colin Crosse16ce362020-11-12 08:29:30 -0800505func testRuleBuilder_Build(ctx BuilderContext, in Paths, out, outDep, outDir, manifestPath WritablePath, restat, sbox bool) {
Colin Crossf1a035e2020-11-16 17:32:30 -0800506 rule := NewRuleBuilder(pctx, ctx)
Colin Cross786cd6d2019-02-01 16:41:11 -0800507
Dan Willemsen633c5022019-04-12 11:11:38 -0700508 if sbox {
Colin Crosse16ce362020-11-12 08:29:30 -0800509 rule.Sbox(outDir, manifestPath)
Dan Willemsen633c5022019-04-12 11:11:38 -0700510 }
Colin Crossfeec25b2019-01-30 17:32:39 -0800511
Colin Cross3d680512020-11-13 16:23:53 -0800512 rule.Command().Tool(PathForSource(ctx, "cp")).Inputs(in).Output(out).ImplicitDepFile(outDep)
Dan Willemsen633c5022019-04-12 11:11:38 -0700513
514 if restat {
515 rule.Restat()
516 }
Colin Crossbaa676f2019-02-25 14:56:01 -0800517
Colin Crossf1a035e2020-11-16 17:32:30 -0800518 rule.Build("rule", "desc")
Colin Crossfeec25b2019-01-30 17:32:39 -0800519}
520
Paul Duffind250ff62021-03-16 21:51:29 +0000521var prepareForRuleBuilderTest = FixtureRegisterWithContext(func(ctx RegistrationContext) {
522 ctx.RegisterModuleType("rule_builder_test", testRuleBuilderFactory)
523 ctx.RegisterSingletonType("rule_builder_test", testRuleBuilderSingletonFactory)
524})
525
Colin Crossfeec25b2019-01-30 17:32:39 -0800526func TestRuleBuilder_Build(t *testing.T) {
Paul Duffind250ff62021-03-16 21:51:29 +0000527 fs := MockFS{
Colin Cross98be1bb2019-12-13 20:41:13 -0800528 "bar": nil,
529 "cp": nil,
530 }
531
Colin Crossfeec25b2019-01-30 17:32:39 -0800532 bp := `
533 rule_builder_test {
534 name: "foo",
Colin Cross3d680512020-11-13 16:23:53 -0800535 srcs: ["bar"],
Dan Willemsen633c5022019-04-12 11:11:38 -0700536 restat: true,
537 }
538 rule_builder_test {
539 name: "foo_sbox",
Colin Cross3d680512020-11-13 16:23:53 -0800540 srcs: ["bar"],
Dan Willemsen633c5022019-04-12 11:11:38 -0700541 sbox: true,
Colin Crossfeec25b2019-01-30 17:32:39 -0800542 }
543 `
544
Paul Duffin30ac3e72021-03-20 00:36:14 +0000545 result := GroupFixturePreparers(
Paul Duffind250ff62021-03-16 21:51:29 +0000546 prepareForRuleBuilderTest,
547 FixtureWithRootAndroidBp(bp),
548 fs.AddToFixture(),
Paul Duffin30ac3e72021-03-20 00:36:14 +0000549 ).RunTest(t)
Colin Crossfeec25b2019-01-30 17:32:39 -0800550
Colin Crosse16ce362020-11-12 08:29:30 -0800551 check := func(t *testing.T, params TestingBuildParams, wantCommand, wantOutput, wantDepfile string, wantRestat bool, extraImplicits, extraCmdDeps []string) {
Dan Willemsenc89b6f12019-08-29 14:47:40 -0700552 t.Helper()
Colin Cross3d680512020-11-13 16:23:53 -0800553 command := params.RuleParams.Command
Colin Crosse16ce362020-11-12 08:29:30 -0800554 re := regexp.MustCompile(" # hash of input list: [a-z0-9]*$")
Colin Cross3d680512020-11-13 16:23:53 -0800555 command = re.ReplaceAllLiteralString(command, "")
Paul Duffind250ff62021-03-16 21:51:29 +0000556
557 AssertStringEquals(t, "RuleParams.Command", wantCommand, command)
Dan Willemsen633c5022019-04-12 11:11:38 -0700558
559 wantDeps := append([]string{"cp"}, extraCmdDeps...)
Paul Duffind250ff62021-03-16 21:51:29 +0000560 AssertArrayString(t, "RuleParams.CommandDeps", wantDeps, params.RuleParams.CommandDeps)
Dan Willemsen633c5022019-04-12 11:11:38 -0700561
Paul Duffind250ff62021-03-16 21:51:29 +0000562 AssertBoolEquals(t, "RuleParams.Restat", wantRestat, params.RuleParams.Restat)
Colin Crossfeec25b2019-01-30 17:32:39 -0800563
Colin Crosse16ce362020-11-12 08:29:30 -0800564 wantImplicits := append([]string{"bar"}, extraImplicits...)
Paul Duffind250ff62021-03-16 21:51:29 +0000565 AssertArrayString(t, "Implicits", wantImplicits, params.Implicits.Strings())
Colin Crossfeec25b2019-01-30 17:32:39 -0800566
Paul Duffind250ff62021-03-16 21:51:29 +0000567 AssertStringEquals(t, "Output", wantOutput, params.Output.String())
Colin Crossbaa676f2019-02-25 14:56:01 -0800568
Dan Willemsen633c5022019-04-12 11:11:38 -0700569 if len(params.ImplicitOutputs) != 0 {
570 t.Errorf("want ImplicitOutputs = [], got %q", params.ImplicitOutputs.Strings())
571 }
572
Paul Duffind250ff62021-03-16 21:51:29 +0000573 AssertStringEquals(t, "Depfile", wantDepfile, params.Depfile.String())
Dan Willemsen633c5022019-04-12 11:11:38 -0700574
575 if params.Deps != blueprint.DepsGCC {
576 t.Errorf("want Deps = %q, got %q", blueprint.DepsGCC, params.Deps)
Colin Crossbaa676f2019-02-25 14:56:01 -0800577 }
Colin Crossfeec25b2019-01-30 17:32:39 -0800578 }
579
Paul Duffind250ff62021-03-16 21:51:29 +0000580 buildDir := result.Config.BuildDir()
581
Colin Cross4c83e5c2019-02-25 14:54:28 -0800582 t.Run("module", func(t *testing.T) {
Colin Crosse16ce362020-11-12 08:29:30 -0800583 outFile := filepath.Join(buildDir, ".intermediates", "foo", "gen", "foo")
Paul Duffind250ff62021-03-16 21:51:29 +0000584 check(t, result.ModuleForTests("foo", "").Rule("rule"),
Dan Willemsen633c5022019-04-12 11:11:38 -0700585 "cp bar "+outFile,
Colin Crosse16ce362020-11-12 08:29:30 -0800586 outFile, outFile+".d", true, nil, nil)
Dan Willemsen633c5022019-04-12 11:11:38 -0700587 })
588 t.Run("sbox", func(t *testing.T) {
589 outDir := filepath.Join(buildDir, ".intermediates", "foo_sbox")
Colin Crosse16ce362020-11-12 08:29:30 -0800590 outFile := filepath.Join(outDir, "gen/foo_sbox")
591 depFile := filepath.Join(outDir, "gen/foo_sbox.d")
592 manifest := filepath.Join(outDir, "sbox.textproto")
Paul Duffind250ff62021-03-16 21:51:29 +0000593 sbox := filepath.Join(buildDir, "host", result.Config.PrebuiltOS(), "bin/sbox")
Dan Willemsen633c5022019-04-12 11:11:38 -0700594 sandboxPath := shared.TempDirForOutDir(buildDir)
595
Colin Crosse16ce362020-11-12 08:29:30 -0800596 cmd := `rm -rf ` + outDir + `/gen && ` +
597 sbox + ` --sandbox-path ` + sandboxPath + ` --manifest ` + manifest
Dan Willemsen633c5022019-04-12 11:11:38 -0700598
Paul Duffind250ff62021-03-16 21:51:29 +0000599 check(t, result.ModuleForTests("foo_sbox", "").Output("gen/foo_sbox"),
Colin Crosse16ce362020-11-12 08:29:30 -0800600 cmd, outFile, depFile, false, []string{manifest}, []string{sbox})
Colin Cross4c83e5c2019-02-25 14:54:28 -0800601 })
602 t.Run("singleton", func(t *testing.T) {
Colin Crosse16ce362020-11-12 08:29:30 -0800603 outFile := filepath.Join(buildDir, "singleton/gen/baz")
Paul Duffind250ff62021-03-16 21:51:29 +0000604 check(t, result.SingletonForTests("rule_builder_test").Rule("rule"),
Colin Crosse16ce362020-11-12 08:29:30 -0800605 "cp bar "+outFile, outFile, outFile+".d", true, nil, nil)
Colin Cross4c83e5c2019-02-25 14:54:28 -0800606 })
Colin Crossfeec25b2019-01-30 17:32:39 -0800607}
Colin Cross0cb0d7b2019-07-11 10:59:15 -0700608
Colin Cross3d680512020-11-13 16:23:53 -0800609func TestRuleBuilderHashInputs(t *testing.T) {
610 // The basic idea here is to verify that the command (in the case of a
611 // non-sbox rule) or the sbox textproto manifest contain a hash of the
612 // inputs.
613
614 // By including a hash of the inputs, we cause the rule to re-run if
615 // the list of inputs changes because the command line or a dependency
616 // changes.
617
618 bp := `
619 rule_builder_test {
620 name: "hash0",
621 srcs: ["in1.txt", "in2.txt"],
622 }
623 rule_builder_test {
624 name: "hash0_sbox",
625 srcs: ["in1.txt", "in2.txt"],
626 sbox: true,
627 }
628 rule_builder_test {
629 name: "hash1",
630 srcs: ["in1.txt", "in2.txt", "in3.txt"],
631 }
632 rule_builder_test {
633 name: "hash1_sbox",
634 srcs: ["in1.txt", "in2.txt", "in3.txt"],
635 sbox: true,
636 }
637 `
638 testcases := []struct {
639 name string
640 expectedHash string
641 }{
642 {
643 name: "hash0",
644 // sha256 value obtained from: echo -en 'in1.txt\nin2.txt' | sha256sum
645 expectedHash: "18da75b9b1cc74b09e365b4ca2e321b5d618f438cc632b387ad9dc2ab4b20e9d",
646 },
647 {
648 name: "hash1",
649 // sha256 value obtained from: echo -en 'in1.txt\nin2.txt\nin3.txt' | sha256sum
650 expectedHash: "a38d432a4b19df93140e1f1fe26c97ff0387dae01fe506412b47208f0595fb45",
651 },
652 }
653
Paul Duffin30ac3e72021-03-20 00:36:14 +0000654 result := GroupFixturePreparers(
Paul Duffind250ff62021-03-16 21:51:29 +0000655 prepareForRuleBuilderTest,
656 FixtureWithRootAndroidBp(bp),
Paul Duffin30ac3e72021-03-20 00:36:14 +0000657 ).RunTest(t)
Colin Cross3d680512020-11-13 16:23:53 -0800658
659 for _, test := range testcases {
660 t.Run(test.name, func(t *testing.T) {
661 t.Run("sbox", func(t *testing.T) {
Paul Duffind250ff62021-03-16 21:51:29 +0000662 gen := result.ModuleForTests(test.name+"_sbox", "")
Colin Crosse16ce362020-11-12 08:29:30 -0800663 manifest := RuleBuilderSboxProtoForTests(t, gen.Output("sbox.textproto"))
664 hash := manifest.Commands[0].GetInputHash()
665
Paul Duffind250ff62021-03-16 21:51:29 +0000666 AssertStringEquals(t, "hash", test.expectedHash, hash)
Colin Cross3d680512020-11-13 16:23:53 -0800667 })
668 t.Run("", func(t *testing.T) {
Paul Duffind250ff62021-03-16 21:51:29 +0000669 gen := result.ModuleForTests(test.name+"", "")
Colin Crosse16ce362020-11-12 08:29:30 -0800670 command := gen.Output("gen/" + test.name).RuleParams.Command
Colin Cross3d680512020-11-13 16:23:53 -0800671 if g, w := command, " # hash of input list: "+test.expectedHash; !strings.HasSuffix(g, w) {
672 t.Errorf("Expected command line to end with %q, got %q", w, g)
673 }
674 })
675 })
676 }
677}