Line data Source code
1 : // Unit tests for `script::ExecuteAssert`.
2 : //
3 : // We drive a real `AnalyzeStatement` against a tiny `SimpleCatalog`
4 : // (mirroring `executor_test.cc`) and run `ExecuteAssert` over the
5 : // analyzer's resolved `ResolvedAssertStmt`. Tests pin every branch
6 : // of the BigQuery-documented `ASSERT` contract:
7 : //
8 : // * `ASSERT TRUE` -- OK.
9 : // * `ASSERT FALSE` -- INVALID_ARGUMENT carrying the
10 : // `kInvalidArgument` semantic-error reason and the default
11 : // "Assertion failed" message.
12 : // * `ASSERT FALSE AS '<msg>'` -- same code, message includes
13 : // `<msg>`.
14 : // * `ASSERT NULL` -- INVALID_ARGUMENT (NULL is not TRUE).
15 : // * Expression evaluation failures (e.g. `ASSERT 1.0 / 0 > 0`)
16 : // propagate the underlying semantic error reason (here:
17 : // `kDivisionByZero`).
18 :
19 : #include "backend/engine/semantic/script/assert_stmt.h"
20 :
21 : #include <memory>
22 : #include <string>
23 :
24 : #include "absl/status/status.h"
25 : #include "absl/status/statusor.h"
26 : #include "backend/engine/engine.h"
27 : #include "backend/engine/semantic/error.h"
28 : #include "backend/engine/semantic/script/script_driver.h"
29 : #include "googlesql/public/analyzer.h"
30 : #include "googlesql/public/analyzer_options.h"
31 : #include "googlesql/public/analyzer_output.h"
32 : #include "googlesql/public/builtin_function_options.h"
33 : #include "googlesql/public/catalog.h"
34 : #include "googlesql/public/language_options.h"
35 : #include "googlesql/public/options.pb.h"
36 : #include "googlesql/public/simple_catalog.h"
37 : #include "googlesql/public/types/type_factory.h"
38 : #include "googlesql/resolved_ast/resolved_ast.h"
39 : #include "gtest/gtest.h"
40 :
41 : namespace bigquery_emulator {
42 : namespace backend {
43 : namespace engine {
44 : namespace semantic {
45 : namespace script {
46 : namespace {
47 :
48 : // Build an analyzer options bundle that supports every statement
49 : // kind (ASSERT is not in the default supported set) and enables
50 : // the maximum language feature surface so the resolver accepts the
51 : // `AS '<msg>'` form.
52 6 : ::googlesql::AnalyzerOptions MakeAssertAnalyzerOptions() {
53 6 : ::googlesql::LanguageOptions language;
54 6 : language.EnableMaximumLanguageFeatures();
55 6 : language.set_product_mode(::googlesql::PRODUCT_EXTERNAL);
56 6 : language.SetSupportsAllStatementKinds();
57 6 : ::googlesql::AnalyzerOptions options(language);
58 6 : options.CreateDefaultArenasIfNotSet();
59 6 : return options;
60 6 : }
61 :
62 : class AssertStmtTest : public ::testing::Test {
63 : protected:
64 6 : void SetUp() override {
65 6 : type_factory_ = std::make_unique<::googlesql::TypeFactory>();
66 6 : catalog_ = std::make_unique<::googlesql::SimpleCatalog>(
67 6 : "assert_catalog", type_factory_.get());
68 6 : catalog_->AddBuiltinFunctions(
69 6 : ::googlesql::BuiltinFunctionOptions::AllReleasedFunctions());
70 6 : }
71 :
72 6 : const ::googlesql::ResolvedAssertStmt* Analyze(absl::string_view sql) {
73 6 : last_output_.reset();
74 6 : auto options = MakeAssertAnalyzerOptions();
75 6 : absl::Status s = ::googlesql::AnalyzeStatement(
76 6 : sql, options, catalog_.get(), type_factory_.get(), &last_output_);
77 12 : EXPECT_TRUE(s.ok()) << s;
78 6 : if (!s.ok() || last_output_ == nullptr) return nullptr;
79 6 : const ::googlesql::ResolvedStatement* stmt = nullptr;
80 6 : stmt = last_output_->resolved_statement();
81 6 : EXPECT_NE(stmt, nullptr);
82 6 : if (stmt == nullptr) return nullptr;
83 6 : EXPECT_EQ(stmt->node_kind(), ::googlesql::RESOLVED_ASSERT_STMT);
84 6 : return stmt->GetAs<::googlesql::ResolvedAssertStmt>();
85 6 : }
86 :
87 6 : QueryRequest MakeRequest(absl::string_view sql) {
88 6 : QueryRequest req;
89 6 : req.project_id = "test-project";
90 6 : req.sql = std::string(sql);
91 6 : return req;
92 6 : }
93 :
94 : std::unique_ptr<::googlesql::TypeFactory> type_factory_{};
95 : std::unique_ptr<::googlesql::SimpleCatalog> catalog_{};
96 : std::unique_ptr<const ::googlesql::AnalyzerOutput> last_output_{};
97 : };
98 :
99 1 : TEST_F(AssertStmtTest, AssertTrueIsNoop) {
100 1 : const auto* stmt = Analyze("ASSERT TRUE");
101 1 : ASSERT_NE(stmt, nullptr);
102 1 : ScriptDriver driver;
103 1 : absl::Status status =
104 1 : ExecuteAssert(MakeRequest("ASSERT TRUE"), *stmt, driver);
105 2 : EXPECT_TRUE(status.ok()) << status;
106 1 : }
107 :
108 1 : TEST_F(AssertStmtTest, AssertFalseSurfacesInvalidArgument) {
109 1 : const auto* stmt = Analyze("ASSERT FALSE");
110 1 : ASSERT_NE(stmt, nullptr);
111 1 : ScriptDriver driver;
112 1 : absl::Status status =
113 1 : ExecuteAssert(MakeRequest("ASSERT FALSE"), *stmt, driver);
114 1 : ASSERT_FALSE(status.ok());
115 1 : EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
116 1 : EXPECT_EQ(GetSemanticErrorReason(status),
117 1 : SemanticErrorReason::kInvalidArgument);
118 2 : EXPECT_NE(status.message().find("Assertion failed"), std::string::npos)
119 2 : << status.message();
120 1 : }
121 :
122 1 : TEST_F(AssertStmtTest, AssertFalseWithDescriptionIncludesIt) {
123 1 : const std::string sql = "ASSERT FALSE AS 'predicate must hold'";
124 1 : const auto* stmt = Analyze(sql);
125 1 : ASSERT_NE(stmt, nullptr);
126 1 : ScriptDriver driver;
127 1 : absl::Status status = ExecuteAssert(MakeRequest(sql), *stmt, driver);
128 1 : ASSERT_FALSE(status.ok());
129 1 : EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
130 2 : EXPECT_NE(status.message().find("predicate must hold"), std::string::npos)
131 2 : << status.message();
132 1 : }
133 :
134 1 : TEST_F(AssertStmtTest, AssertNullTreatedAsFailure) {
135 1 : const std::string sql = "ASSERT CAST(NULL AS BOOL)";
136 1 : const auto* stmt = Analyze(sql);
137 1 : ASSERT_NE(stmt, nullptr);
138 1 : ScriptDriver driver;
139 1 : absl::Status status = ExecuteAssert(MakeRequest(sql), *stmt, driver);
140 1 : ASSERT_FALSE(status.ok());
141 1 : EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
142 1 : }
143 :
144 1 : TEST_F(AssertStmtTest, AssertWithComparisonOnTruePredicate) {
145 1 : const std::string sql = "ASSERT 1 + 1 = 2";
146 1 : const auto* stmt = Analyze(sql);
147 1 : ASSERT_NE(stmt, nullptr);
148 1 : ScriptDriver driver;
149 1 : EXPECT_TRUE(ExecuteAssert(MakeRequest(sql), *stmt, driver).ok());
150 1 : }
151 :
152 1 : TEST_F(AssertStmtTest, AssertPropagatesEvaluationError) {
153 : // The expression evaluator surfaces a structured division-by-zero
154 : // error; ASSERT propagates it verbatim so the caller sees the
155 : // same envelope they would have seen for `SELECT 1.0 / 0`.
156 1 : const std::string sql = "ASSERT 1.0 / 0 > 0";
157 1 : const auto* stmt = Analyze(sql);
158 1 : ASSERT_NE(stmt, nullptr);
159 1 : ScriptDriver driver;
160 1 : absl::Status status = ExecuteAssert(MakeRequest(sql), *stmt, driver);
161 1 : ASSERT_FALSE(status.ok());
162 1 : EXPECT_EQ(GetSemanticErrorReason(status),
163 1 : SemanticErrorReason::kDivisionByZero);
164 1 : }
165 :
166 : } // namespace
167 : } // namespace script
168 : } // namespace semantic
169 : } // namespace engine
170 : } // namespace backend
171 : } // namespace bigquery_emulator
|