| // Copyright 2023 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package python | |
| import ( | |
| "context" | |
| "testing" | |
| "github.com/stretchr/testify/assert" | |
| ) | |
| func TestParseImportStatements(t *testing.T) { | |
| t.Parallel() | |
| units := []struct { | |
| name string | |
| code string | |
| filepath string | |
| result []module | |
| }{ | |
| { | |
| name: "not has import", | |
| code: "a = 1\nb = 2", | |
| filepath: "", | |
| result: nil, | |
| }, | |
| { | |
| name: "has import", | |
| code: "import unittest\nimport os.path\nfrom foo.bar import abc.xyz", | |
| filepath: "abc.py", | |
| result: []module{ | |
| { | |
| Name: "unittest", | |
| LineNumber: 1, | |
| Filepath: "abc.py", | |
| From: "", | |
| }, | |
| { | |
| Name: "os.path", | |
| LineNumber: 2, | |
| Filepath: "abc.py", | |
| From: "", | |
| }, | |
| { | |
| Name: "foo.bar.abc.xyz", | |
| LineNumber: 3, | |
| Filepath: "abc.py", | |
| From: "foo.bar", | |
| }, | |
| }, | |
| }, | |
| { | |
| name: "has import in def", | |
| code: `def foo(): | |
| import unittest | |
| `, | |
| filepath: "abc.py", | |
| result: []module{ | |
| { | |
| Name: "unittest", | |
| LineNumber: 2, | |
| Filepath: "abc.py", | |
| From: "", | |
| }, | |
| }, | |
| }, | |
| { | |
| name: "invalid syntax", | |
| code: "import os\nimport", | |
| filepath: "abc.py", | |
| result: []module{ | |
| { | |
| Name: "os", | |
| LineNumber: 1, | |
| Filepath: "abc.py", | |
| From: "", | |
| }, | |
| }, | |
| }, | |
| { | |
| name: "import as", | |
| code: "import os as b\nfrom foo import bar as c# 123", | |
| filepath: "abc.py", | |
| result: []module{ | |
| { | |
| Name: "os", | |
| LineNumber: 1, | |
| Filepath: "abc.py", | |
| From: "", | |
| }, | |
| { | |
| Name: "foo.bar", | |
| LineNumber: 2, | |
| Filepath: "abc.py", | |
| From: "foo", | |
| }, | |
| }, | |
| }, | |
| // align to https://docs.python.org/3/reference/simple_stmts.html#index-34 | |
| { | |
| name: "complex import", | |
| code: "from unittest import *\nfrom foo import (bar as c, baz, qux as d)\nfrom . import abc", | |
| result: []module{ | |
| { | |
| Name: "unittest.*", | |
| LineNumber: 1, | |
| From: "unittest", | |
| }, | |
| { | |
| Name: "foo.bar", | |
| LineNumber: 2, | |
| From: "foo", | |
| }, | |
| { | |
| Name: "foo.baz", | |
| LineNumber: 2, | |
| From: "foo", | |
| }, | |
| { | |
| Name: "foo.qux", | |
| LineNumber: 2, | |
| From: "foo", | |
| }, | |
| }, | |
| }, | |
| } | |
| for _, u := range units { | |
| t.Run(u.name, func(t *testing.T) { | |
| p := NewFileParser() | |
| code := []byte(u.code) | |
| p.SetCodeAndFile(code, "", u.filepath) | |
| output, err := p.Parse(context.Background()) | |
| assert.NoError(t, err) | |
| assert.Equal(t, u.result, output.Modules) | |
| }) | |
| } | |
| } | |
| func TestParseComments(t *testing.T) { | |
| t.Parallel() | |
| units := []struct { | |
| name string | |
| code string | |
| result []comment | |
| }{ | |
| { | |
| name: "not has comment", | |
| code: "a = 1\nb = 2", | |
| result: nil, | |
| }, | |
| { | |
| name: "has comment", | |
| code: "# a = 1\n# b = 2", | |
| result: []comment{"# a = 1", "# b = 2"}, | |
| }, | |
| { | |
| name: "has comment in if", | |
| code: "if True:\n # a = 1\n # b = 2", | |
| result: []comment{"# a = 1", "# b = 2"}, | |
| }, | |
| { | |
| name: "has comment inline", | |
| code: "import os# 123\nfrom pathlib import Path as b#456", | |
| result: []comment{"# 123", "#456"}, | |
| }, | |
| } | |
| for _, u := range units { | |
| t.Run(u.name, func(t *testing.T) { | |
| p := NewFileParser() | |
| code := []byte(u.code) | |
| p.SetCodeAndFile(code, "", "") | |
| output, err := p.Parse(context.Background()) | |
| assert.NoError(t, err) | |
| assert.Equal(t, u.result, output.Comments) | |
| }) | |
| } | |
| } | |
| func TestParseMain(t *testing.T) { | |
| t.Parallel() | |
| units := []struct { | |
| name string | |
| code string | |
| result bool | |
| }{ | |
| { | |
| name: "not has main", | |
| code: "a = 1\nb = 2", | |
| result: false, | |
| }, | |
| { | |
| name: "has main in function", | |
| code: `def foo(): | |
| if __name__ == "__main__": | |
| a = 3 | |
| `, | |
| result: false, | |
| }, | |
| { | |
| name: "has main", | |
| code: ` | |
| import unittest | |
| from lib import main | |
| class ExampleTest(unittest.TestCase): | |
| def test_main(self): | |
| self.assertEqual( | |
| "", | |
| main([["A", 1], ["B", 2]]), | |
| ) | |
| if __name__ == "__main__": | |
| unittest.main() | |
| `, | |
| result: true, | |
| }, | |
| } | |
| for _, u := range units { | |
| t.Run(u.name, func(t *testing.T) { | |
| p := NewFileParser() | |
| code := []byte(u.code) | |
| p.SetCodeAndFile(code, "", "") | |
| output, err := p.Parse(context.Background()) | |
| assert.NoError(t, err) | |
| assert.Equal(t, u.result, output.HasMain) | |
| }) | |
| } | |
| } | |
| func TestParseFull(t *testing.T) { | |
| p := NewFileParser() | |
| code := []byte(`from bar import abc`) | |
| p.SetCodeAndFile(code, "foo", "a.py") | |
| output, err := p.Parse(context.Background()) | |
| assert.NoError(t, err) | |
| assert.Equal(t, ParserOutput{ | |
| Modules: []module{{Name: "bar.abc", LineNumber: 1, Filepath: "foo/a.py", From: "bar"}}, | |
| Comments: nil, | |
| HasMain: false, | |
| FileName: "a.py", | |
| }, *output) | |
| } |