• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  def test_valid_table(self):
29    res = parse_file(
30        'foo/bar.sql', f'''
31-- First line.
32-- Second line.
33-- @column slice_id           Id of slice.
34-- @column slice_name         Name of slice.
35CREATE TABLE foo_table AS
36SELECT 1;
37    '''.strip())
38    self.assertListEqual(res.errors, [])
39
40    table = res.table_views[0]
41    self.assertEqual(table.name, 'foo_table')
42    self.assertEqual(table.desc, 'First line.\n Second line.')
43    self.assertEqual(table.type, 'TABLE')
44    self.assertEqual(
45        table.cols, {
46            'slice_id': Arg(None, 'Id of slice.'),
47            'slice_name': Arg(None, 'Name of slice.'),
48        })
49
50  def test_valid_function(self):
51    res = parse_file(
52        'foo/bar.sql', f'''
53-- First line.
54-- Second line.
55-- @arg utid INT              Utid of thread.
56-- @arg name STRING           String name.
57CREATE PERFETTO FUNCTION foo_fn(utid INT, name STRING)
58-- Exists.
59RETURNS BOOL
60AS
61SELECT 1;
62    '''.strip())
63    self.assertListEqual(res.errors, [])
64
65    fn = res.functions[0]
66    self.assertEqual(fn.name, 'foo_fn')
67    self.assertEqual(fn.desc, 'First line.\n Second line.')
68    self.assertEqual(
69        fn.args, {
70            'utid': Arg('INT', 'Utid of thread.'),
71            'name': Arg('STRING', 'String name.'),
72        })
73    self.assertEqual(fn.return_type, 'BOOL')
74    self.assertEqual(fn.return_desc, 'Exists.')
75
76  def test_valid_table_function(self):
77    res = parse_file(
78        'foo/bar.sql', f'''
79-- Table comment.
80-- @arg utid INT              Utid of thread.
81-- @arg name STRING           String name.
82-- @column slice_id           Id of slice.
83-- @column slice_name         Name of slice.
84CREATE PERFETTO FUNCTION foo_view_fn(utid INT, name STRING)
85RETURNS TABLE(slice_id INT, slice_name STRING)
86AS SELECT 1;
87    '''.strip())
88    self.assertListEqual(res.errors, [])
89
90    fn = res.table_functions[0]
91    self.assertEqual(fn.name, 'foo_view_fn')
92    self.assertEqual(fn.desc, 'Table comment.')
93    self.assertEqual(
94        fn.args, {
95            'utid': Arg('INT', 'Utid of thread.'),
96            'name': Arg('STRING', 'String name.'),
97        })
98    self.assertEqual(
99        fn.cols, {
100            'slice_id': Arg('INT', 'Id of slice.'),
101            'slice_name': Arg('STRING', 'Name of slice.'),
102        })
103
104  def test_missing_module_name(self):
105    res = parse_file(
106        'foo/bar.sql', f'''
107-- Comment
108-- @column slice_id           Id of slice.
109CREATE TABLE bar_table AS
110SELECT 1;
111    '''.strip())
112    # Expecting an error: function prefix (bar) not matching module name (foo).
113    self.assertEqual(len(res.errors), 1)
114
115  # Checks that custom prefixes (cr for chrome/util) are allowed.
116  def test_custom_module_prefix(self):
117    res = parse_file(
118        'chrome/util/test.sql', f'''
119-- Comment
120CREATE PERFETTO TABLE cr_table(
121    -- Column.
122    x INT
123) AS
124SELECT 1;
125    '''.strip())
126    self.assertListEqual(res.errors, [])
127
128    fn = res.table_views[0]
129    self.assertEqual(fn.name, 'cr_table')
130    self.assertEqual(fn.desc, 'Comment')
131    self.assertEqual(fn.cols, {
132        'x': Arg('INT', 'Column.'),
133    })
134
135  # Checks that when custom prefixes (cr for chrome/util) are present,
136  # the full module name (chrome) is still accepted.
137  def test_custom_module_prefix_full_module_name(self):
138    res = parse_file(
139        'chrome/util/test.sql', f'''
140-- Comment
141CREATE PERFETTO TABLE chrome_table(
142    -- Column.
143    x INT
144) AS
145SELECT 1;
146    '''.strip())
147    self.assertListEqual(res.errors, [])
148
149    fn = res.table_views[0]
150    self.assertEqual(fn.name, 'chrome_table')
151    self.assertEqual(fn.desc, 'Comment')
152    self.assertEqual(fn.cols, {
153        'x': Arg('INT', 'Column.'),
154    })
155
156  # Checks that when custom prefixes (cr for chrome/util) are present,
157  # the incorrect prefixes (foo) are not accepted.
158  def test_custom_module_prefix_incorrect(self):
159    res = parse_file(
160        'chrome/util/test.sql', f'''
161-- Comment
162CREATE PERFETTO TABLE foo_table(
163    -- Column.
164    x INT
165) AS
166SELECT 1;
167    '''.strip())
168    # Expecting an error: table prefix (foo) is not allowed for a given path
169    # (allowed: chrome, cr).
170    self.assertEqual(len(res.errors), 1)
171
172  # Checks that when custom prefixes (cr for chrome/util) are present,
173  # they do not apply outside of the path scope.
174  def test_custom_module_prefix_does_not_apply_outside(self):
175    res = parse_file(
176        'foo/bar.sql', f'''
177-- Comment
178CREATE PERFETTO TABLE cr_table(
179    -- Column.
180    x INT
181) AS
182SELECT 1;
183    '''.strip())
184    # Expecting an error: table prefix (foo) is not allowed for a given path
185    # (allowed: foo).
186    self.assertEqual(len(res.errors), 1)
187
188  def test_common_does_not_include_module_name(self):
189    res = parse_file(
190        'common/bar.sql', f'''
191-- Comment.
192-- @column slice_id           Id of slice.
193CREATE TABLE common_table AS
194SELECT 1;
195    '''.strip())
196    # Expecting an error: functions in common/ should not have a module prefix.
197    self.assertEqual(len(res.errors), 1)
198
199  def test_cols_typo(self):
200    res = parse_file(
201        'foo/bar.sql', f'''
202-- Comment.
203--
204-- @column slice_id2          Foo.
205-- @column slice_name         Bar.
206CREATE TABLE bar_table AS
207SELECT 1;
208    '''.strip())
209    # Expecting an error: column slice_id2 not found in the table.
210    self.assertEqual(len(res.errors), 1)
211
212  def test_cols_no_desc(self):
213    res = parse_file(
214        'foo/bar.sql', f'''
215-- Comment.
216--
217-- @column slice_id
218-- @column slice_name         Bar.
219CREATE TABLE bar_table AS
220SELECT 1;
221    '''.strip())
222    # Expecting an error: column slice_id is missing a description.
223    self.assertEqual(len(res.errors), 1)
224
225  def test_args_typo(self):
226    res = parse_file(
227        'foo/bar.sql', f'''
228-- Comment.
229--
230-- @arg utid2 INT             Uint.
231-- @arg name STRING           String name.
232CREATE PERFETTO FUNCTION foo_fn(utid INT, name STRING)
233-- Exists.
234RETURNS BOOL
235AS
236SELECT 1;
237    '''.strip())
238    # Expecting 2 errors:
239    # - arg utid2 not found in the function (should be utid);
240    # - utid not documented.
241    self.assertEqual(len(res.errors), 2)
242
243  def test_args_no_desc(self):
244    res = parse_file(
245        'foo/bar.sql', f'''
246-- Comment.
247--
248-- @arg utid INT
249-- @arg name STRING           String name.
250CREATE PERFETTO FUNCTION foo_fn(utid INT, name STRING)
251-- Exists.
252RETURNS BOOL
253AS
254SELECT 1;
255    '''.strip())
256    # Expecting 2 errors:
257    # - arg utid is missing a description;
258    # - arg utid is not documented.
259    self.assertEqual(len(res.errors), 2)
260
261  def test_ret_no_desc(self):
262    res = parse_file(
263        'foo/bar.sql', f'''
264-- Comment
265CREATE PERFETTO FUNCTION foo_fn()
266--
267RETURNS BOOL
268AS
269SELECT TRUE;
270    '''.strip())
271    # Expecting an error: return value is missing a description.
272    self.assertEqual(len(res.errors), 1)
273
274  def test_multiline_desc(self):
275    res = parse_file(
276        'foo/bar.sql', f'''
277-- This
278-- is
279--
280-- a
281--      very
282--
283-- long
284--
285-- description.
286CREATE PERFETTO FUNCTION foo_fn()
287-- Exists.
288RETURNS BOOL
289AS
290SELECT 1;
291    '''.strip())
292    self.assertListEqual(res.errors, [])
293
294    fn = res.functions[0]
295    self.assertEqual(fn.desc,
296                     'This\n is\n\n a\n      very\n\n long\n\n description.')
297
298  def test_multiline_arg_desc(self):
299    res = parse_file(
300        'foo/bar.sql', f'''
301-- Comment.
302--
303-- @arg utid INT              Uint
304-- spread
305--
306-- across lines.
307-- @arg name STRING            String name
308--                             which spans across multiple lines
309-- inconsistently.
310CREATE PERFETTO FUNCTION foo_fn(utid INT, name STRING)
311-- Exists.
312RETURNS BOOL
313AS
314SELECT 1;
315    '''.strip())
316
317    fn = res.functions[0]
318    self.assertEqual(
319        fn.args, {
320            'utid':
321                Arg('INT', 'Uint spread  across lines.'),
322            'name':
323                Arg(
324                    'STRING', 'String name which spans across multiple lines '
325                    'inconsistently.'),
326        })
327
328  def test_function_name_style(self):
329    res = parse_file(
330        'foo/bar.sql', f'''
331-- Function comment.
332CREATE PERFETTO FUNCTION foo_SnakeCase()
333-- Exists.
334RETURNS BOOL
335AS
336SELECT 1;
337    '''.strip())
338    # Expecting an error: function name should be using hacker_style.
339    self.assertEqual(len(res.errors), 1)
340
341  def test_table_with_schema(self):
342    res = parse_file(
343        'foo/bar.sql', f'''
344-- Table comment.
345CREATE PERFETTO TABLE foo_table(
346    -- Id of slice.
347    id INT
348) AS
349SELECT 1 as id;
350    '''.strip())
351    self.assertListEqual(res.errors, [])
352
353    table = res.table_views[0]
354    self.assertEqual(table.name, 'foo_table')
355    self.assertEqual(table.desc, 'Table comment.')
356    self.assertEqual(table.type, 'TABLE')
357    self.assertEqual(table.cols, {
358        'id': Arg('INT', 'Id of slice.'),
359    })
360
361  def test_perfetto_view_with_schema(self):
362    res = parse_file(
363        'foo/bar.sql', f'''
364-- View comment.
365CREATE PERFETTO VIEW foo_table(
366    -- Foo.
367    foo INT,
368    -- Bar.
369    bar STRING
370) AS
371SELECT 1;
372    '''.strip())
373    self.assertListEqual(res.errors, [])
374
375    table = res.table_views[0]
376    self.assertEqual(table.name, 'foo_table')
377    self.assertEqual(table.desc, 'View comment.')
378    self.assertEqual(table.type, 'VIEW')
379    self.assertEqual(table.cols, {
380        'foo': Arg('INT', 'Foo.'),
381        'bar': Arg('STRING', 'Bar.'),
382    })
383
384  def test_function_with_new_style_docs(self):
385    res = parse_file(
386        'foo/bar.sql', f'''
387-- Function foo.
388CREATE PERFETTO FUNCTION foo_fn(
389    -- Utid of thread.
390    utid INT,
391    -- String name.
392    name STRING)
393-- Exists.
394RETURNS BOOL
395AS
396SELECT 1;
397    '''.strip())
398    self.assertListEqual(res.errors, [])
399
400    fn = res.functions[0]
401    self.assertEqual(fn.name, 'foo_fn')
402    self.assertEqual(fn.desc, 'Function foo.')
403    self.assertEqual(
404        fn.args, {
405            'utid': Arg('INT', 'Utid of thread.'),
406            'name': Arg('STRING', 'String name.'),
407        })
408    self.assertEqual(fn.return_type, 'BOOL')
409    self.assertEqual(fn.return_desc, 'Exists.')
410
411  def test_function_returns_table_with_new_style_docs(self):
412    res = parse_file(
413        'foo/bar.sql', f'''
414-- Function foo.
415CREATE PERFETTO FUNCTION foo_fn(
416    -- Utid of thread.
417    utid INT)
418-- Impl comment.
419RETURNS TABLE(
420    -- Count.
421    count INT
422)
423AS
424SELECT 1;
425    '''.strip())
426    self.assertListEqual(res.errors, [])
427
428    fn = res.table_functions[0]
429    self.assertEqual(fn.name, 'foo_fn')
430    self.assertEqual(fn.desc, 'Function foo.')
431    self.assertEqual(fn.args, {
432        'utid': Arg('INT', 'Utid of thread.'),
433    })
434    self.assertEqual(fn.cols, {
435        'count': Arg('INT', 'Count.'),
436    })
437
438  def test_function_with_new_style_docs_multiline_comment(self):
439    res = parse_file(
440        'foo/bar.sql', f'''
441-- Function foo.
442CREATE PERFETTO FUNCTION foo_fn(
443    -- Multi
444    -- line
445    --
446    -- comment.
447    arg INT)
448-- Exists.
449RETURNS BOOL
450AS
451SELECT 1;
452    '''.strip())
453    self.assertListEqual(res.errors, [])
454
455    fn = res.functions[0]
456    self.assertEqual(fn.name, 'foo_fn')
457    self.assertEqual(fn.desc, 'Function foo.')
458    self.assertEqual(fn.args, {
459        'arg': Arg('INT', 'Multi line  comment.'),
460    })
461    self.assertEqual(fn.return_type, 'BOOL')
462    self.assertEqual(fn.return_desc, 'Exists.')
463
464  def test_function_with_multiline_return_comment(self):
465    res = parse_file(
466        'foo/bar.sql', f'''
467-- Function foo.
468CREATE PERFETTO FUNCTION foo_fn(
469    -- Arg
470    arg INT)
471-- Multi
472-- line
473-- return
474-- comment.
475RETURNS BOOL
476AS
477SELECT 1;
478    '''.strip())
479    self.assertListEqual(res.errors, [])
480
481    fn = res.functions[0]
482    self.assertEqual(fn.name, 'foo_fn')
483    self.assertEqual(fn.desc, 'Function foo.')
484    self.assertEqual(fn.args, {
485        'arg': Arg('INT', 'Arg'),
486    })
487    self.assertEqual(fn.return_type, 'BOOL')
488    self.assertEqual(fn.return_desc, 'Multi line return comment.')
489
490  def test_create_or_replace_table_banned(self):
491    res = parse_file(
492        'common/bar.sql', f'''
493-- Table.
494CREATE OR REPLACE PERFETTO TABLE foo(
495    -- Column.
496    x INT
497)
498AS
499SELECT 1;
500
501    '''.strip())
502    # Expecting an error: CREATE OR REPLACE is not allowed in stdlib.
503    self.assertEqual(len(res.errors), 1)
504
505  def test_create_or_replace_view_banned(self):
506    res = parse_file(
507        'common/bar.sql', f'''
508-- Table.
509CREATE OR REPLACE PERFETTO VIEW foo(
510    -- Column.
511    x INT
512)
513AS
514SELECT 1;
515
516    '''.strip())
517    # Expecting an error: CREATE OR REPLACE is not allowed in stdlib.
518    self.assertEqual(len(res.errors), 1)
519
520  def test_create_or_replace_function_banned(self):
521    res = parse_file(
522        'foo/bar.sql', f'''
523-- Function foo.
524CREATE OR REPLACE PERFETTO FUNCTION foo_fn()
525-- Exists.
526RETURNS BOOL
527AS
528SELECT 1;
529    '''.strip())
530    # Expecting an error: CREATE OR REPLACE is not allowed in stdlib.
531    self.assertEqual(len(res.errors), 1)
532
533  def test_function_with_new_style_docs_with_parenthesis(self):
534    res = parse_file(
535        'foo/bar.sql', f'''
536-- Function foo.
537CREATE PERFETTO FUNCTION foo_fn(
538    -- Utid of thread (important).
539    utid INT)
540-- Exists.
541RETURNS BOOL
542AS
543SELECT 1;
544    '''.strip())
545    self.assertListEqual(res.errors, [])
546
547    fn = res.functions[0]
548    self.assertEqual(fn.name, 'foo_fn')
549    self.assertEqual(fn.desc, 'Function foo.')
550    self.assertEqual(fn.args, {
551        'utid': Arg('INT', 'Utid of thread (important).'),
552    })
553    self.assertEqual(fn.return_type, 'BOOL')
554    self.assertEqual(fn.return_desc, 'Exists.')
555
556  def test_macro(self):
557    res = parse_file(
558        'foo/bar.sql', f'''
559-- Macro
560CREATE OR REPLACE PERFETTO FUNCTION foo_fn()
561-- Exists.
562RETURNS BOOL
563AS
564SELECT 1;
565    '''.strip())
566    # Expecting an error: CREATE OR REPLACE is not allowed in stdlib.
567    self.assertEqual(len(res.errors), 1)
568
569  def test_create_or_replace_macro_smoke(self):
570    res = parse_file(
571        'foo/bar.sql', f'''
572-- Macro
573CREATE PERFETTO MACRO foo_macro(
574  -- x Arg.
575  x TableOrSubquery
576)
577-- Exists.
578RETURNS TableOrSubquery
579AS
580SELECT 1;
581    '''.strip())
582
583    macro = res.macros[0]
584    self.assertEqual(macro.name, 'foo_macro')
585    self.assertEqual(macro.desc, 'Macro')
586    self.assertEqual(macro.args, {
587        'x': Arg('TableOrSubquery', 'x Arg.'),
588    })
589    self.assertEqual(macro.return_type, 'TableOrSubquery')
590    self.assertEqual(macro.return_desc, 'Exists.')
591
592  def test_create_or_replace_macro_banned(self):
593    res = parse_file(
594        'foo/bar.sql', f'''
595-- Macro
596CREATE OR REPLACE PERFETTO MACRO foo_macro(
597  -- x Arg.
598  x TableOrSubquery
599)
600-- Exists.
601RETURNS TableOrSubquery
602AS
603SELECT 1;
604    '''.strip())
605    # Expecting an error: CREATE OR REPLACE is not allowed in stdlib.
606    self.assertEqual(len(res.errors), 1)
607