1# Copyright 2024 The Bazel Authors. 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"""Implementation of a result type for use with rules_testing.""" 15 16load("@bazel_skylib//lib:structs.bzl", "structs") 17load("@rules_testing//lib:truth.bzl", "subjects") 18 19visibility("//tests/rule_based_toolchain/...") 20 21def result_fn_wrapper(fn): 22 """Wraps a function that may fail in a type similar to rust's Result type. 23 24 An example usage is the following: 25 # Implementation file 26 def get_only(value, fail=fail): 27 if len(value) == 1: 28 return value[0] 29 elif not value: 30 fail("Unexpectedly empty") 31 else: 32 fail("%r had length %d, expected 1" % (value, len(value)) 33 34 # Test file 35 load("...", _fn=fn) 36 37 fn = result_fn_wrapper(_fn) 38 int_result = result_subject(subjects.int) 39 40 def my_test(env, _): 41 env.expect.that_value(fn([]), factory=int_result) 42 .err().equals("Unexpectedly empty") 43 env.expect.that_value(fn([1]), factory=int_result) 44 .ok().equals(1) 45 env.expect.that_value(fn([1, 2]), factory=int_result) 46 .err().contains("had length 2, expected 1") 47 48 Args: 49 fn: A function that takes in a parameter fail and calls it on failure. 50 51 Returns: 52 On success: struct(ok = <result>, err = None) 53 On failure: struct(ok = None, err = <first error message> 54 """ 55 56 def new_fn(*args, **kwargs): 57 # Use a mutable type so that the fail_wrapper can modify this. 58 failures = [] 59 60 def fail_wrapper(msg): 61 failures.append(msg) 62 63 result = fn(fail = fail_wrapper, *args, **kwargs) 64 if failures: 65 return struct(ok = None, err = failures[0]) 66 else: 67 return struct(ok = result, err = None) 68 69 return new_fn 70 71def result_subject(factory): 72 """A subject factory for Result<T>. 73 74 Args: 75 factory: A subject factory for T 76 Returns: 77 A subject factory for Result<T> 78 """ 79 80 def new_factory(value, *, meta): 81 def ok(): 82 if value.err != None: 83 meta.add_failure("Wanted a value, but got an error", value.err) 84 return factory(value.ok, meta = meta.derive("ok()")) 85 86 def err(): 87 if value.err == None: 88 meta.add_failure("Wanted an error, but got a value", value.ok) 89 subject = subjects.str(value.err, meta = meta.derive("err()")) 90 91 def contains_all_of(values): 92 for value in values: 93 subject.contains(str(value)) 94 95 return struct(contains_all_of = contains_all_of, **structs.to_dict(subject)) 96 97 return struct(ok = ok, err = err) 98 99 return new_factory 100 101def optional_subject(factory): 102 """A subject factory for Optional<T>. 103 104 Args: 105 factory: A subject factory for T 106 Returns: 107 A subject factory for Optional<T> 108 """ 109 110 def new_factory(value, *, meta): 111 def some(): 112 if value == None: 113 meta.add_failure("Wanted a value, but got None", None) 114 return factory(value, meta = meta) 115 116 def is_none(): 117 if value != None: 118 meta.add_failure("Wanted None, but got a value", value) 119 120 return struct(some = some, is_none = is_none) 121 122 return new_factory 123 124# Curry subjects.struct so the type is actually generic. 125struct_subject = lambda **attrs: lambda value, *, meta: subjects.struct( 126 value, 127 meta = meta, 128 attrs = attrs, 129) 130 131# We can't do complex assertions on containers. This allows you to write 132# assert.that_value({"foo": 1), factory=dict_key_subject(subjects.int)) 133# .get("foo").equals(1) 134dict_key_subject = lambda factory: lambda value, *, meta: struct( 135 get = lambda key: factory( 136 value[key], 137 meta = meta.derive("get({})".format(key)), 138 ), 139) 140