Line data Source code
1 : // Unit tests for the shared `semantic::FrameStack` primitive.
2 : //
3 : // `FrameStack` is the foundation shared between the script driver
4 : // (BEGIN..END variables) and the UDF / TVF invocation surface
5 : // (argument bindings). Tests pin the frame semantics callers
6 : // depend on: push/pop, innermost-binding wins, redeclaration in
7 : // the same frame is rejected, redeclaration in a nested frame is
8 : // allowed, and case-insensitive identifier matching.
9 : //
10 : // The script-statement / UDF-statement tests assert the
11 : // `kAlreadyExists` / `kNotFound` error surfaces translate into the
12 : // matching BigQuery REST envelope on top of this primitive's
13 : // `absl::Status` codes.
14 :
15 : #include "backend/engine/semantic/frame_stack.h"
16 :
17 : #include "absl/status/status.h"
18 : #include "backend/engine/semantic/value.h"
19 : #include "googlesql/public/value.h"
20 : #include "gtest/gtest.h"
21 :
22 : namespace bigquery_emulator {
23 : namespace backend {
24 : namespace engine {
25 : namespace semantic {
26 : namespace {
27 :
28 1 : TEST(FrameStackTest, NewStackHasOuterFrame) {
29 1 : FrameStack stack;
30 1 : EXPECT_EQ(stack.frame_count(), 1u);
31 1 : EXPECT_FALSE(stack.Has("x"));
32 1 : }
33 :
34 1 : TEST(FrameStackTest, DeclareAndLookupRoundTrips) {
35 1 : FrameStack stack;
36 1 : ASSERT_TRUE(stack.Declare("x", Value::Int64(42)).ok());
37 1 : ASSERT_TRUE(stack.Has("x"));
38 1 : auto got = stack.Lookup("x");
39 2 : ASSERT_TRUE(got.ok()) << got.status();
40 1 : EXPECT_EQ(got->int64_value(), 42);
41 1 : }
42 :
43 1 : TEST(FrameStackTest, IdentifierMatchIsCaseInsensitive) {
44 1 : FrameStack stack;
45 1 : ASSERT_TRUE(stack.Declare("FooBar", Value::Int64(1)).ok());
46 1 : EXPECT_TRUE(stack.Has("foobar"));
47 1 : EXPECT_TRUE(stack.Has("FOOBAR"));
48 1 : auto got = stack.Lookup("foobar");
49 1 : ASSERT_TRUE(got.ok());
50 1 : EXPECT_EQ(got->int64_value(), 1);
51 1 : ASSERT_TRUE(stack.Set("FOOBAR", Value::Int64(2)).ok());
52 1 : got = stack.Lookup("FooBar");
53 1 : ASSERT_TRUE(got.ok());
54 1 : EXPECT_EQ(got->int64_value(), 2);
55 1 : }
56 :
57 1 : TEST(FrameStackTest, RedeclareSameFrameRejected) {
58 1 : FrameStack stack;
59 1 : ASSERT_TRUE(stack.Declare("x", Value::Int64(1)).ok());
60 1 : auto status = stack.Declare("x", Value::Int64(2));
61 2 : EXPECT_EQ(status.code(), absl::StatusCode::kAlreadyExists) << status;
62 1 : }
63 :
64 1 : TEST(FrameStackTest, SetWithoutDeclareReturnsNotFound) {
65 1 : FrameStack stack;
66 1 : auto status = stack.Set("missing", Value::Int64(0));
67 2 : EXPECT_EQ(status.code(), absl::StatusCode::kNotFound) << status;
68 1 : }
69 :
70 1 : TEST(FrameStackTest, LookupMissingReturnsNotFound) {
71 1 : FrameStack stack;
72 1 : auto got = stack.Lookup("missing");
73 1 : ASSERT_FALSE(got.ok());
74 1 : EXPECT_EQ(got.status().code(), absl::StatusCode::kNotFound);
75 1 : }
76 :
77 1 : TEST(FrameStackTest, NestedFrameInnerBindingWins) {
78 1 : FrameStack stack;
79 1 : ASSERT_TRUE(stack.Declare("x", Value::Int64(1)).ok());
80 1 : stack.PushFrame();
81 1 : EXPECT_EQ(stack.frame_count(), 2u);
82 1 : ASSERT_TRUE(stack.Declare("x", Value::Int64(2)).ok());
83 1 : auto got = stack.Lookup("x");
84 1 : ASSERT_TRUE(got.ok());
85 1 : EXPECT_EQ(got->int64_value(), 2);
86 1 : ASSERT_TRUE(stack.PopFrame().ok());
87 1 : EXPECT_EQ(stack.frame_count(), 1u);
88 1 : got = stack.Lookup("x");
89 1 : ASSERT_TRUE(got.ok());
90 1 : EXPECT_EQ(got->int64_value(), 1);
91 1 : }
92 :
93 1 : TEST(FrameStackTest, SetUpdatesInnermostBinding) {
94 1 : FrameStack stack;
95 1 : ASSERT_TRUE(stack.Declare("x", Value::Int64(1)).ok());
96 1 : stack.PushFrame();
97 1 : ASSERT_TRUE(stack.Declare("x", Value::Int64(2)).ok());
98 1 : ASSERT_TRUE(stack.Set("x", Value::Int64(20)).ok());
99 1 : auto inner = stack.Lookup("x");
100 1 : ASSERT_TRUE(inner.ok());
101 1 : EXPECT_EQ(inner->int64_value(), 20);
102 1 : ASSERT_TRUE(stack.PopFrame().ok());
103 : // The outer frame's binding is unchanged: `Set` updated only the
104 : // innermost binding, not every frame's copy.
105 1 : auto outer = stack.Lookup("x");
106 1 : ASSERT_TRUE(outer.ok());
107 1 : EXPECT_EQ(outer->int64_value(), 1);
108 1 : }
109 :
110 1 : TEST(FrameStackTest, SetReachesOuterFrame) {
111 1 : FrameStack stack;
112 1 : ASSERT_TRUE(stack.Declare("x", Value::Int64(1)).ok());
113 1 : stack.PushFrame();
114 : // Inner frame has no `x` binding; `Set` walks up and finds the
115 : // outer frame's binding.
116 1 : ASSERT_TRUE(stack.Set("x", Value::Int64(99)).ok());
117 1 : ASSERT_TRUE(stack.PopFrame().ok());
118 1 : auto got = stack.Lookup("x");
119 1 : ASSERT_TRUE(got.ok());
120 1 : EXPECT_EQ(got->int64_value(), 99);
121 1 : }
122 :
123 1 : TEST(FrameStackTest, PopOuterFrameRejected) {
124 1 : FrameStack stack;
125 1 : auto status = stack.PopFrame();
126 2 : EXPECT_EQ(status.code(), absl::StatusCode::kFailedPrecondition) << status;
127 1 : }
128 :
129 : } // namespace
130 : } // namespace semantic
131 : } // namespace engine
132 : } // namespace backend
133 : } // namespace bigquery_emulator
|