Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions testing/testrunner/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,19 @@ cc_library(
name = "runner",
srcs = ["runner_bin.cc"],
deps = [
":cel_expression_source",
":cel_test_context",
":cel_test_factories",
":runner_lib",
"//internal:status_macros",
"//internal:testing_no_main",
"@com_google_absl//absl/flags:flag",
"@com_google_absl//absl/log:absl_check",
"@com_google_absl//absl/log:absl_log",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_cel_spec//proto/cel/expr:checked_cc_proto",
"@com_google_cel_spec//proto/cel/expr/conformance/test:suite_cc_proto",
"@com_google_protobuf//:protobuf",
"@com_google_protobuf//src/google/protobuf/io",
Expand Down
47 changes: 45 additions & 2 deletions testing/testrunner/cel_cc_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ load("@rules_cc//cc:cc_test.bzl", "cc_test")
def cel_cc_test(
name,
test_suite = "",
cel_expr = "",
is_raw_expr = False,
filegroup = "",
deps = [],
test_data_path = "",
Expand All @@ -32,14 +34,25 @@ def cel_cc_test(
name: str name for the generated artifact
test_suite: str label of a file containing a test suite. The file should have a
.textproto extension.
cel_expr: The CEL expression source. The meaning of this argument depends on `is_raw_expr`.
is_raw_expr: bool whether the cel_expr is a raw expression string. If False,
cel_expr is treated as a file path. The file type (.cel or .textproto)
is inferred from the extension.
filegroup: str label of a filegroup containing the test suite, the config and the checked
expression.
deps: list of dependencies for the cc_test rule.
data: list of data dependencies for the cc_test rule.
test_data_path: absolute path of the directory containing the test files. This is needed only
if the test files are not located in the same directory as the BUILD file.
"""
data, test_data_path = _update_data_with_test_files(data, filegroup, test_data_path, test_suite)
data, test_data_path = _update_data_with_test_files(
data,
filegroup,
test_data_path,
test_suite,
cel_expr,
is_raw_expr,
)
args = []

test_data_path = test_data_path.lstrip("/")
Expand All @@ -48,23 +61,53 @@ def cel_cc_test(
test_suite = test_data_path + "/" + test_suite
args.append("--test_suite_path=" + test_suite)

if cel_expr != "":
expression_kind = ""
cel_expr_value = ""
if is_raw_expr:
expression_kind = "raw"
cel_expr_value = "\"" + cel_expr + "\""
else:
_, ext = _split_extension(cel_expr)
resolved_path = test_data_path + "/" + cel_expr
if ext == ".cel":
expression_kind = "file"
else:
expression_kind = "checked"
cel_expr_value = resolved_path

args.append("--expression_kind=" + expression_kind)
args.append("--cel_expr_value=" + cel_expr_value)

cc_test(
name = name,
data = data,
args = args,
deps = ["//testing/testrunner:runner"] + deps,
)

def _update_data_with_test_files(data, filegroup, test_data_path, test_suite):
def _split_extension(path):
"""Extracts the file extension from a path string."""

parts = path.rsplit(".", 1)
if len(parts) == 1:
return path, ""
return parts[0], "." + parts[1]

def _update_data_with_test_files(data, filegroup, test_data_path, test_suite, cel_expr, is_raw_expr):
"""Updates the data with the test files."""

if filegroup != "":
data = data + [filegroup]
elif test_data_path != "" and test_data_path != native.package_name():
if test_suite != "":
data = data + [test_data_path + ":" + test_suite]
if cel_expr != "" and not is_raw_expr:
data = data + [test_data_path + ":" + cel_expr]
else:
test_data_path = native.package_name()
if test_suite != "":
data = data + [test_suite]
if cel_expr != "" and not is_raw_expr:
data = data + [cel_expr]
return data, test_data_path
3 changes: 2 additions & 1 deletion testing/testrunner/cel_test_factories.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ namespace cel::test {
namespace internal {

using CelTestContextFactoryFn =
std::function<absl::StatusOr<std::unique_ptr<CelTestContext>>()>;
std::function<absl::StatusOr<std::unique_ptr<CelTestContext>>(
CelTestContextOptions options)>;
using CelTestSuiteFactoryFn =
std::function<cel::expr::conformance::test::TestSuite()>;

Expand Down
5 changes: 4 additions & 1 deletion testing/testrunner/resources/BUILD
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package(default_visibility = ["//visibility:public"])

exports_files(
["test.cel"],
[
"test.cel",
"subtraction_checked_expr.textproto",
],
)

filegroup(
Expand Down
42 changes: 42 additions & 0 deletions testing/testrunner/resources/subtraction_checked_expr.textproto
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# proto-file: google3/google/api/expr/checked.proto
# proto-message: google.api.expr.CheckedExpr

# CEL expression: x - y (where x is Int64, y is Int64)

expr {
id: 1
call_expr {
function: "_-_"
args {
id: 2
ident_expr {
name: "x"
}
}
args {
id: 3
ident_expr {
name: "y"
}
}
}
}
# Type information confirming the final result is an Int64
type_map {
key: 1
value {
primitive: INT64
}
}
type_map {
key: 2
value {
primitive: INT64
}
}
type_map {
key: 3
value {
primitive: INT64
}
}
95 changes: 76 additions & 19 deletions testing/testrunner/runner_bin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,44 @@
// written in the CEL test suite format.
#include <fstream>
#include <functional>
#include <ios>
#include <memory>
#include <sstream>
#include <string>
#include <string_view>
#include <utility>

#include "cel/expr/checked.pb.h"
#include "absl/flags/flag.h"
#include "absl/log/absl_check.h"
#include "absl/log/absl_log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "internal/status_macros.h"
#include "internal/testing.h"
#include "testing/testrunner/cel_expression_source.h"
#include "testing/testrunner/cel_test_context.h"
#include "testing/testrunner/cel_test_factories.h"
#include "testing/testrunner/runner_lib.h"
#include "cel/expr/conformance/test/suite.pb.h"
#include "google/protobuf/io/zero_copy_stream_impl.h"
#include "google/protobuf/text_format.h"

ABSL_FLAG(std::string, test_suite_path, "",
"The path to the file containing the test suite to run.");
ABSL_FLAG(std::string, expression_kind, "",
"The kind of expression source: 'raw', 'file', or 'checked'.");
ABSL_FLAG(std::string, cel_expr_value, "",
"The value of the CEL expression source. For 'raw', it's the "
"expression string. For 'file' and 'checked', it's the file path.");

namespace {

using ::cel::expr::conformance::test::TestCase;
using ::cel::expr::conformance::test::TestSuite;
using ::cel::test::CelExpressionSource;
using ::cel::test::CelTestContextOptions;
using ::cel::test::TestRunner;
using ::cel::expr::CheckedExpr;

class CelTest : public testing::Test {
public:
Expand Down Expand Up @@ -73,21 +83,37 @@ absl::Status RegisterTests(const TestSuite& test_suite,
return absl::OkStatus();
}

TestSuite ReadTestSuiteFromPath(std::string_view test_suite_path) {
TestSuite test_suite;
{
std::ifstream in;
in.open(std::string(test_suite_path),
std::ios_base::in | std::ios_base::binary);
if (!in.is_open()) {
ABSL_LOG(FATAL) << "failed to open file: " << test_suite_path;
}
google::protobuf::io::IstreamInputStream stream(&in);
if (!google::protobuf::TextFormat::Parse(&stream, &test_suite)) {
ABSL_LOG(FATAL) << "failed to parse file: " << test_suite_path;
}
absl::StatusOr<std::string> ReadFileToString(absl::string_view file_path) {
std::ifstream file_stream{std::string(file_path)};
if (!file_stream.is_open()) {
return absl::NotFoundError(
absl::StrCat("Unable to open file: ", file_path));
}
std::stringstream buffer;
buffer << file_stream.rdbuf();
return buffer.str();
}

template <typename T>
absl::StatusOr<T> ReadTextProtoFromFile(absl::string_view file_path) {
CEL_ASSIGN_OR_RETURN(std::string contents, ReadFileToString(file_path));
T message;
if (!google::protobuf::TextFormat::ParseFromString(contents, &message)) {
return absl::InternalError(absl::StrCat(
"Failed to parse text-format proto from file: ", file_path));
}
return message;
}

TestSuite ReadTestSuiteFromPath(absl::string_view test_suite_path) {
absl::StatusOr<TestSuite> test_suite_or =
ReadTextProtoFromFile<TestSuite>(test_suite_path);

if (!test_suite_or.ok()) {
ABSL_LOG(FATAL) << "Failed to load test suite from " << test_suite_path
<< ": " << test_suite_or.status();
}
return test_suite;
return *std::move(test_suite_or);
}

TestSuite GetTestSuite() {
Expand All @@ -108,15 +134,46 @@ TestSuite GetTestSuite() {
}
return test_suite_factory();
}

absl::StatusOr<CelTestContextOptions> GetExpressionSourceOptions() {
CelTestContextOptions options;
if (absl::GetFlag(FLAGS_expression_kind).empty()) {
return options;
}

std::string kind = absl::GetFlag(FLAGS_expression_kind);
std::string value = absl::GetFlag(FLAGS_cel_expr_value);

if (kind == "raw") {
options.expression_source = CelExpressionSource::FromRawExpression(value);
} else if (kind == "file") {
options.expression_source = CelExpressionSource::FromCelFile(value);
} else if (kind == "checked") {
CEL_ASSIGN_OR_RETURN(CheckedExpr checked_expr,
ReadTextProtoFromFile<CheckedExpr>(value));
options.expression_source =
CelExpressionSource::FromCheckedExpr(std::move(checked_expr));
} else {
ABSL_LOG(FATAL) << "Unknown expression kind: " << kind;
}
return options;
}

} // namespace

int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);

absl::StatusOr<CelTestContextOptions> test_context_options =
GetExpressionSourceOptions();
if (!test_context_options.ok()) {
ABSL_LOG(FATAL) << "Failed to get expression source options: "
<< test_context_options.status();
}
// Create a test context using the factory function returned by the global
// factory function provider which was initialized by the user.
absl::StatusOr<std::unique_ptr<cel::test::CelTestContext>> cel_test_context =
cel::test::internal::GetCelTestContextFactory()();
cel::test::internal::GetCelTestContextFactory()(
std::move(*test_context_options));
if (!cel_test_context.ok()) {
ABSL_LOG(FATAL) << "Failed to create CEL test context: "
<< cel_test_context.status();
Expand Down
56 changes: 56 additions & 0 deletions testing/testrunner/user_tests/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,35 @@ cc_library(
],
)

cc_library(
name = "flexible_user_test",
testonly = True,
srcs = ["flexible_test.cc"],
deps = [
"//checker:type_checker_builder",
"//common:decl",
"//common:type",
"//compiler",
"//compiler:compiler_factory",
"//compiler:standard_library",
"//internal:status_macros",
"//internal:testing_descriptor_pool",
"//runtime",
"//runtime:runtime_builder",
"//runtime:standard_runtime_builder_factory",
"//testing/testrunner:cel_test_context",
"//testing/testrunner:cel_test_factories",
"@com_google_absl//absl/log:absl_check",
"@com_google_absl//absl/log:absl_log",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings:string_view",
"@com_google_cel_spec//proto/cel/expr:checked_cc_proto",
"@com_google_cel_spec//proto/cel/expr/conformance/test:suite_cc_proto",
"@com_google_protobuf//:protobuf",
],
alwayslink = True,
)

cel_cc_test(
name = "simple_test",
filegroup = "//testing/testrunner/resources",
Expand All @@ -86,3 +115,30 @@ cel_cc_test(
":raw_expression_user_test",
],
)

cel_cc_test(
name = "subtraction_checked_expr_test",
cel_expr = "subtraction_checked_expr.textproto",
test_data_path = "//testing/testrunner/resources",
deps = [
":flexible_user_test",
],
)

cel_cc_test(
name = "subtraction_raw_expr_test",
cel_expr = "x - y",
is_raw_expr = True,
deps = [
":flexible_user_test",
],
)

cel_cc_test(
name = "subtraction_cel_file_test",
cel_expr = "test.cel",
test_data_path = "//testing/testrunner/resources",
deps = [
":flexible_user_test",
],
)
Loading