1# Copyright (C) 2023 The Android Open Source Project 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"""Tests for the validate() function.""" 15 16load("@bazel_skylib//lib:unittest.bzl", "analysistest") 17load(":schema_validation.scl", "validate") 18 19def _string_comparison_test_impl(ctx): 20 env = analysistest.begin(ctx) 21 if ctx.attr.actual != ctx.attr.expected: 22 analysistest.fail(env, "expected '%s' but got '%s'" % (ctx.attr.expected, ctx.attr.actual)) 23 return analysistest.end(env) 24 25_string_comparison_raw_test = analysistest.make( 26 _string_comparison_test_impl, 27 attrs = { 28 "actual": attr.string(), 29 "expected": attr.string(), 30 }, 31) 32 33def _string_comparison_test(*, name, actual, expected): 34 _string_comparison_raw_test( 35 name = name, 36 actual = actual, 37 expected = expected, 38 # target_under_test is required but unused 39 target_under_test = "//build/bazel/utils:always_on_config_setting", 40 ) 41 42def _test_string_success(): 43 test_name = "test_string_success" 44 data = "hello, world" 45 schema = {"type": "string"} 46 message = validate(data, schema, fail_on_error = False) 47 _string_comparison_test( 48 name = test_name, 49 expected = "", 50 actual = message, 51 ) 52 return test_name 53 54def _choices_success(): 55 test_name = "choices_success" 56 data = "bar" 57 schema = { 58 "type": "string", 59 "choices": [ 60 "foo", 61 "bar", 62 "baz", 63 ], 64 } 65 message = validate(data, schema, fail_on_error = False) 66 _string_comparison_test( 67 name = test_name, 68 expected = "", 69 actual = message, 70 ) 71 return test_name 72 73def _choices_failure(): 74 test_name = "choices_failure" 75 data = "qux" 76 schema = { 77 "type": "string", 78 "choices": [ 79 "foo", 80 "bar", 81 "baz", 82 ], 83 } 84 message = validate(data, schema, fail_on_error = False) 85 _string_comparison_test( 86 name = test_name, 87 expected = 'Expected one of ["foo", "bar", "baz"], got qux', 88 actual = message, 89 ) 90 return test_name 91 92def _value_success(): 93 test_name = "value_success" 94 data = "bar" 95 schema = { 96 "type": "string", 97 "value": "bar", 98 } 99 message = validate(data, schema, fail_on_error = False) 100 _string_comparison_test( 101 name = test_name, 102 expected = "", 103 actual = message, 104 ) 105 return test_name 106 107def _value_failure(): 108 test_name = "value_failure" 109 data = "qux" 110 schema = { 111 "type": "string", 112 "value": "bar", 113 } 114 message = validate(data, schema, fail_on_error = False) 115 _string_comparison_test( 116 name = test_name, 117 expected = "Expected bar, got qux", 118 actual = message, 119 ) 120 return test_name 121 122def _length_success(): 123 test_name = "length_success" 124 data = { 125 "a": "foo", 126 "b": "foo", 127 "c": "foo", 128 "d": "foo", 129 "e": "foo", 130 "f": "foo", 131 } 132 schema = { 133 "type": "dict", 134 "required_keys": { 135 "a": { 136 "type": "string", 137 "length": 3, 138 }, 139 "b": { 140 "type": "string", 141 "length": "<4", 142 }, 143 "c": { 144 "type": "string", 145 "length": "<=4", 146 }, 147 "d": { 148 "type": "string", 149 "length": ">2", 150 }, 151 "e": { 152 "type": "string", 153 "length": ">=2", 154 }, 155 "f": { 156 "type": "string", 157 "length": "=3", 158 }, 159 }, 160 } 161 message = validate(data, schema, fail_on_error = False) 162 _string_comparison_test( 163 name = test_name, 164 expected = "", 165 actual = message, 166 ) 167 return test_name 168 169def _length_failure_1(): 170 test_name = "length_failure_1" 171 data = "qux" 172 schema = { 173 "type": "string", 174 "length": 4, 175 } 176 message = validate(data, schema, fail_on_error = False) 177 _string_comparison_test( 178 name = test_name, 179 expected = "Expected length 4, got 3", 180 actual = message, 181 ) 182 return test_name 183 184def _length_failure_2(): 185 test_name = "length_failure_2" 186 data = "qux" 187 schema = { 188 "type": "string", 189 "length": ">3", 190 } 191 message = validate(data, schema, fail_on_error = False) 192 _string_comparison_test( 193 name = test_name, 194 expected = "Expected length >3, got 3", 195 actual = message, 196 ) 197 return test_name 198 199def _test_type_failure(): 200 test_name = "test_type_failure" 201 data = 5 202 schema = {"type": "string"} 203 message = validate(data, schema, fail_on_error = False) 204 _string_comparison_test( 205 name = test_name, 206 expected = "Expected string, got int", 207 actual = message, 208 ) 209 return test_name 210 211def _test_or_success(): 212 test_name = "test_or_success" 213 data = "hello, world" 214 schema = {"or": [ 215 {"type": "int"}, 216 {"type": "string"}, 217 ]} 218 message = validate(data, schema, fail_on_error = False) 219 _string_comparison_test( 220 name = test_name, 221 expected = "", 222 actual = message, 223 ) 224 return test_name 225 226def _test_or_failure(): 227 test_name = "test_or_failure" 228 data = 3.5 229 schema = {"or": [ 230 {"type": "int"}, 231 {"type": "string"}, 232 ]} 233 message = validate(data, schema, fail_on_error = False) 234 _string_comparison_test( 235 name = test_name, 236 expected = "did not match any schemas in 'or' list, errors:\n Expected int, got float\n Expected string, got float", 237 actual = message, 238 ) 239 return test_name 240 241def _list_of_strings_success(): 242 test_name = "list_of_strings_success" 243 data = ["a", "b"] 244 schema = { 245 "type": "list", 246 "of": {"type": "string"}, 247 } 248 message = validate(data, schema, fail_on_error = False) 249 _string_comparison_test( 250 name = test_name, 251 expected = "", 252 actual = message, 253 ) 254 return test_name 255 256def _list_of_strings_failure(): 257 test_name = "list_of_strings_failure" 258 data = ["a", 5, "b"] 259 schema = { 260 "type": "list", 261 "of": {"type": "string"}, 262 } 263 message = validate(data, schema, fail_on_error = False) 264 _string_comparison_test( 265 name = test_name, 266 expected = "Expected string, got int", 267 actual = message, 268 ) 269 return test_name 270 271def _tuple_of_strings_success(): 272 test_name = "tuple_of_strings_success" 273 data = ("a", "b") 274 schema = { 275 "type": "tuple", 276 "of": {"type": "string"}, 277 } 278 message = validate(data, schema, fail_on_error = False) 279 _string_comparison_test( 280 name = test_name, 281 expected = "", 282 actual = message, 283 ) 284 return test_name 285 286def _tuple_of_strings_failure(): 287 test_name = "tuple_of_strings_failure" 288 data = ("a", 5, "b") 289 schema = { 290 "type": "tuple", 291 "of": {"type": "string"}, 292 } 293 message = validate(data, schema, fail_on_error = False) 294 _string_comparison_test( 295 name = test_name, 296 expected = "Expected string, got int", 297 actual = message, 298 ) 299 return test_name 300 301def _unique_list_of_strings_success(): 302 test_name = "unique_list_of_strings_success" 303 data = ["a", "b"] 304 schema = { 305 "type": "list", 306 "of": {"type": "string"}, 307 "unique": True, 308 } 309 message = validate(data, schema, fail_on_error = False) 310 _string_comparison_test( 311 name = test_name, 312 expected = "", 313 actual = message, 314 ) 315 return test_name 316 317def _unique_list_of_strings_failure(): 318 test_name = "unique_list_of_strings_failure" 319 data = ["a", "b", "a"] 320 schema = { 321 "type": "list", 322 "of": {"type": "string"}, 323 "unique": True, 324 } 325 message = validate(data, schema, fail_on_error = False) 326 _string_comparison_test( 327 name = test_name, 328 expected = "Expected all elements to be unique, but saw 'a' twice", 329 actual = message, 330 ) 331 return test_name 332 333def _dict_success(): 334 test_name = "dict_success" 335 data = { 336 "foo": 5, 337 "bar": "baz", 338 "qux": 3.5, 339 } 340 schema = { 341 "type": "dict", 342 "required_keys": { 343 "foo": {"type": "int"}, 344 "bar": {"type": "string"}, 345 }, 346 "optional_keys": { 347 "qux": {"type": "float"}, 348 }, 349 } 350 message = validate(data, schema, fail_on_error = False) 351 _string_comparison_test( 352 name = test_name, 353 expected = "", 354 actual = message, 355 ) 356 return test_name 357 358def _dict_missing_required_key(): 359 test_name = "dict_missing_required_key" 360 data = { 361 "foo": 5, 362 } 363 schema = { 364 "type": "dict", 365 "required_keys": { 366 "foo": {"type": "int"}, 367 "bar": {"type": "string"}, 368 }, 369 } 370 message = validate(data, schema, fail_on_error = False) 371 _string_comparison_test( 372 name = test_name, 373 expected = "required key 'bar' not found", 374 actual = message, 375 ) 376 return test_name 377 378def _dict_extra_keys(): 379 test_name = "dict_extra_keys" 380 data = { 381 "foo": 5, 382 "bar": "hello", 383 "baz": 3.5, 384 } 385 schema = { 386 "type": "dict", 387 "required_keys": { 388 "foo": {"type": "int"}, 389 }, 390 "optional_keys": { 391 "bar": {"type": "string"}, 392 }, 393 } 394 message = validate(data, schema, fail_on_error = False) 395 _string_comparison_test( 396 name = test_name, 397 expected = 'keys ["baz"] not allowed, valid keys: ["foo", "bar"]', 398 actual = message, 399 ) 400 return test_name 401 402def _dict_generic_keys_success(): 403 test_name = "dict_generic_keys_success" 404 data = { 405 "foo": 5, 406 "bar": "hello", 407 } 408 schema = { 409 "type": "dict", 410 "keys": {"type": "string"}, 411 "values": { 412 "or": [ 413 {"type": "string"}, 414 {"type": "int"}, 415 ], 416 }, 417 } 418 message = validate(data, schema, fail_on_error = False) 419 _string_comparison_test( 420 name = test_name, 421 expected = "", 422 actual = message, 423 ) 424 return test_name 425 426def _dict_generic_keys_failure(): 427 test_name = "dict_generic_keys_failure" 428 data = { 429 "foo": 5, 430 "bar": "hello", 431 "baz": 3.5, 432 } 433 schema = { 434 "type": "dict", 435 "keys": {"type": "string"}, 436 "values": { 437 "or": [ 438 {"type": "string"}, 439 {"type": "int"}, 440 ], 441 }, 442 } 443 message = validate(data, schema, fail_on_error = False) 444 _string_comparison_test( 445 name = test_name, 446 expected = "did not match any schemas in 'or' list, errors:\n Expected string, got float\n Expected int, got float", 447 actual = message, 448 ) 449 return test_name 450 451def _struct_success(): 452 test_name = "struct_success" 453 data = struct( 454 foo = 5, 455 bar = "baz", 456 qux = 3.5, 457 ) 458 schema = { 459 "type": "struct", 460 "required_fields": { 461 "foo": {"type": "int"}, 462 "bar": {"type": "string"}, 463 }, 464 "optional_fields": { 465 "qux": {"type": "float"}, 466 }, 467 } 468 message = validate(data, schema, fail_on_error = False) 469 _string_comparison_test( 470 name = test_name, 471 expected = "", 472 actual = message, 473 ) 474 return test_name 475 476def _struct_missing_required_field(): 477 test_name = "struct_missing_required_field" 478 data = struct( 479 foo = 5, 480 ) 481 schema = { 482 "type": "struct", 483 "required_fields": { 484 "foo": {"type": "int"}, 485 "bar": {"type": "string"}, 486 }, 487 } 488 message = validate(data, schema, fail_on_error = False) 489 _string_comparison_test( 490 name = test_name, 491 expected = "required field 'bar' not found", 492 actual = message, 493 ) 494 return test_name 495 496def _struct_extra_fields(): 497 test_name = "struct_extra_fields" 498 data = struct( 499 foo = 5, 500 bar = "baz", 501 baz = 3.5, 502 ) 503 schema = { 504 "type": "struct", 505 "required_fields": { 506 "foo": {"type": "int"}, 507 }, 508 "optional_fields": { 509 "bar": {"type": "string"}, 510 }, 511 } 512 message = validate(data, schema, fail_on_error = False) 513 _string_comparison_test( 514 name = test_name, 515 expected = 'fields ["baz"] not allowed, valid keys: ["foo", "bar"]', 516 actual = message, 517 ) 518 return test_name 519 520def schema_validation_test_suite(name): 521 native.test_suite( 522 name = name, 523 tests = [ 524 _test_string_success(), 525 _choices_success(), 526 _choices_failure(), 527 _value_success(), 528 _value_failure(), 529 _length_success(), 530 _length_failure_1(), 531 _length_failure_2(), 532 _test_type_failure(), 533 _test_or_success(), 534 _test_or_failure(), 535 _list_of_strings_success(), 536 _list_of_strings_failure(), 537 _tuple_of_strings_success(), 538 _tuple_of_strings_failure(), 539 _unique_list_of_strings_success(), 540 _unique_list_of_strings_failure(), 541 _dict_success(), 542 _dict_missing_required_key(), 543 _dict_extra_keys(), 544 _dict_generic_keys_success(), 545 _dict_generic_keys_failure(), 546 _struct_success(), 547 _struct_missing_required_field(), 548 _struct_extra_fields(), 549 ], 550 ) 551