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