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 rust
16
17import (
18	"encoding/json"
19	"io/ioutil"
20	"path/filepath"
21	"sort"
22	"strings"
23	"testing"
24
25	"android/soong/android"
26)
27
28// testProjectJson run the generation of rust-project.json. It returns the raw
29// content of the generated file.
30func testProjectJson(t *testing.T, bp string) []byte {
31	result := android.GroupFixturePreparers(
32		prepareForRustTest,
33		android.FixtureMergeEnv(map[string]string{"SOONG_GEN_RUST_PROJECT": "1"}),
34	).RunTestWithBp(t, bp)
35
36	// The JSON file is generated via WriteFileToOutputDir. Therefore, it
37	// won't appear in the Output of the TestingSingleton. Manually verify
38	// it exists.
39	content, err := ioutil.ReadFile(filepath.Join(result.Config.SoongOutDir(), rustProjectJsonFileName))
40	if err != nil {
41		t.Errorf("rust-project.json has not been generated")
42	}
43	return content
44}
45
46// validateJsonCrates validates that content follows the basic structure of
47// rust-project.json. It returns the crates attribute if the validation
48// succeeded.
49// It uses an empty interface instead of relying on a defined structure to
50// avoid a strong dependency on our implementation.
51func validateJsonCrates(t *testing.T, rawContent []byte) []interface{} {
52	var content interface{}
53	err := json.Unmarshal(rawContent, &content)
54	if err != nil {
55		t.Errorf("Unable to parse the rust-project.json as JSON: %v", err)
56	}
57	root, ok := content.(map[string]interface{})
58	if !ok {
59		t.Errorf("Unexpected JSON format: %v", content)
60	}
61	if _, ok = root["crates"]; !ok {
62		t.Errorf("No crates attribute in rust-project.json: %v", root)
63	}
64	crates, ok := root["crates"].([]interface{})
65	if !ok {
66		t.Errorf("Unexpected crates format: %v", root["crates"])
67	}
68	return crates
69}
70
71// validateCrate ensures that a crate can be parsed as a map.
72func validateCrate(t *testing.T, crate interface{}) map[string]interface{} {
73	c, ok := crate.(map[string]interface{})
74	if !ok {
75		t.Fatalf("Unexpected type for crate: %v", c)
76	}
77	return c
78}
79
80// validateDependencies parses the dependencies for a crate. It returns a list
81// of the dependencies name.
82func validateDependencies(t *testing.T, crate map[string]interface{}) []string {
83	var dependencies []string
84	deps, ok := crate["deps"].([]interface{})
85	if !ok {
86		t.Errorf("Unexpected format for deps: %v", crate["deps"])
87	}
88	for _, dep := range deps {
89		d, ok := dep.(map[string]interface{})
90		if !ok {
91			t.Errorf("Unexpected format for dependency: %v", dep)
92		}
93		name, ok := d["name"].(string)
94		if !ok {
95			t.Errorf("Dependency is missing the name key: %v", d)
96		}
97		dependencies = append(dependencies, name)
98	}
99	return dependencies
100}
101
102func TestProjectJsonDep(t *testing.T) {
103	bp := `
104	rust_library {
105		name: "liba",
106		srcs: ["a/src/lib.rs"],
107		crate_name: "a"
108	}
109	rust_library {
110		name: "libb",
111		srcs: ["b/src/lib.rs"],
112		crate_name: "b",
113		rlibs: ["liba"],
114	}
115	`
116	jsonContent := testProjectJson(t, bp)
117	validateJsonCrates(t, jsonContent)
118}
119
120func TestProjectJsonProcMacroDep(t *testing.T) {
121	bp := `
122	rust_proc_macro {
123		name: "libproc_macro",
124		srcs: ["a/src/lib.rs"],
125		crate_name: "proc_macro"
126	}
127	rust_library {
128		name: "librust",
129		srcs: ["b/src/lib.rs"],
130		crate_name: "rust",
131		proc_macros: ["libproc_macro"],
132	}
133	`
134	jsonContent := testProjectJson(t, bp)
135	crates := validateJsonCrates(t, jsonContent)
136	libproc_macro_count := 0
137	librust_count := 0
138	for _, c := range crates {
139		crate := validateCrate(t, c)
140		procMacro, ok := crate["is_proc_macro"].(bool)
141		if !ok {
142			t.Fatalf("Unexpected type for is_proc_macro: %v", crate["is_proc_macro"])
143		}
144
145		name, ok := crate["display_name"].(string)
146		if !ok {
147			t.Fatalf("Unexpected type for display_name: %v", crate["display_name"])
148		}
149
150		switch name {
151		case "libproc_macro":
152			libproc_macro_count += 1
153			if !procMacro {
154				t.Fatalf("'libproc_macro' is marked with is_proc_macro=false")
155			}
156		case "librust":
157			librust_count += 1
158			if procMacro {
159				t.Fatalf("'librust' is not a proc macro crate, but is marked with is_proc_macro=true")
160			}
161		default:
162			break
163		}
164	}
165
166	if libproc_macro_count != 1 || librust_count != 1 {
167		t.Fatalf("Unexpected crate counts: libproc_macro_count: %v, librust_count: %v",
168			libproc_macro_count, librust_count)
169	}
170}
171
172func TestProjectJsonFeature(t *testing.T) {
173	bp := `
174	rust_library {
175		name: "liba",
176		srcs: ["a/src/lib.rs"],
177		crate_name: "a",
178		features: ["f1", "f2"]
179	}
180	`
181	jsonContent := testProjectJson(t, bp)
182	crates := validateJsonCrates(t, jsonContent)
183	for _, c := range crates {
184		crate := validateCrate(t, c)
185		cfgs, ok := crate["cfg"].([]interface{})
186		if !ok {
187			t.Fatalf("Unexpected type for cfgs: %v", crate)
188		}
189		expectedCfgs := []string{"feature=\"f1\"", "feature=\"f2\""}
190		foundCfgs := []string{}
191		for _, cfg := range cfgs {
192			cfg, ok := cfg.(string)
193			if !ok {
194				t.Fatalf("Unexpected type for cfg: %v", cfg)
195			}
196			foundCfgs = append(foundCfgs, cfg)
197		}
198		sort.Strings(foundCfgs)
199		for i, foundCfg := range foundCfgs {
200			if foundCfg != expectedCfgs[i] {
201				t.Errorf("Incorrect features: got %v; want %v", foundCfg, expectedCfgs[i])
202			}
203		}
204	}
205}
206
207func TestProjectJsonBinary(t *testing.T) {
208	bp := `
209	rust_binary {
210		name: "libz",
211		srcs: ["z/src/lib.rs"],
212		crate_name: "z"
213	}
214	`
215	jsonContent := testProjectJson(t, bp)
216	crates := validateJsonCrates(t, jsonContent)
217	for _, c := range crates {
218		crate := validateCrate(t, c)
219		rootModule, ok := crate["root_module"].(string)
220		if !ok {
221			t.Fatalf("Unexpected type for root_module: %v", crate["root_module"])
222		}
223		if rootModule == "z/src/lib.rs" {
224			return
225		}
226	}
227	t.Errorf("Entry for binary %q not found: %s", "a", jsonContent)
228}
229
230func TestProjectJsonBindGen(t *testing.T) {
231	buildOS := android.TestConfig(t.TempDir(), nil, "", nil).BuildOS
232
233	bp := `
234	rust_library {
235		name: "libd",
236		srcs: ["d/src/lib.rs"],
237		rlibs: ["libbindings1"],
238		crate_name: "d"
239	}
240	rust_bindgen {
241		name: "libbindings1",
242		crate_name: "bindings1",
243		source_stem: "bindings1",
244		host_supported: true,
245		wrapper_src: "src/any.h",
246	}
247	rust_library_host {
248		name: "libe",
249		srcs: ["e/src/lib.rs"],
250		rustlibs: ["libbindings2"],
251		crate_name: "e"
252	}
253	rust_bindgen_host {
254		name: "libbindings2",
255		crate_name: "bindings2",
256		source_stem: "bindings2",
257		wrapper_src: "src/any.h",
258	}
259	`
260	jsonContent := testProjectJson(t, bp)
261	crates := validateJsonCrates(t, jsonContent)
262	for _, c := range crates {
263		crate := validateCrate(t, c)
264		rootModule, ok := crate["root_module"].(string)
265		if !ok {
266			t.Fatalf("Unexpected type for root_module: %v", crate["root_module"])
267		}
268		if strings.Contains(rootModule, "libbindings1") && !strings.Contains(rootModule, "android_arm64") {
269			t.Errorf("The source path for libbindings1 does not contain android_arm64, got %v", rootModule)
270		}
271		if strings.Contains(rootModule, "libbindings2") && !strings.Contains(rootModule, buildOS.String()) {
272			t.Errorf("The source path for libbindings2 does not contain the BuildOs, got %v; want %v",
273				rootModule, buildOS.String())
274		}
275		// Check that libbindings1 does not depend on itself.
276		if strings.Contains(rootModule, "libbindings1") {
277			for _, depName := range validateDependencies(t, crate) {
278				if depName == "bindings1" {
279					t.Errorf("libbindings1 depends on itself")
280				}
281			}
282		}
283		if strings.Contains(rootModule, "d/src/lib.rs") {
284			// Check that libd depends on libbindings1
285			found := false
286			for _, depName := range validateDependencies(t, crate) {
287				if depName == "bindings1" {
288					found = true
289					break
290				}
291			}
292			if !found {
293				t.Errorf("libd does not depend on libbindings1: %v", crate)
294			}
295			// Check that OUT_DIR is populated.
296			env, ok := crate["env"].(map[string]interface{})
297			if !ok {
298				t.Errorf("libd does not have its environment variables set: %v", crate)
299			}
300			if _, ok = env["OUT_DIR"]; !ok {
301				t.Errorf("libd does not have its OUT_DIR set: %v", env)
302			}
303
304		}
305	}
306}
307
308func TestProjectJsonMultiVersion(t *testing.T) {
309	bp := `
310	rust_library {
311		name: "liba1",
312		srcs: ["a1/src/lib.rs"],
313		crate_name: "a"
314	}
315	rust_library {
316		name: "liba2",
317		srcs: ["a2/src/lib.rs"],
318		crate_name: "a",
319	}
320	rust_library {
321		name: "libb",
322		srcs: ["b/src/lib.rs"],
323		crate_name: "b",
324		rustlibs: ["liba1", "liba2"],
325	}
326	`
327	jsonContent := testProjectJson(t, bp)
328	crates := validateJsonCrates(t, jsonContent)
329	for _, c := range crates {
330		crate := validateCrate(t, c)
331		rootModule, ok := crate["root_module"].(string)
332		if !ok {
333			t.Fatalf("Unexpected type for root_module: %v", crate["root_module"])
334		}
335		// Make sure that b has 2 different dependencies.
336		if rootModule == "b/src/lib.rs" {
337			aCount := 0
338			deps := validateDependencies(t, crate)
339			for _, depName := range deps {
340				if depName == "a" {
341					aCount++
342				}
343			}
344			if aCount != 2 {
345				t.Errorf("Unexpected number of liba dependencies want %v, got %v: %v", 2, aCount, deps)
346			}
347			return
348		}
349	}
350	t.Errorf("libb crate has not been found: %v", crates)
351}
352