xref: /aosp_15_r20/external/perfetto/python/test/stdlib_unittest.py (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1# Copyright (C) 2022 The Android Open Source Project
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
15import os
16import sys
17import unittest
18
19ROOT_DIR = os.path.dirname(
20    os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
21sys.path.append(os.path.join(ROOT_DIR))
22
23from python.generators.sql_processing.docs_parse import Arg, parse_file
24
25
26class TestStdlib(unittest.TestCase):
27
28  # Checks that custom prefixes (cr for chrome/util) are allowed.
29  def test_custom_module_prefix(self):
30    res = parse_file(
31        'chrome/util/test.sql', f'''
32-- Comment
33CREATE PERFETTO TABLE cr_table(
34    -- Column.
35    x LONG
36) AS
37SELECT 1;
38    '''.strip())
39    self.assertListEqual(res.errors, [])
40
41    fn = res.table_views[0]
42    self.assertEqual(fn.name, 'cr_table')
43    self.assertEqual(fn.desc, 'Comment')
44    self.assertEqual(fn.cols, {
45        'x': Arg('LONG', 'LONG', 'Column.', None),
46    })
47
48  # Checks that when custom prefixes (cr for chrome/util) are present,
49  # the full module name (chrome) is still accepted.
50  def test_custom_module_prefix_full_module_name(self):
51    res = parse_file(
52        'chrome/util/test.sql', f'''
53-- Comment
54CREATE PERFETTO TABLE chrome_table(
55    -- Column.
56    x LONG
57) AS
58SELECT 1;
59    '''.strip())
60    self.assertListEqual(res.errors, [])
61
62    fn = res.table_views[0]
63    self.assertEqual(fn.name, 'chrome_table')
64    self.assertEqual(fn.desc, 'Comment')
65    self.assertEqual(fn.cols, {
66        'x': Arg('LONG', 'LONG', 'Column.', None),
67    })
68
69  # Checks that when custom prefixes (cr for chrome/util) are present,
70  # the incorrect prefixes (foo) are not accepted.
71  def test_custom_module_prefix_incorrect(self):
72    res = parse_file(
73        'chrome/util/test.sql', f'''
74-- Comment
75CREATE PERFETTO TABLE foo_table(
76    -- Column.
77    x LONG
78) AS
79SELECT 1;
80    '''.strip())
81    # Expecting an error: table prefix (foo) is not allowed for a given path
82    # (allowed: chrome, cr).
83    self.assertEqual(len(res.errors), 1)
84
85  # Checks that when custom prefixes (cr for chrome/util) are present,
86  # they do not apply outside of the path scope.
87  def test_custom_module_prefix_does_not_apply_outside(self):
88    res = parse_file(
89        'foo/bar.sql', f'''
90-- Comment
91CREATE PERFETTO TABLE cr_table(
92    -- Column.
93    x LONG
94) AS
95SELECT 1;
96    '''.strip())
97    # Expecting an error: table prefix (foo) is not allowed for a given path
98    # (allowed: foo).
99    self.assertEqual(len(res.errors), 1)
100
101  def test_ret_no_desc(self):
102    res = parse_file(
103        'foo/bar.sql', f'''
104-- Comment
105CREATE PERFETTO FUNCTION foo_fn()
106--
107RETURNS BOOL
108AS
109SELECT TRUE;
110    '''.strip())
111    # Expecting an error: return value is missing a description.
112    self.assertEqual(len(res.errors), 1)
113
114  def test_multiline_desc(self):
115    res = parse_file(
116        'foo/bar.sql', f'''
117-- This
118-- is
119--
120-- a
121--      very
122--
123-- long
124--
125-- description.
126CREATE PERFETTO FUNCTION foo_fn()
127-- Exists.
128RETURNS BOOL
129AS
130SELECT 1;
131    '''.strip())
132    self.assertListEqual(res.errors, [])
133
134    fn = res.functions[0]
135    self.assertEqual(fn.desc,
136                     'This\n is\n\n a\n      very\n\n long\n\n description.')
137
138
139  def test_function_name_style(self):
140    res = parse_file(
141        'foo/bar.sql', f'''
142-- Function comment.
143CREATE PERFETTO FUNCTION foo_SnakeCase()
144-- Exists.
145RETURNS BOOL
146AS
147SELECT 1;
148    '''.strip())
149    # Expecting an error: function name should be using hacker_style.
150    self.assertEqual(len(res.errors), 1)
151
152  def test_table_with_schema(self):
153    res = parse_file(
154        'foo/bar.sql', f'''
155-- Table comment.
156CREATE PERFETTO TABLE foo_table(
157    -- Id of slice.
158    id LONG
159) AS
160SELECT 1 as id;
161    '''.strip())
162    self.assertListEqual(res.errors, [])
163
164    table = res.table_views[0]
165    self.assertEqual(table.name, 'foo_table')
166    self.assertEqual(table.desc, 'Table comment.')
167    self.assertEqual(table.type, 'TABLE')
168    self.assertEqual(table.cols, {
169        'id': Arg('LONG', 'LONG', 'Id of slice.', None),
170    })
171
172  def test_perfetto_view_with_schema(self):
173    res = parse_file(
174        'foo/bar.sql', f'''
175-- View comment.
176CREATE PERFETTO VIEW foo_table(
177    -- Foo.
178    foo LONG,
179    -- Bar.
180    bar STRING
181) AS
182SELECT 1;
183    '''.strip())
184    self.assertListEqual(res.errors, [])
185
186    table = res.table_views[0]
187    self.assertEqual(table.name, 'foo_table')
188    self.assertEqual(table.desc, 'View comment.')
189    self.assertEqual(table.type, 'VIEW')
190    self.assertEqual(
191        table.cols, {
192            'foo': Arg('LONG', 'LONG', 'Foo.', None),
193            'bar': Arg('STRING', 'STRING', 'Bar.', None),
194        })
195
196  def test_function_with_new_style_docs(self):
197    res = parse_file(
198        'foo/bar.sql', f'''
199-- Function foo.
200CREATE PERFETTO FUNCTION foo_fn(
201    -- Utid of thread.
202    utid LONG,
203    -- String name.
204    name STRING)
205-- Exists.
206RETURNS BOOL
207AS
208SELECT 1;
209    '''.strip())
210    self.assertListEqual(res.errors, [])
211
212    fn = res.functions[0]
213    self.assertEqual(fn.name, 'foo_fn')
214    self.assertEqual(fn.desc, 'Function foo.')
215    self.assertEqual(
216        fn.args, {
217            'utid': Arg('LONG', 'LONG', 'Utid of thread.', None),
218            'name': Arg('STRING', 'STRING', 'String name.', None),
219        })
220    self.assertEqual(fn.return_type, 'BOOL')
221    self.assertEqual(fn.return_desc, 'Exists.')
222
223  def test_function_returns_table_with_new_style_docs(self):
224    res = parse_file(
225        'foo/bar.sql', f'''
226-- Function foo.
227CREATE PERFETTO FUNCTION foo_fn(
228    -- Utid of thread.
229    utid LONG)
230-- Impl comment.
231RETURNS TABLE(
232    -- Count.
233    count LONG
234)
235AS
236SELECT 1;
237    '''.strip())
238    self.assertListEqual(res.errors, [])
239
240    fn = res.table_functions[0]
241    self.assertEqual(fn.name, 'foo_fn')
242    self.assertEqual(fn.desc, 'Function foo.')
243    self.assertEqual(fn.args, {
244        'utid': Arg('LONG', 'LONG', 'Utid of thread.', None),
245    })
246    self.assertEqual(fn.cols, {
247        'count': Arg('LONG', 'LONG', 'Count.', None),
248    })
249
250  def test_function_with_new_style_docs_multiline_comment(self):
251    res = parse_file(
252        'foo/bar.sql', f'''
253-- Function foo.
254CREATE PERFETTO FUNCTION foo_fn(
255    -- Multi
256    -- line
257    --
258    -- comment.
259    arg LONG)
260-- Exists.
261RETURNS BOOL
262AS
263SELECT 1;
264    '''.strip())
265    self.assertListEqual(res.errors, [])
266
267    fn = res.functions[0]
268    self.assertEqual(fn.name, 'foo_fn')
269    self.assertEqual(fn.desc, 'Function foo.')
270    self.assertEqual(fn.args, {
271        'arg': Arg('LONG', 'LONG', 'Multi line  comment.', None),
272    })
273    self.assertEqual(fn.return_type, 'BOOL')
274    self.assertEqual(fn.return_desc, 'Exists.')
275
276  def test_function_with_multiline_return_comment(self):
277    res = parse_file(
278        'foo/bar.sql', f'''
279-- Function foo.
280CREATE PERFETTO FUNCTION foo_fn(
281    -- Arg
282    arg LONG)
283-- Multi
284-- line
285-- return
286-- comment.
287RETURNS BOOL
288AS
289SELECT 1;
290    '''.strip())
291    self.assertListEqual(res.errors, [])
292
293    fn = res.functions[0]
294    self.assertEqual(fn.name, 'foo_fn')
295    self.assertEqual(fn.desc, 'Function foo.')
296    self.assertEqual(fn.args, {
297        'arg': Arg('LONG', 'LONG', 'Arg', None),
298    })
299    self.assertEqual(fn.return_type, 'BOOL')
300    self.assertEqual(fn.return_desc, 'Multi line return comment.')
301
302  def test_create_or_replace_table_banned(self):
303    res = parse_file(
304        'common/bar.sql', f'''
305-- Table.
306CREATE OR REPLACE PERFETTO TABLE foo(
307    -- Column.
308    x LONG
309)
310AS
311SELECT 1;
312
313    '''.strip())
314    # Expecting an error: CREATE OR REPLACE is not allowed in stdlib.
315    self.assertEqual(len(res.errors), 1)
316
317  def test_create_or_replace_view_banned(self):
318    res = parse_file(
319        'common/bar.sql', f'''
320-- Table.
321CREATE OR REPLACE PERFETTO VIEW foo(
322    -- Column.
323    x LONG
324)
325AS
326SELECT 1;
327
328    '''.strip())
329    # Expecting an error: CREATE OR REPLACE is not allowed in stdlib.
330    self.assertEqual(len(res.errors), 1)
331
332  def test_create_or_replace_function_banned(self):
333    res = parse_file(
334        'foo/bar.sql', f'''
335-- Function foo.
336CREATE OR REPLACE PERFETTO FUNCTION foo_fn()
337-- Exists.
338RETURNS BOOL
339AS
340SELECT 1;
341    '''.strip())
342    # Expecting an error: CREATE OR REPLACE is not allowed in stdlib.
343    self.assertEqual(len(res.errors), 1)
344
345  def test_function_with_new_style_docs_with_parenthesis(self):
346    res = parse_file(
347        'foo/bar.sql', f'''
348-- Function foo.
349CREATE PERFETTO FUNCTION foo_fn(
350    -- Utid of thread (important).
351    utid LONG)
352-- Exists.
353RETURNS BOOL
354AS
355SELECT 1;
356    '''.strip())
357    self.assertListEqual(res.errors, [])
358
359    fn = res.functions[0]
360    self.assertEqual(fn.name, 'foo_fn')
361    self.assertEqual(fn.desc, 'Function foo.')
362    self.assertEqual(fn.args, {
363        'utid': Arg('LONG', 'LONG', 'Utid of thread (important).', None),
364    })
365    self.assertEqual(fn.return_type, 'BOOL')
366    self.assertEqual(fn.return_desc, 'Exists.')
367
368  def test_macro(self):
369    res = parse_file(
370        'foo/bar.sql', f'''
371-- Macro
372CREATE OR REPLACE PERFETTO FUNCTION foo_fn()
373-- Exists.
374RETURNS BOOL
375AS
376SELECT 1;
377    '''.strip())
378    # Expecting an error: CREATE OR REPLACE is not allowed in stdlib.
379    self.assertEqual(len(res.errors), 1)
380
381  def test_create_or_replace_macro_smoke(self):
382    res = parse_file(
383        'foo/bar.sql', f'''
384-- Macro
385CREATE PERFETTO MACRO foo_macro(
386  -- x Arg.
387  x TableOrSubquery
388)
389-- Exists.
390RETURNS TableOrSubquery
391AS
392SELECT 1;
393    '''.strip())
394
395    macro = res.macros[0]
396    self.assertEqual(macro.name, 'foo_macro')
397    self.assertEqual(macro.desc, 'Macro')
398    self.assertEqual(macro.args, {
399        'x': Arg('TableOrSubquery', 'TableOrSubquery', 'x Arg.', None),
400    })
401    self.assertEqual(macro.return_type, 'TableOrSubquery')
402    self.assertEqual(macro.return_desc, 'Exists.')
403
404  def test_create_or_replace_macro_banned(self):
405    res = parse_file(
406        'foo/bar.sql', f'''
407-- Macro
408CREATE OR REPLACE PERFETTO MACRO foo_macro(
409  -- x Arg.
410  x TableOrSubquery
411)
412-- Exists.
413RETURNS TableOrSubquery
414AS
415SELECT 1;
416    '''.strip())
417    # Expecting an error: CREATE OR REPLACE is not allowed in stdlib.
418    self.assertEqual(len(res.errors), 1)
419