Skip to content

Commit 976567c

Browse files
committed
Improve cell magic parser test coverage
1 parent 5690b81 commit 976567c

File tree

6 files changed

+538
-16
lines changed

6 files changed

+538
-16
lines changed

google/cloud/bigquery/ipython_magics/line_arg_parser/lexer.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ def __iter__(self):
203203
while state != LexerState.STATE_END:
204204
token_generator = self._get_state_token_generator(state, offset)
205205

206-
for maybe_token in token_generator:
206+
for maybe_token in token_generator: # pragma: NO COVER
207207
if isinstance(maybe_token, StateTransition):
208208
state = maybe_token.new_state
209209
offset = maybe_token.total_offset
@@ -240,7 +240,7 @@ def _scan_for_tokens(self, scanner):
240240
Yields:
241241
The next ``Token`` or ``StateTransition`` instance.
242242
"""
243-
for match in iter(scanner.match, None):
243+
for match in iter(scanner.match, None): # pragma: NO COVER
244244
token_type = match.lastgroup
245245

246246
if token_type.startswith("GOTO_STATE"):
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright 2020 Google LLC
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.
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Copyright 2020 Google LLC
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+
15+
import pytest
16+
17+
18+
@pytest.fixture(scope="session")
19+
def lexer_class():
20+
from google.cloud.bigquery.ipython_magics.line_arg_parser.lexer import Lexer
21+
22+
return Lexer
23+
24+
25+
def test_empy_input(lexer_class):
26+
from google.cloud.bigquery.ipython_magics.line_arg_parser import TokenType
27+
from google.cloud.bigquery.ipython_magics.line_arg_parser.lexer import Token
28+
29+
lexer = lexer_class("")
30+
tokens = list(lexer)
31+
32+
assert tokens == [Token(TokenType.EOL, lexeme="", pos=0)]
+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Copyright 2020 Google LLC
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+
15+
import pytest
16+
17+
18+
@pytest.fixture(scope="session")
19+
def parser_class():
20+
from google.cloud.bigquery.ipython_magics.line_arg_parser.parser import Parser
21+
22+
return Parser
23+
24+
25+
def test_consume_expected_eol(parser_class):
26+
from google.cloud.bigquery.ipython_magics.line_arg_parser import TokenType
27+
from google.cloud.bigquery.ipython_magics.line_arg_parser.lexer import Token
28+
29+
# A simple iterable of Tokens is sufficient.
30+
fake_lexer = [Token(TokenType.EOL, lexeme="", pos=0)]
31+
parser = parser_class(fake_lexer)
32+
33+
parser.consume(TokenType.EOL) # no error
34+
35+
36+
def test_consume_unexpected_eol(parser_class):
37+
from google.cloud.bigquery.ipython_magics.line_arg_parser import ParseError
38+
from google.cloud.bigquery.ipython_magics.line_arg_parser import TokenType
39+
from google.cloud.bigquery.ipython_magics.line_arg_parser.lexer import Token
40+
41+
# A simple iterable of Tokens is sufficient.
42+
fake_lexer = [Token(TokenType.EOL, lexeme="", pos=0)]
43+
parser = parser_class(fake_lexer)
44+
45+
with pytest.raises(ParseError, match=r"Unexpected end of input.*expected COLON.*"):
46+
parser.consume(TokenType.COLON)
47+
48+
49+
def test_input_line_unexpected_input(parser_class):
50+
from google.cloud.bigquery.ipython_magics.line_arg_parser import ParseError
51+
from google.cloud.bigquery.ipython_magics.line_arg_parser import TokenType
52+
from google.cloud.bigquery.ipython_magics.line_arg_parser.lexer import Token
53+
54+
# A simple iterable of Tokens is sufficient.
55+
fake_lexer = [
56+
Token(TokenType.DEST_VAR, lexeme="results", pos=0),
57+
Token(TokenType.UNKNOWN, lexeme="boo!", pos=8),
58+
Token(TokenType.EOL, lexeme="", pos=12),
59+
]
60+
parser = parser_class(fake_lexer)
61+
62+
with pytest.raises(ParseError, match=r"Unexpected input.*position 8.*boo!.*"):
63+
parser.input_line()
64+
65+
66+
def test_destination_var_unexpected_input(parser_class):
67+
from google.cloud.bigquery.ipython_magics.line_arg_parser import ParseError
68+
from google.cloud.bigquery.ipython_magics.line_arg_parser import TokenType
69+
from google.cloud.bigquery.ipython_magics.line_arg_parser.lexer import Token
70+
71+
# A simple iterable of Tokens is sufficient.
72+
fake_lexer = [
73+
Token(TokenType.UNKNOWN, lexeme="@!#", pos=2),
74+
Token(TokenType.EOL, lexeme="", pos=5),
75+
]
76+
parser = parser_class(fake_lexer)
77+
78+
with pytest.raises(ParseError, match=r"Unknown.*position 2.*@!#.*"):
79+
parser.destination_var()
80+
81+
82+
def test_dict_items_empty_dict(parser_class):
83+
from google.cloud.bigquery.ipython_magics.line_arg_parser import TokenType
84+
from google.cloud.bigquery.ipython_magics.line_arg_parser.lexer import Token
85+
86+
# A simple iterable of Tokens is sufficient.
87+
fake_lexer = [Token(TokenType.RCURL, lexeme="}", pos=22)]
88+
parser = parser_class(fake_lexer)
89+
90+
result = parser.dict_items()
91+
92+
assert result == []
93+
94+
95+
def test_dict_items_trailing_comma(parser_class):
96+
from google.cloud.bigquery.ipython_magics.line_arg_parser import TokenType
97+
from google.cloud.bigquery.ipython_magics.line_arg_parser.lexer import Token
98+
99+
# A simple iterable of Tokens is sufficient.
100+
fake_lexer = [
101+
Token(TokenType.PY_STRING, lexeme="'age'", pos=10),
102+
Token(TokenType.COLON, lexeme=":", pos=17),
103+
Token(TokenType.PY_NUMBER, lexeme="18", pos=19),
104+
Token(TokenType.COMMA, lexeme=",", pos=21),
105+
Token(TokenType.RCURL, lexeme="}", pos=22),
106+
]
107+
parser = parser_class(fake_lexer)
108+
109+
result = parser.dict_items()
110+
111+
assert len(result) == 1
112+
dict_item = result[0]
113+
assert dict_item.key.key_value == "'age'"
114+
assert dict_item.value.raw_value == "18"
115+
116+
117+
def test_dict_item_unknown_input(parser_class):
118+
from google.cloud.bigquery.ipython_magics.line_arg_parser import ParseError
119+
from google.cloud.bigquery.ipython_magics.line_arg_parser import TokenType
120+
from google.cloud.bigquery.ipython_magics.line_arg_parser.lexer import Token
121+
122+
# A simple iterable of Tokens is sufficient.
123+
fake_lexer = [Token(TokenType.UNKNOWN, lexeme="#/%", pos=35)]
124+
parser = parser_class(fake_lexer)
125+
126+
with pytest.raises(ParseError, match=r"Unknown.*position 35.*#/%.*"):
127+
parser.dict_item()
128+
129+
130+
def test_pyvalue_list_containing_dict(parser_class):
131+
from google.cloud.bigquery.ipython_magics.line_arg_parser import TokenType
132+
from google.cloud.bigquery.ipython_magics.line_arg_parser.lexer import Token
133+
from google.cloud.bigquery.ipython_magics.line_arg_parser.parser import PyDict
134+
from google.cloud.bigquery.ipython_magics.line_arg_parser.parser import PyList
135+
136+
# A simple iterable of Tokens is sufficient.
137+
fake_lexer = [
138+
Token(TokenType.LSQUARE, lexeme="[", pos=21),
139+
Token(TokenType.LCURL, lexeme="{", pos=22),
140+
Token(TokenType.PY_STRING, lexeme="'age'", pos=23),
141+
Token(TokenType.COLON, lexeme=":", pos=28),
142+
Token(TokenType.PY_NUMBER, lexeme="18", pos=30),
143+
Token(TokenType.RCURL, lexeme="}", pos=32),
144+
Token(TokenType.COMMA, lexeme=",", pos=33), # trailing comma
145+
Token(TokenType.RSQUARE, lexeme="]", pos=34),
146+
Token(TokenType.EOL, lexeme="", pos=40),
147+
]
148+
parser = parser_class(fake_lexer)
149+
150+
result = parser.py_value()
151+
152+
assert isinstance(result, PyList)
153+
assert len(result.items) == 1
154+
155+
element = result.items[0]
156+
assert isinstance(element, PyDict)
157+
assert len(element.items) == 1
158+
159+
dict_item = element.items[0]
160+
assert dict_item.key.key_value == "'age'"
161+
assert dict_item.value.raw_value == "18"
162+
163+
164+
def test_pyvalue_invalid_token(parser_class):
165+
from google.cloud.bigquery.ipython_magics.line_arg_parser import ParseError
166+
from google.cloud.bigquery.ipython_magics.line_arg_parser import TokenType
167+
from google.cloud.bigquery.ipython_magics.line_arg_parser.lexer import Token
168+
169+
# A simple iterable of Tokens is sufficient.
170+
fake_lexer = [Token(TokenType.OPTION_SPEC, lexeme="--verbose", pos=75)]
171+
parser = parser_class(fake_lexer)
172+
173+
error_pattern = r"Unexpected token.*OPTION_SPEC.*position 75.*"
174+
with pytest.raises(ParseError, match=error_pattern):
175+
parser.py_value()
176+
177+
178+
def test_collection_items_empty(parser_class):
179+
from google.cloud.bigquery.ipython_magics.line_arg_parser import TokenType
180+
from google.cloud.bigquery.ipython_magics.line_arg_parser.lexer import Token
181+
182+
# A simple iterable of Tokens is sufficient.
183+
fake_lexer = [Token(TokenType.RPAREN, lexeme=")", pos=30)]
184+
parser = parser_class(fake_lexer)
185+
186+
result = parser.collection_items()
187+
188+
assert result == []
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright 2020 Google LLC
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+
15+
import pytest
16+
17+
18+
@pytest.fixture
19+
def base_visitor():
20+
from google.cloud.bigquery.ipython_magics.line_arg_parser.visitors import (
21+
NodeVisitor,
22+
)
23+
24+
return NodeVisitor()
25+
26+
27+
def test_unknown_node(base_visitor):
28+
from google.cloud.bigquery.ipython_magics.line_arg_parser.parser import ParseNode
29+
30+
class UnknownNode(ParseNode):
31+
pass
32+
33+
node = UnknownNode()
34+
35+
with pytest.raises(Exception, match=r"No visit_UnknownNode method"):
36+
base_visitor.visit(node)

0 commit comments

Comments
 (0)