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