• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2020 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 bazel
16
17import (
18	"encoding/json"
19	"fmt"
20	"reflect"
21	"sort"
22	"testing"
23
24	analysis_v2_proto "prebuilts/bazel/common/proto/analysis_v2"
25
26	"github.com/google/blueprint/metrics"
27	"google.golang.org/protobuf/proto"
28)
29
30func TestAqueryMultiArchGenrule(t *testing.T) {
31	// This input string is retrieved from a real build of bionic-related genrules.
32	const inputString = `
33{
34 "Artifacts": [
35   { "Id": 1, "path_fragment_id": 1 },
36   { "Id": 2, "path_fragment_id": 6 },
37   { "Id": 3, "path_fragment_id": 8 },
38   { "Id": 4, "path_fragment_id": 12 },
39   { "Id": 5, "path_fragment_id": 19 },
40   { "Id": 6, "path_fragment_id": 20 },
41   { "Id": 7, "path_fragment_id": 21 }],
42 "Actions": [{
43   "target_id": 1,
44   "action_key": "ab53f6ecbdc2ee8cb8812613b63205464f1f5083f6dca87081a0a398c0f1ecf7",
45   "Mnemonic": "Genrule",
46   "configuration_id": 1,
47   "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py arm ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-arm.S"],
48   "environment_variables": [{
49     "Key": "PATH",
50     "Value": "/bin:/usr/bin:/usr/local/bin"
51   }],
52   "input_dep_set_ids": [1],
53   "output_ids": [4],
54   "primary_output_id": 4
55 }, {
56   "target_id": 2,
57   "action_key": "9f4309ce165dac458498cb92811c18b0b7919782cc37b82a42d2141b8cc90826",
58   "Mnemonic": "Genrule",
59   "configuration_id": 1,
60   "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py x86 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-x86.S"],
61   "environment_variables": [{
62     "Key": "PATH",
63     "Value": "/bin:/usr/bin:/usr/local/bin"
64   }],
65   "input_dep_set_ids": [2],
66   "output_ids": [5],
67   "primary_output_id": 5
68 }, {
69   "target_id": 3,
70   "action_key": "50d6c586103ebeed3a218195540bcc30d329464eae36377eb82f8ce7c36ac342",
71   "Mnemonic": "Genrule",
72   "configuration_id": 1,
73   "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py x86_64 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-x86_64.S"],
74   "environment_variables": [{
75     "Key": "PATH",
76     "Value": "/bin:/usr/bin:/usr/local/bin"
77   }],
78   "input_dep_set_ids": [3],
79   "output_ids": [6],
80   "primary_output_id": 6
81 }, {
82   "target_id": 4,
83   "action_key": "f30cbe442f5216f4223cf16a39112cad4ec56f31f49290d85cff587e48647ffa",
84   "Mnemonic": "Genrule",
85   "configuration_id": 1,
86   "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py arm64 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-arm64.S"],
87   "environment_variables": [{
88     "Key": "PATH",
89     "Value": "/bin:/usr/bin:/usr/local/bin"
90   }],
91   "input_dep_set_ids": [4],
92   "output_ids": [7],
93   "primary_output_id": 7
94 }],
95 "Targets": [
96   { "Id": 1, "Label": "@sourceroot//bionic/libc:syscalls-arm", "rule_class_id": 1 },
97   { "Id": 2, "Label": "@sourceroot//bionic/libc:syscalls-x86", "rule_class_id": 1 },
98   { "Id": 3, "Label": "@sourceroot//bionic/libc:syscalls-x86_64", "rule_class_id": 1 },
99   { "Id": 4, "Label": "@sourceroot//bionic/libc:syscalls-arm64", "rule_class_id": 1 }],
100 "dep_set_of_files": [
101   { "Id": 1, "direct_artifact_ids": [1, 2, 3] },
102   { "Id": 2, "direct_artifact_ids": [1, 2, 3] },
103   { "Id": 3, "direct_artifact_ids": [1, 2, 3] },
104   { "Id": 4, "direct_artifact_ids": [1, 2, 3] }],
105 "Configuration": [{
106   "Id": 1,
107   "Mnemonic": "k8-fastbuild",
108   "platform_name": "k8",
109   "Checksum": "485c362832c178e367d972177f68e69e0981e51e67ef1c160944473db53fe046"
110 }],
111 "rule_classes": [{ "Id": 1, "Name": "genrule"}],
112 "path_fragments": [
113   { "Id": 5, "Label": ".." },
114   { "Id": 4, "Label": "sourceroot", "parent_id": 5 },
115   { "Id": 3, "Label": "bionic", "parent_id": 4 },
116   { "Id": 2, "Label": "libc", "parent_id": 3 },
117   { "Id": 1, "Label": "SYSCALLS.TXT", "parent_id": 2 },
118   { "Id": 7, "Label": "tools", "parent_id": 2 },
119   { "Id": 6, "Label": "gensyscalls.py", "parent_id": 7 },
120   { "Id": 11, "Label": "bazel_tools", "parent_id": 5 },
121   { "Id": 10, "Label": "tools", "parent_id": 11 },
122   { "Id": 9, "Label": "genrule", "parent_id": 10 },
123   { "Id": 8, "Label": "genrule-setup.sh", "parent_id": 9 },
124   { "Id": 18, "Label": "bazel-out" },
125   { "Id": 17, "Label": "sourceroot", "parent_id": 18 },
126   { "Id": 16, "Label": "k8-fastbuild", "parent_id": 17 },
127   { "Id": 15, "Label": "bin", "parent_id": 16 },
128   { "Id": 14, "Label": "bionic", "parent_id": 15 },
129   { "Id": 13, "Label": "libc", "parent_id": 14 },
130   { "Id": 12, "Label": "syscalls-arm.S", "parent_id": 13 },
131   { "Id": 19, "Label": "syscalls-x86.S", "parent_id": 13 },
132   { "Id": 20, "Label": "syscalls-x86_64.S", "parent_id": 13 },
133   { "Id": 21, "Label": "syscalls-arm64.S", "parent_id": 13 }]
134}
135`
136	data, err := JsonToActionGraphContainer(inputString)
137	if err != nil {
138		t.Error(err)
139		return
140	}
141	actualbuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{})
142	var expectedBuildStatements []*BuildStatement
143	for _, arch := range []string{"arm", "arm64", "x86", "x86_64"} {
144		expectedBuildStatements = append(expectedBuildStatements,
145			&BuildStatement{
146				Command: fmt.Sprintf(
147					"/bin/bash -c 'source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py %s ../sourceroot/bionic/libc/SYSCALLS.TXT > bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-%s.S'",
148					arch, arch),
149				OutputPaths: []string{
150					fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-%s.S", arch),
151				},
152				Env: []*analysis_v2_proto.KeyValuePair{
153					{Key: "PATH", Value: "/bin:/usr/bin:/usr/local/bin"},
154				},
155				Mnemonic: "Genrule",
156			})
157	}
158	assertBuildStatements(t, expectedBuildStatements, actualbuildStatements)
159
160	expectedFlattenedInputs := []string{
161		"../sourceroot/bionic/libc/SYSCALLS.TXT",
162		"../sourceroot/bionic/libc/tools/gensyscalls.py",
163	}
164	// In this example, each depset should have the same expected inputs.
165	for _, actualDepset := range actualDepsets {
166		actualFlattenedInputs := flattenDepsets([]string{actualDepset.ContentHash}, actualDepsets)
167		if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
168			t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
169		}
170	}
171}
172
173func TestInvalidOutputId(t *testing.T) {
174	const inputString = `
175{
176 "artifacts": [
177   { "id": 1, "path_fragment_id": 1 },
178   { "id": 2, "path_fragment_id": 2 }],
179 "actions": [{
180   "target_id": 1,
181   "action_key": "action_x",
182   "mnemonic": "X",
183   "arguments": ["touch", "foo"],
184   "input_dep_set_ids": [1],
185   "output_ids": [3],
186   "primary_output_id": 3
187 }],
188 "dep_set_of_files": [
189   { "id": 1, "direct_artifact_ids": [1, 2] }],
190 "path_fragments": [
191   { "id": 1, "label": "one" },
192   { "id": 2, "label": "two" }]
193}`
194
195	data, err := JsonToActionGraphContainer(inputString)
196	if err != nil {
197		t.Error(err)
198		return
199	}
200	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
201	assertError(t, err, "undefined outputId 3: [X] []")
202}
203
204func TestInvalidInputDepsetIdFromAction(t *testing.T) {
205	const inputString = `
206{
207 "artifacts": [
208   { "id": 1, "path_fragment_id": 1 },
209   { "id": 2, "path_fragment_id": 2 }],
210 "actions": [{
211   "target_id": 1,
212   "action_key": "action_x",
213   "mnemonic": "X",
214   "arguments": ["touch", "foo"],
215   "input_dep_set_ids": [2],
216   "output_ids": [1],
217   "primary_output_id": 1
218 }],
219 "targets": [{
220   "id": 1,
221   "label": "target_x"
222 }],
223 "dep_set_of_files": [
224   { "id": 1, "direct_artifact_ids": [1, 2] }],
225 "path_fragments": [
226   { "id": 1, "label": "one" },
227   { "id": 2, "label": "two" }]
228}`
229
230	data, err := JsonToActionGraphContainer(inputString)
231	if err != nil {
232		t.Error(err)
233		return
234	}
235	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
236	assertError(t, err, "undefined (not even empty) input depsetId 2: [X] [target_x]")
237}
238
239func TestInvalidInputDepsetIdFromDepset(t *testing.T) {
240	const inputString = `
241{
242 "artifacts": [
243   { "id": 1, "path_fragment_id": 1 },
244   { "id": 2, "path_fragment_id": 2 }],
245 "actions": [{
246   "target_id": 1,
247   "action_key": "x",
248   "mnemonic": "x",
249   "arguments": ["touch", "foo"],
250   "input_dep_set_ids": [1],
251   "output_ids": [1],
252   "primary_output_id": 1
253 }],
254 "dep_set_of_files": [
255   { "id": 1, "direct_artifact_ids": [1, 2], "transitive_dep_set_ids": [42] }],
256 "path_fragments": [
257   { "id": 1, "label": "one"},
258   { "id": 2, "label": "two" }]
259}`
260
261	data, err := JsonToActionGraphContainer(inputString)
262	if err != nil {
263		t.Error(err)
264		return
265	}
266	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
267	assertError(t, err, "undefined input depsetId 42 (referenced by depsetId 1)")
268}
269
270func TestInvalidInputArtifactId(t *testing.T) {
271	const inputString = `
272{
273 "artifacts": [
274   { "id": 1, "path_fragment_id": 1 },
275   { "id": 2, "path_fragment_id": 2 }],
276 "actions": [{
277   "target_id": 1,
278   "action_key": "x",
279   "mnemonic": "x",
280   "arguments": ["touch", "foo"],
281   "input_dep_set_ids": [1],
282   "output_ids": [1],
283   "primary_output_id": 1
284 }],
285 "dep_set_of_files": [
286   { "id": 1, "direct_artifact_ids": [1, 3] }],
287 "path_fragments": [
288   { "id": 1, "label": "one" },
289   { "id": 2, "label": "two" }]
290}`
291
292	data, err := JsonToActionGraphContainer(inputString)
293	if err != nil {
294		t.Error(err)
295		return
296	}
297	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
298	assertError(t, err, "undefined input artifactId 3")
299}
300
301func TestInvalidPathFragmentId(t *testing.T) {
302	const inputString = `
303{
304 "artifacts": [
305   { "id": 1, "path_fragment_id": 1 },
306   { "id": 2, "path_fragment_id": 2 }],
307 "actions": [{
308   "target_id": 1,
309   "action_key": "x",
310   "mnemonic": "x",
311   "arguments": ["touch", "foo"],
312   "input_dep_set_ids": [1],
313   "output_ids": [1],
314   "primary_output_id": 1
315 }],
316 "dep_set_of_files": [
317    { "id": 1, "direct_artifact_ids": [1, 2] }],
318 "path_fragments": [
319   {  "id": 1, "label": "one" },
320   {  "id": 2, "label": "two", "parent_id": 3 }]
321}`
322
323	data, err := JsonToActionGraphContainer(inputString)
324	if err != nil {
325		t.Error(err)
326		return
327	}
328	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
329	assertError(t, err, "undefined path fragment id 3")
330}
331
332func TestDepfiles(t *testing.T) {
333	const inputString = `
334{
335  "artifacts": [
336    { "id": 1, "path_fragment_id": 1 },
337    { "id": 2, "path_fragment_id": 2 },
338    { "id": 3, "path_fragment_id": 3 }],
339  "actions": [{
340    "target_Id": 1,
341    "action_Key": "x",
342    "mnemonic": "x",
343    "arguments": ["touch", "foo"],
344    "input_dep_set_ids": [1],
345    "output_ids": [2, 3],
346    "primary_output_id": 2
347  }],
348  "dep_set_of_files": [
349    { "id": 1, "direct_Artifact_Ids": [1, 2, 3] }],
350  "path_fragments": [
351    { "id": 1, "label": "one" },
352    { "id": 2, "label": "two" },
353    { "id": 3, "label": "two.d" }]
354}`
355
356	data, err := JsonToActionGraphContainer(inputString)
357	if err != nil {
358		t.Error(err)
359		return
360	}
361	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
362	if err != nil {
363		t.Errorf("Unexpected error %q", err)
364		return
365	}
366	if expected := 1; len(actual) != expected {
367		t.Fatalf("Expected %d build statements, got %d", expected, len(actual))
368		return
369	}
370
371	bs := actual[0]
372	expectedDepfile := "two.d"
373	if bs.Depfile == nil {
374		t.Errorf("Expected depfile %q, but there was none found", expectedDepfile)
375	} else if *bs.Depfile != expectedDepfile {
376		t.Errorf("Expected depfile %q, but got %q", expectedDepfile, *bs.Depfile)
377	}
378}
379
380func TestMultipleDepfiles(t *testing.T) {
381	const inputString = `
382{
383 "artifacts": [
384   { "id": 1, "path_fragment_id": 1 },
385   { "id": 2, "path_fragment_id": 2 },
386   { "id": 3, "path_fragment_id": 3 },
387   { "id": 4, "path_fragment_id": 4 }],
388 "actions": [{
389   "target_id": 1,
390   "action_key": "action_x",
391   "mnemonic": "X",
392   "arguments": ["touch", "foo"],
393   "input_dep_set_ids": [1],
394   "output_ids": [2,3,4],
395   "primary_output_id": 2
396 }],
397 "dep_set_of_files": [{
398   "id": 1,
399   "direct_artifact_ids": [1, 2, 3, 4]
400 }],
401 "path_fragments": [
402   { "id": 1, "label": "one" },
403   { "id": 2, "label": "two" },
404   { "id": 3, "label": "two.d" },
405   { "id": 4, "label": "other.d" }]
406}`
407
408	data, err := JsonToActionGraphContainer(inputString)
409	if err != nil {
410		t.Error(err)
411		return
412	}
413	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
414	assertError(t, err, `found multiple potential depfiles "two.d", "other.d": [X] []`)
415}
416
417func TestTransitiveInputDepsets(t *testing.T) {
418	// The input aquery for this test comes from a proof-of-concept starlark rule which registers
419	// a single action with many inputs given via a deep depset.
420	const inputString = `
421{
422 "artifacts": [
423  { "id": 1, "path_fragment_id": 1 },
424  { "id": 2, "path_fragment_id": 7 },
425  { "id": 3, "path_fragment_id": 8 },
426  { "id": 4, "path_fragment_id": 9 },
427  { "id": 5, "path_fragment_id": 10 },
428  { "id": 6, "path_fragment_id": 11 },
429  { "id": 7, "path_fragment_id": 12 },
430  { "id": 8, "path_fragment_id": 13 },
431  { "id": 9, "path_fragment_id": 14 },
432  { "id": 10, "path_fragment_id": 15 },
433  { "id": 11, "path_fragment_id": 16 },
434  { "id": 12, "path_fragment_id": 17 },
435  { "id": 13, "path_fragment_id": 18 },
436  { "id": 14, "path_fragment_id": 19 },
437  { "id": 15, "path_fragment_id": 20 },
438  { "id": 16, "path_fragment_id": 21 },
439  { "id": 17, "path_fragment_id": 22 },
440  { "id": 18, "path_fragment_id": 23 },
441  { "id": 19, "path_fragment_id": 24 },
442  { "id": 20, "path_fragment_id": 25 },
443  { "id": 21, "path_fragment_id": 26 }],
444 "actions": [{
445   "target_id": 1,
446   "action_key": "3b826d17fadbbbcd8313e456b90ec47c078c438088891dd45b4adbcd8889dc50",
447   "mnemonic": "Action",
448   "configuration_id": 1,
449   "arguments": ["/bin/bash", "-c", "touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"],
450   "input_dep_set_ids": [1],
451   "output_ids": [21],
452   "primary_output_id": 21
453 }],
454 "dep_set_of_files": [
455   { "id": 3, "direct_artifact_ids": [1, 2, 3, 4, 5] },
456   { "id": 4, "direct_artifact_ids": [6, 7, 8, 9, 10] },
457   { "id": 2, "transitive_dep_set_ids": [3, 4], "direct_artifact_ids": [11, 12, 13, 14, 15] },
458   { "id": 5, "direct_artifact_ids": [16, 17, 18, 19] },
459   { "id": 1, "transitive_dep_set_ids": [2, 5], "direct_artifact_ids": [20] }],
460 "path_fragments": [
461   { "id": 6, "label": "bazel-out" },
462   { "id": 5, "label": "sourceroot", "parent_id": 6 },
463   { "id": 4, "label": "k8-fastbuild", "parent_id": 5 },
464   { "id": 3, "label": "bin", "parent_id": 4 },
465   { "id": 2, "label": "testpkg", "parent_id": 3 },
466   { "id": 1, "label": "test_1", "parent_id": 2 },
467   { "id": 7, "label": "test_2", "parent_id": 2 },
468   { "id": 8, "label": "test_3", "parent_id": 2 },
469   { "id": 9, "label": "test_4", "parent_id": 2 },
470   { "id": 10, "label": "test_5", "parent_id": 2 },
471   { "id": 11, "label": "test_6", "parent_id": 2 },
472   { "id": 12, "label": "test_7", "parent_id": 2 },
473	 { "id": 13, "label": "test_8", "parent_id": 2 },
474   { "id": 14, "label": "test_9", "parent_id": 2 },
475   { "id": 15, "label": "test_10", "parent_id": 2 },
476   { "id": 16, "label": "test_11", "parent_id": 2 },
477   { "id": 17, "label": "test_12", "parent_id": 2 },
478   { "id": 18, "label": "test_13", "parent_id": 2 },
479   { "id": 19, "label": "test_14", "parent_id": 2 },
480   { "id": 20, "label": "test_15", "parent_id": 2 },
481   { "id": 21, "label": "test_16", "parent_id": 2 },
482   { "id": 22, "label": "test_17", "parent_id": 2 },
483   { "id": 23, "label": "test_18", "parent_id": 2 },
484   { "id": 24, "label": "test_19", "parent_id": 2 },
485   { "id": 25, "label": "test_root", "parent_id": 2 },
486   { "id": 26,"label": "test_out", "parent_id": 2 }]
487}`
488
489	data, err := JsonToActionGraphContainer(inputString)
490	if err != nil {
491		t.Error(err)
492		return
493	}
494	actualbuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{})
495
496	expectedBuildStatements := []*BuildStatement{
497		&BuildStatement{
498			Command:      "/bin/bash -c 'touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out'",
499			OutputPaths:  []string{"bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"},
500			Mnemonic:     "Action",
501			SymlinkPaths: []string{},
502		},
503	}
504	assertBuildStatements(t, expectedBuildStatements, actualbuildStatements)
505
506	// Inputs for the action are test_{i} from 1 to 20, and test_root. These inputs
507	// are given via a deep depset, but the depset is flattened when returned as a
508	// BuildStatement slice.
509	var expectedFlattenedInputs []string
510	for i := 1; i < 20; i++ {
511		expectedFlattenedInputs = append(expectedFlattenedInputs, fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_%d", i))
512	}
513	expectedFlattenedInputs = append(expectedFlattenedInputs, "bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_root")
514
515	actualDepsetHashes := actualbuildStatements[0].InputDepsetHashes
516	actualFlattenedInputs := flattenDepsets(actualDepsetHashes, actualDepsets)
517	if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
518		t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
519	}
520}
521
522func TestSymlinkTree(t *testing.T) {
523	const inputString = `
524{
525 "artifacts": [
526   { "id": 1, "path_fragment_id": 1 },
527   { "id": 2, "path_fragment_id": 2 }],
528 "actions": [{
529   "target_id": 1,
530   "action_key": "x",
531   "mnemonic": "SymlinkTree",
532   "configuration_id": 1,
533   "input_dep_set_ids": [1],
534   "output_ids": [2],
535   "primary_output_id": 2,
536   "execution_platform": "//build/bazel/platforms:linux_x86_64"
537 }],
538 "path_fragments": [
539   { "id": 1, "label": "foo.manifest" },
540   { "id": 2, "label": "foo.runfiles/MANIFEST" }],
541 "dep_set_of_files": [
542   { "id": 1, "direct_artifact_ids": [1] }]
543}
544`
545	data, err := JsonToActionGraphContainer(inputString)
546	if err != nil {
547		t.Error(err)
548		return
549	}
550	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
551	if err != nil {
552		t.Errorf("Unexpected error %q", err)
553		return
554	}
555	assertBuildStatements(t, []*BuildStatement{
556		&BuildStatement{
557			Command:      "",
558			OutputPaths:  []string{"foo.runfiles/MANIFEST"},
559			Mnemonic:     "SymlinkTree",
560			InputPaths:   []string{"foo.manifest"},
561			SymlinkPaths: []string{},
562		},
563	}, actual)
564}
565
566func TestBazelToolsRemovalFromInputDepsets(t *testing.T) {
567	const inputString = `{
568 "artifacts": [
569   { "id": 1, "path_fragment_id": 10 },
570   { "id": 2, "path_fragment_id": 20 },
571   { "id": 3, "path_fragment_id": 30 },
572   { "id": 4, "path_fragment_id": 40 }],
573 "dep_set_of_files": [{
574   "id": 1111,
575   "direct_artifact_ids": [3 , 4]
576 }, {
577   "id": 2222,
578   "direct_artifact_ids": [3]
579 }],
580 "actions": [{
581   "target_id": 100,
582   "action_key": "x",
583   "input_dep_set_ids": [1111, 2222],
584   "mnemonic": "x",
585   "arguments": ["bogus", "command"],
586   "output_ids": [2],
587   "primary_output_id": 1
588 }],
589 "path_fragments": [
590   { "id": 10, "label": "input" },
591   { "id": 20, "label": "output" },
592   { "id": 30, "label": "dep1", "parent_id": 50 },
593   { "id": 40, "label": "dep2", "parent_id": 60 },
594   { "id": 50, "label": "bazel_tools", "parent_id": 60 },
595   { "id": 60, "label": ".."}
596 ]
597}`
598	/* depsets
599	       1111  2222
600	       /  \   |
601	../dep2    ../bazel_tools/dep1
602	*/
603	data, err := JsonToActionGraphContainer(inputString)
604	if err != nil {
605		t.Error(err)
606		return
607	}
608	actualBuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{})
609	if len(actualDepsets) != 1 {
610		t.Errorf("expected 1 depset but found %#v", actualDepsets)
611		return
612	}
613	dep2Found := false
614	for _, dep := range flattenDepsets([]string{actualDepsets[0].ContentHash}, actualDepsets) {
615		if dep == "../bazel_tools/dep1" {
616			t.Errorf("dependency %s expected to be removed but still exists", dep)
617		} else if dep == "../dep2" {
618			dep2Found = true
619		}
620	}
621	if !dep2Found {
622		t.Errorf("dependency ../dep2 expected but not found")
623	}
624
625	expectedBuildStatement := &BuildStatement{
626		Command:      "bogus command",
627		OutputPaths:  []string{"output"},
628		Mnemonic:     "x",
629		SymlinkPaths: []string{},
630	}
631	buildStatementFound := false
632	for _, actualBuildStatement := range actualBuildStatements {
633		if buildStatementEquals(actualBuildStatement, expectedBuildStatement) == "" {
634			buildStatementFound = true
635			break
636		}
637	}
638	if !buildStatementFound {
639		t.Errorf("expected but missing %#v in %#v", expectedBuildStatement, actualBuildStatements)
640		return
641	}
642}
643
644func TestBazelToolsRemovalFromTargets(t *testing.T) {
645	const inputString = `{
646 "artifacts": [{ "id": 1, "path_fragment_id": 10 }],
647 "targets": [
648   { "id": 100, "label": "targetX" },
649   { "id": 200, "label": "@bazel_tools//tool_y" }
650],
651 "actions": [{
652   "target_id": 100,
653   "action_key": "actionX",
654   "arguments": ["bogus", "command"],
655   "mnemonic" : "x",
656   "output_ids": [1]
657 }, {
658   "target_id": 200,
659   "action_key": "y"
660 }],
661 "path_fragments": [{ "id": 10, "label": "outputX"}]
662}`
663	data, err := JsonToActionGraphContainer(inputString)
664	if err != nil {
665		t.Error(err)
666		return
667	}
668	actualBuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{})
669	if len(actualDepsets) != 0 {
670		t.Errorf("expected 0 depset but found %#v", actualDepsets)
671		return
672	}
673	expectedBuildStatement := &BuildStatement{
674		Command:      "bogus command",
675		OutputPaths:  []string{"outputX"},
676		Mnemonic:     "x",
677		SymlinkPaths: []string{},
678	}
679	buildStatementFound := false
680	for _, actualBuildStatement := range actualBuildStatements {
681		if buildStatementEquals(actualBuildStatement, expectedBuildStatement) == "" {
682			buildStatementFound = true
683			break
684		}
685	}
686	if !buildStatementFound {
687		t.Errorf("expected but missing %#v in %#v build statements", expectedBuildStatement, len(actualBuildStatements))
688		return
689	}
690}
691
692func TestBazelToolsRemovalFromTransitiveInputDepsets(t *testing.T) {
693	const inputString = `{
694 "artifacts": [
695   { "id": 1, "path_fragment_id": 10 },
696   { "id": 2, "path_fragment_id": 20 },
697   { "id": 3, "path_fragment_id": 30 }],
698 "dep_set_of_files": [{
699   "id": 1111,
700   "transitive_dep_set_ids": [2222]
701 }, {
702   "id": 2222,
703   "direct_artifact_ids": [3]
704 }, {
705   "id": 3333,
706   "direct_artifact_ids": [3]
707 }, {
708   "id": 4444,
709   "transitive_dep_set_ids": [3333]
710 }],
711 "actions": [{
712   "target_id": 100,
713   "action_key": "x",
714   "input_dep_set_ids": [1111, 4444],
715   "mnemonic": "x",
716   "arguments": ["bogus", "command"],
717   "output_ids": [2],
718   "primary_output_id": 1
719 }],
720 "path_fragments": [
721   { "id": 10, "label": "input" },
722   { "id": 20, "label": "output" },
723   { "id": 30, "label": "dep", "parent_id": 50 },
724   { "id": 50, "label": "bazel_tools", "parent_id": 60 },
725   { "id": 60, "label": ".."}
726 ]
727}`
728	/* depsets
729	    1111    4444
730	     ||      ||
731	    2222    3333
732	      |      |
733	../bazel_tools/dep
734	Note: in dep_set_of_files:
735	  1111 appears BEFORE its dependency,2222 while
736	  4444 appears AFTER its dependency 3333
737	and this test shows that that order doesn't affect empty depset pruning
738	*/
739	data, err := JsonToActionGraphContainer(inputString)
740	if err != nil {
741		t.Error(err)
742		return
743	}
744	actualBuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{})
745	if len(actualDepsets) != 0 {
746		t.Errorf("expected 0 depsets but found %#v", actualDepsets)
747		return
748	}
749
750	expectedBuildStatement := &BuildStatement{
751		Command:     "bogus command",
752		OutputPaths: []string{"output"},
753		Mnemonic:    "x",
754	}
755	buildStatementFound := false
756	for _, actualBuildStatement := range actualBuildStatements {
757		if buildStatementEquals(actualBuildStatement, expectedBuildStatement) == "" {
758			buildStatementFound = true
759			break
760		}
761	}
762	if !buildStatementFound {
763		t.Errorf("expected but missing %#v in %#v", expectedBuildStatement, actualBuildStatements)
764		return
765	}
766}
767
768func TestMiddlemenAction(t *testing.T) {
769	const inputString = `
770{
771 "artifacts": [
772   { "id": 1, "path_fragment_id": 1 },
773   { "id": 2, "path_fragment_id": 2 },
774   { "id": 3, "path_fragment_id": 3 },
775   { "id": 4, "path_fragment_id": 4 },
776   { "id": 5, "path_fragment_id": 5 },
777   { "id": 6, "path_fragment_id": 6 }],
778 "path_fragments": [
779   { "id": 1, "label": "middleinput_one" },
780   { "id": 2, "label": "middleinput_two" },
781   { "id": 3, "label": "middleman_artifact" },
782   { "id": 4, "label": "maininput_one" },
783   { "id": 5, "label": "maininput_two" },
784   { "id": 6, "label": "output" }],
785 "dep_set_of_files": [
786   { "id": 1, "direct_artifact_ids": [1, 2] },
787   { "id": 2, "direct_artifact_ids": [3, 4, 5] }],
788 "actions": [{
789   "target_id": 1,
790   "action_key": "x",
791   "mnemonic": "Middleman",
792   "arguments": ["touch", "foo"],
793   "input_dep_set_ids": [1],
794   "output_ids": [3],
795   "primary_output_id": 3
796 }, {
797   "target_id": 2,
798   "action_key": "y",
799   "mnemonic": "Main action",
800   "arguments": ["touch", "foo"],
801   "input_dep_set_ids": [2],
802   "output_ids": [6],
803   "primary_output_id": 6
804 }]
805}`
806	data, err := JsonToActionGraphContainer(inputString)
807	if err != nil {
808		t.Error(err)
809		return
810	}
811	actualBuildStatements, actualDepsets, err := AqueryBuildStatements(data, &metrics.EventHandler{})
812	if err != nil {
813		t.Errorf("Unexpected error %q", err)
814		return
815	}
816	if expected := 2; len(actualBuildStatements) != expected {
817		t.Fatalf("Expected %d build statements, got %d %#v", expected, len(actualBuildStatements), actualBuildStatements)
818		return
819	}
820
821	expectedDepsetFiles := [][]string{
822		{"middleinput_one", "middleinput_two", "maininput_one", "maininput_two"},
823		{"middleinput_one", "middleinput_two"},
824	}
825	assertFlattenedDepsets(t, actualDepsets, expectedDepsetFiles)
826
827	bs := actualBuildStatements[0]
828	if len(bs.InputPaths) > 0 {
829		t.Errorf("Expected main action raw inputs to be empty, but got %q", bs.InputPaths)
830	}
831
832	expectedOutputs := []string{"output"}
833	if !reflect.DeepEqual(bs.OutputPaths, expectedOutputs) {
834		t.Errorf("Expected main action outputs %q, but got %q", expectedOutputs, bs.OutputPaths)
835	}
836
837	expectedFlattenedInputs := []string{"middleinput_one", "middleinput_two", "maininput_one", "maininput_two"}
838	actualFlattenedInputs := flattenDepsets(bs.InputDepsetHashes, actualDepsets)
839
840	if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
841		t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
842	}
843
844	bs = actualBuildStatements[1]
845	if bs != nil {
846		t.Errorf("Expected nil action for skipped")
847	}
848}
849
850// Returns the contents of given depsets in concatenated post order.
851func flattenDepsets(depsetHashesToFlatten []string, allDepsets []AqueryDepset) []string {
852	depsetsByHash := map[string]AqueryDepset{}
853	for _, depset := range allDepsets {
854		depsetsByHash[depset.ContentHash] = depset
855	}
856	var result []string
857	for _, depsetId := range depsetHashesToFlatten {
858		result = append(result, flattenDepset(depsetId, depsetsByHash)...)
859	}
860	return result
861}
862
863// Returns the contents of a given depset in post order.
864func flattenDepset(depsetHashToFlatten string, allDepsets map[string]AqueryDepset) []string {
865	depset := allDepsets[depsetHashToFlatten]
866	var result []string
867	for _, depsetId := range depset.TransitiveDepSetHashes {
868		result = append(result, flattenDepset(depsetId, allDepsets)...)
869	}
870	result = append(result, depset.DirectArtifacts...)
871	return result
872}
873
874func assertFlattenedDepsets(t *testing.T, actualDepsets []AqueryDepset, expectedDepsetFiles [][]string) {
875	t.Helper()
876	if len(actualDepsets) != len(expectedDepsetFiles) {
877		t.Errorf("Expected %d depsets, but got %d depsets", len(expectedDepsetFiles), len(actualDepsets))
878	}
879	for i, actualDepset := range actualDepsets {
880		actualFlattenedInputs := flattenDepsets([]string{actualDepset.ContentHash}, actualDepsets)
881		if !reflect.DeepEqual(actualFlattenedInputs, expectedDepsetFiles[i]) {
882			t.Errorf("Expected depset files: %v, but got %v", expectedDepsetFiles[i], actualFlattenedInputs)
883		}
884	}
885}
886
887func TestSimpleSymlink(t *testing.T) {
888	const inputString = `
889{
890 "artifacts": [
891   { "id": 1, "path_fragment_id": 3 },
892   { "id": 2, "path_fragment_id": 5 }],
893 "actions": [{
894   "target_id": 1,
895   "action_key": "x",
896   "mnemonic": "Symlink",
897   "input_dep_set_ids": [1],
898   "output_ids": [2],
899   "primary_output_id": 2
900 }],
901 "dep_set_of_files": [
902   { "id": 1, "direct_artifact_ids": [1] }],
903 "path_fragments": [
904   { "id": 1, "label": "one" },
905   { "id": 2, "label": "file_subdir", "parent_id": 1 },
906   { "id": 3, "label": "file", "parent_id": 2 },
907   { "id": 4, "label": "symlink_subdir", "parent_id": 1 },
908   { "id": 5, "label": "symlink", "parent_id": 4 }]
909}`
910	data, err := JsonToActionGraphContainer(inputString)
911	if err != nil {
912		t.Error(err)
913		return
914	}
915	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
916
917	if err != nil {
918		t.Errorf("Unexpected error %q", err)
919		return
920	}
921
922	expectedBuildStatements := []*BuildStatement{
923		&BuildStatement{
924			Command: "mkdir -p one/symlink_subdir && " +
925				"rm -f one/symlink_subdir/symlink && " +
926				"ln -sf $PWD/one/file_subdir/file one/symlink_subdir/symlink",
927			InputPaths:   []string{"one/file_subdir/file"},
928			OutputPaths:  []string{"one/symlink_subdir/symlink"},
929			SymlinkPaths: []string{"one/symlink_subdir/symlink"},
930			Mnemonic:     "Symlink",
931		},
932	}
933	assertBuildStatements(t, actual, expectedBuildStatements)
934}
935
936func TestSymlinkQuotesPaths(t *testing.T) {
937	const inputString = `
938{
939 "artifacts": [
940   { "id": 1, "path_fragment_id": 3 },
941   { "id": 2, "path_fragment_id": 5 }],
942 "actions": [{
943   "target_id": 1,
944   "action_key": "x",
945   "mnemonic": "SolibSymlink",
946   "input_dep_set_ids": [1],
947   "output_ids": [2],
948   "primary_output_id": 2
949 }],
950 "dep_set_of_files": [
951   { "id": 1, "direct_artifact_ids": [1] }],
952 "path_fragments": [
953   { "id": 1, "label": "one" },
954   { "id": 2, "label": "file subdir", "parent_id": 1 },
955   { "id": 3, "label": "file", "parent_id": 2 },
956   { "id": 4, "label": "symlink subdir", "parent_id": 1 },
957   { "id": 5, "label": "symlink", "parent_id": 4 }]
958}`
959
960	data, err := JsonToActionGraphContainer(inputString)
961	if err != nil {
962		t.Error(err)
963		return
964	}
965	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
966	if err != nil {
967		t.Errorf("Unexpected error %q", err)
968		return
969	}
970
971	expectedBuildStatements := []*BuildStatement{
972		&BuildStatement{
973			Command: "mkdir -p 'one/symlink subdir' && " +
974				"rm -f 'one/symlink subdir/symlink' && " +
975				"ln -sf $PWD/'one/file subdir/file' 'one/symlink subdir/symlink'",
976			InputPaths:   []string{"one/file subdir/file"},
977			OutputPaths:  []string{"one/symlink subdir/symlink"},
978			SymlinkPaths: []string{"one/symlink subdir/symlink"},
979			Mnemonic:     "SolibSymlink",
980		},
981	}
982	assertBuildStatements(t, expectedBuildStatements, actual)
983}
984
985func TestSymlinkMultipleInputs(t *testing.T) {
986	const inputString = `
987{
988 "artifacts": [
989   { "id": 1, "path_fragment_id": 1 },
990   { "id": 2, "path_fragment_id": 2 },
991   { "id": 3, "path_fragment_id": 3 }],
992 "actions": [{
993   "target_id": 1,
994   "action_key": "action_x",
995   "mnemonic": "Symlink",
996   "input_dep_set_ids": [1],
997   "output_ids": [3],
998   "primary_output_id": 3
999 }],
1000 "dep_set_of_files": [{ "id": 1, "direct_artifact_ids": [1,2] }],
1001 "path_fragments": [
1002   { "id": 1, "label": "file" },
1003   { "id": 2, "label": "other_file" },
1004   { "id": 3, "label": "symlink" }]
1005}`
1006
1007	data, err := JsonToActionGraphContainer(inputString)
1008	if err != nil {
1009		t.Error(err)
1010		return
1011	}
1012	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
1013	assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file" "other_file"], output ["symlink"]: [Symlink] []`)
1014}
1015
1016func TestSymlinkMultipleOutputs(t *testing.T) {
1017	const inputString = `
1018{
1019 "artifacts": [
1020   { "id": 1, "path_fragment_id": 1 },
1021   { "id": 3, "path_fragment_id": 3 }],
1022 "actions": [{
1023   "target_id": 1,
1024   "action_key": "x",
1025   "mnemonic": "Symlink",
1026   "input_dep_set_ids": [1],
1027   "output_ids": [2,3],
1028   "primary_output_id": 2
1029 }],
1030 "dep_set_of_files": [
1031   { "id": 1, "direct_artifact_ids": [1] }],
1032 "path_fragments": [
1033   { "id": 1, "label": "file" },
1034   { "id": 2, "label": "symlink" },
1035   { "id": 3,  "label": "other_symlink" }]
1036}`
1037
1038	data, err := JsonToActionGraphContainer(inputString)
1039	if err != nil {
1040		t.Error(err)
1041		return
1042	}
1043	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
1044	assertError(t, err, "undefined outputId 2: [Symlink] []")
1045}
1046
1047func TestTemplateExpandActionSubstitutions(t *testing.T) {
1048	const inputString = `
1049{
1050 "artifacts": [{
1051   "id": 1,
1052   "path_fragment_id": 1
1053 }],
1054 "actions": [{
1055   "target_id": 1,
1056   "action_key": "x",
1057   "mnemonic": "TemplateExpand",
1058   "configuration_id": 1,
1059   "output_ids": [1],
1060   "primary_output_id": 1,
1061   "execution_platform": "//build/bazel/platforms:linux_x86_64",
1062   "template_content": "Test template substitutions: %token1%, %python_binary%",
1063   "substitutions": [
1064     { "key": "%token1%", "value": "abcd" },
1065     { "key": "%python_binary%", "value": "python3" }]
1066 }],
1067 "path_fragments": [
1068   { "id": 1, "label": "template_file" }]
1069}`
1070
1071	data, err := JsonToActionGraphContainer(inputString)
1072	if err != nil {
1073		t.Error(err)
1074		return
1075	}
1076	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
1077	if err != nil {
1078		t.Errorf("Unexpected error %q", err)
1079		return
1080	}
1081
1082	expectedBuildStatements := []*BuildStatement{
1083		&BuildStatement{
1084			Command: "/bin/bash -c 'echo \"Test template substitutions: abcd, python3\" | sed \"s/\\\\\\\\n/\\\\n/g\" > template_file && " +
1085				"chmod a+x template_file'",
1086			OutputPaths:  []string{"template_file"},
1087			Mnemonic:     "TemplateExpand",
1088			SymlinkPaths: []string{},
1089		},
1090	}
1091	assertBuildStatements(t, expectedBuildStatements, actual)
1092}
1093
1094func TestTemplateExpandActionNoOutput(t *testing.T) {
1095	const inputString = `
1096{
1097 "artifacts": [
1098   { "id": 1, "path_fragment_id": 1 }],
1099 "actions": [{
1100   "target_id": 1,
1101   "action_key": "x",
1102   "mnemonic": "TemplateExpand",
1103   "configuration_id": 1,
1104   "primary_output_id": 1,
1105   "execution_platform": "//build/bazel/platforms:linux_x86_64",
1106   "templateContent": "Test template substitutions: %token1%, %python_binary%",
1107   "substitutions": [
1108     { "key": "%token1%", "value": "abcd" },
1109     { "key": "%python_binary%", "value": "python3" }]
1110 }],
1111 "path_fragments": [
1112   { "id": 1, "label": "template_file" }]
1113}`
1114
1115	data, err := JsonToActionGraphContainer(inputString)
1116	if err != nil {
1117		t.Error(err)
1118		return
1119	}
1120	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
1121	assertError(t, err, `Expect 1 output to template expand action, got: output []: [TemplateExpand] []`)
1122}
1123
1124func TestFileWrite(t *testing.T) {
1125	const inputString = `
1126{
1127 "artifacts": [
1128   { "id": 1, "path_fragment_id": 1 }],
1129 "actions": [{
1130   "target_id": 1,
1131   "action_key": "x",
1132   "mnemonic": "FileWrite",
1133   "configuration_id": 1,
1134   "output_ids": [1],
1135   "primary_output_id": 1,
1136   "execution_platform": "//build/bazel/platforms:linux_x86_64",
1137   "file_contents": "file data\n"
1138 }],
1139 "path_fragments": [
1140   { "id": 1, "label": "foo.manifest" }]
1141}
1142`
1143	data, err := JsonToActionGraphContainer(inputString)
1144	if err != nil {
1145		t.Error(err)
1146		return
1147	}
1148	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
1149	if err != nil {
1150		t.Errorf("Unexpected error %q", err)
1151		return
1152	}
1153	assertBuildStatements(t, []*BuildStatement{
1154		&BuildStatement{
1155			OutputPaths:  []string{"foo.manifest"},
1156			Mnemonic:     "FileWrite",
1157			FileContents: "file data\n",
1158			SymlinkPaths: []string{},
1159		},
1160	}, actual)
1161}
1162
1163func TestSourceSymlinkManifest(t *testing.T) {
1164	const inputString = `
1165{
1166 "artifacts": [
1167   { "id": 1, "path_fragment_id": 1 }],
1168 "actions": [{
1169   "target_id": 1,
1170   "action_key": "x",
1171   "mnemonic": "SourceSymlinkManifest",
1172   "configuration_id": 1,
1173   "output_ids": [1],
1174   "primary_output_id": 1,
1175   "execution_platform": "//build/bazel/platforms:linux_x86_64",
1176   "file_contents": "symlink target\n"
1177 }],
1178 "path_fragments": [
1179   { "id": 1, "label": "foo.manifest" }]
1180}
1181`
1182	data, err := JsonToActionGraphContainer(inputString)
1183	if err != nil {
1184		t.Error(err)
1185		return
1186	}
1187	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
1188	if err != nil {
1189		t.Errorf("Unexpected error %q", err)
1190		return
1191	}
1192	assertBuildStatements(t, []*BuildStatement{
1193		&BuildStatement{
1194			OutputPaths:  []string{"foo.manifest"},
1195			Mnemonic:     "SourceSymlinkManifest",
1196			SymlinkPaths: []string{},
1197		},
1198	}, actual)
1199}
1200
1201func TestUnresolvedSymlink(t *testing.T) {
1202	const inputString = `
1203{
1204 "artifacts": [
1205   { "id": 1, "path_fragment_id": 1 }
1206 ],
1207 "actions": [{
1208   "target_id": 1,
1209   "action_key": "x",
1210   "mnemonic": "UnresolvedSymlink",
1211   "configuration_id": 1,
1212   "output_ids": [1],
1213   "primary_output_id": 1,
1214   "execution_platform": "//build/bazel/platforms:linux_x86_64",
1215   "unresolved_symlink_target": "symlink/target"
1216 }],
1217 "path_fragments": [
1218   { "id": 1, "label": "path/to/symlink" }
1219 ]
1220}
1221`
1222	data, err := JsonToActionGraphContainer(inputString)
1223	if err != nil {
1224		t.Error(err)
1225		return
1226	}
1227	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
1228	if err != nil {
1229		t.Errorf("Unexpected error %q", err)
1230		return
1231	}
1232	assertBuildStatements(t, []*BuildStatement{{
1233		Command:      "mkdir -p path/to && rm -f path/to/symlink && ln -sf symlink/target path/to/symlink",
1234		OutputPaths:  []string{"path/to/symlink"},
1235		Mnemonic:     "UnresolvedSymlink",
1236		SymlinkPaths: []string{"path/to/symlink"},
1237	}}, actual)
1238}
1239
1240func TestUnresolvedSymlinkBazelSandwich(t *testing.T) {
1241	const inputString = `
1242{
1243 "artifacts": [
1244   { "id": 1, "path_fragment_id": 1 }
1245 ],
1246 "actions": [{
1247   "target_id": 1,
1248   "action_key": "x",
1249   "mnemonic": "UnresolvedSymlink",
1250   "configuration_id": 1,
1251   "output_ids": [1],
1252   "primary_output_id": 1,
1253   "execution_platform": "//build/bazel/platforms:linux_x86_64",
1254   "unresolved_symlink_target": "bazel_sandwich:{\"target\":\"target/product/emulator_x86_64/system\"}"
1255 }],
1256 "path_fragments": [
1257   { "id": 1, "label": "path/to/symlink" }
1258 ]
1259}
1260`
1261	data, err := JsonToActionGraphContainer(inputString)
1262	if err != nil {
1263		t.Error(err)
1264		return
1265	}
1266	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
1267	if err != nil {
1268		t.Errorf("Unexpected error %q", err)
1269		return
1270	}
1271	assertBuildStatements(t, []*BuildStatement{{
1272		Command:      "mkdir -p path/to && rm -f path/to/symlink && ln -sf {DOTDOTS_TO_OUTPUT_ROOT}../../target/product/emulator_x86_64/system path/to/symlink",
1273		OutputPaths:  []string{"path/to/symlink"},
1274		Mnemonic:     "UnresolvedSymlink",
1275		SymlinkPaths: []string{"path/to/symlink"},
1276		ImplicitDeps: []string{"target/product/emulator_x86_64/system"},
1277	}}, actual)
1278}
1279
1280func TestUnresolvedSymlinkBazelSandwichWithAlternativeDeps(t *testing.T) {
1281	const inputString = `
1282{
1283 "artifacts": [
1284   { "id": 1, "path_fragment_id": 1 }
1285 ],
1286 "actions": [{
1287   "target_id": 1,
1288   "action_key": "x",
1289   "mnemonic": "UnresolvedSymlink",
1290   "configuration_id": 1,
1291   "output_ids": [1],
1292   "primary_output_id": 1,
1293   "execution_platform": "//build/bazel/platforms:linux_x86_64",
1294   "unresolved_symlink_target": "bazel_sandwich:{\"depend_on_target\":false,\"implicit_deps\":[\"target/product/emulator_x86_64/obj/PACKAGING/systemimage_intermediates/staging_dir.stamp\"],\"target\":\"target/product/emulator_x86_64/system\"}"
1295 }],
1296 "path_fragments": [
1297   { "id": 1, "label": "path/to/symlink" }
1298 ]
1299}
1300`
1301	data, err := JsonToActionGraphContainer(inputString)
1302	if err != nil {
1303		t.Error(err)
1304		return
1305	}
1306	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
1307	if err != nil {
1308		t.Errorf("Unexpected error %q", err)
1309		return
1310	}
1311	assertBuildStatements(t, []*BuildStatement{{
1312		Command:      "mkdir -p path/to && rm -f path/to/symlink && ln -sf {DOTDOTS_TO_OUTPUT_ROOT}../../target/product/emulator_x86_64/system path/to/symlink",
1313		OutputPaths:  []string{"path/to/symlink"},
1314		Mnemonic:     "UnresolvedSymlink",
1315		SymlinkPaths: []string{"path/to/symlink"},
1316		// Note that the target of the symlink, target/product/emulator_x86_64/system, is not listed here
1317		ImplicitDeps: []string{"target/product/emulator_x86_64/obj/PACKAGING/systemimage_intermediates/staging_dir.stamp"},
1318	}}, actual)
1319}
1320
1321func assertError(t *testing.T, err error, expected string) {
1322	t.Helper()
1323	if err == nil {
1324		t.Errorf("expected error '%s', but got no error", expected)
1325	} else if err.Error() != expected {
1326		t.Errorf("expected error:\n\t'%s', but got:\n\t'%s'", expected, err.Error())
1327	}
1328}
1329
1330// Asserts that the given actual build statements match the given expected build statements.
1331// Build statement equivalence is determined using buildStatementEquals.
1332func assertBuildStatements(t *testing.T, expected []*BuildStatement, actual []*BuildStatement) {
1333	t.Helper()
1334	if len(expected) != len(actual) {
1335		t.Errorf("expected %d build statements, but got %d,\n expected: %#v,\n actual: %#v",
1336			len(expected), len(actual), expected, actual)
1337		return
1338	}
1339	type compareFn = func(i int, j int) bool
1340	byCommand := func(slice []*BuildStatement) compareFn {
1341		return func(i int, j int) bool {
1342			if slice[i] == nil {
1343				return false
1344			} else if slice[j] == nil {
1345				return false
1346			}
1347			return slice[i].Command < slice[j].Command
1348		}
1349	}
1350	sort.SliceStable(expected, byCommand(expected))
1351	sort.SliceStable(actual, byCommand(actual))
1352	for i, actualStatement := range actual {
1353		expectedStatement := expected[i]
1354		if differingField := buildStatementEquals(actualStatement, expectedStatement); differingField != "" {
1355			t.Errorf("%s differs\nunexpected build statement %#v.\nexpected: %#v",
1356				differingField, actualStatement, expectedStatement)
1357			return
1358		}
1359	}
1360}
1361
1362func buildStatementEquals(first *BuildStatement, second *BuildStatement) string {
1363	if (first == nil) != (second == nil) {
1364		return "Nil"
1365	}
1366	if first.Mnemonic != second.Mnemonic {
1367		return "Mnemonic"
1368	}
1369	if first.Command != second.Command {
1370		return "Command"
1371	}
1372	// Ordering is significant for environment variables.
1373	if !reflect.DeepEqual(first.Env, second.Env) {
1374		return "Env"
1375	}
1376	// Ordering is irrelevant for input and output paths, so compare sets.
1377	if !reflect.DeepEqual(sortedStrings(first.InputPaths), sortedStrings(second.InputPaths)) {
1378		return "InputPaths"
1379	}
1380	if !reflect.DeepEqual(sortedStrings(first.OutputPaths), sortedStrings(second.OutputPaths)) {
1381		return "OutputPaths"
1382	}
1383	if !reflect.DeepEqual(sortedStrings(first.SymlinkPaths), sortedStrings(second.SymlinkPaths)) {
1384		return "SymlinkPaths"
1385	}
1386	if !reflect.DeepEqual(sortedStrings(first.ImplicitDeps), sortedStrings(second.ImplicitDeps)) {
1387		return "ImplicitDeps"
1388	}
1389	if first.Depfile != second.Depfile {
1390		return "Depfile"
1391	}
1392	return ""
1393}
1394
1395func sortedStrings(stringSlice []string) []string {
1396	sorted := make([]string, len(stringSlice))
1397	copy(sorted, stringSlice)
1398	sort.Strings(sorted)
1399	return sorted
1400}
1401
1402// Transform the json format to ActionGraphContainer
1403func JsonToActionGraphContainer(inputString string) ([]byte, error) {
1404	var aqueryProtoResult analysis_v2_proto.ActionGraphContainer
1405	err := json.Unmarshal([]byte(inputString), &aqueryProtoResult)
1406	if err != nil {
1407		return []byte(""), err
1408	}
1409	data, _ := proto.Marshal(&aqueryProtoResult)
1410	return data, err
1411}
1412