• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2023 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://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, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Tests for the pw_build.gn_writer module."""
15
16import os
17import unittest
18
19from io import StringIO
20from pathlib import PurePath
21from tempfile import TemporaryDirectory
22
23from pw_build.gn_config import GnConfig
24from pw_build.gn_writer import (
25    COPYRIGHT_HEADER,
26    GnFile,
27    GnWriter,
28)
29from pw_build.gn_target import GnTarget
30from pw_build.gn_utils import MalformedGnError
31
32
33class TestGnWriter(unittest.TestCase):
34    """Tests for gn_writer.GnWriter."""
35
36    def setUp(self):
37        """Creates a GnWriter that writes to a StringIO."""
38        self.reset()
39
40    def reset(self):
41        """Resets the writer and output."""
42        self.output = StringIO()
43        self.writer = GnWriter(self.output)
44
45    def test_write_comment(self):
46        """Writes a GN comment."""
47        self.writer.write_comment('hello, world!')
48        self.assertEqual(
49            self.output.getvalue(),
50            '# hello, world!\n',
51        )
52
53    def test_write_comment_wrap(self):
54        """Writes a GN comment that is exactly 80 characters."""
55        extra_long = (
56            "This line is a " + ("really, " * 5) + "REALLY extra long comment"
57        )
58        self.writer.write_comment(extra_long)
59        self.assertEqual(
60            self.output.getvalue(),
61            '# This line is a really, really, really, really, really, REALLY '
62            'extra long\n# comment\n',
63        )
64
65    def test_write_comment_nowrap(self):
66        """Writes a long GN comment without whitespace to wrap on."""
67        no_breaks = 'A' + ('a' * 76) + 'h!'
68        self.writer.write_comment(no_breaks)
69        self.assertEqual(
70            self.output.getvalue(),
71            '# Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
72            'aaaaaaaaaaaaah!\n',
73        )
74
75    def test_write_imports(self):
76        """Writes GN import statements."""
77        self.writer.write_import('foo.gni')
78        self.writer.write_imports(['bar.gni', 'baz.gni'])
79        lines = [
80            'import("foo.gni")',
81            'import("bar.gni")',
82            'import("baz.gni")',
83        ]
84        self.assertEqual('\n'.join(lines), self.output.getvalue().strip())
85
86    def test_write_config(self):
87        """Writes a GN config."""
88        config = GnConfig(
89            json='''{
90            "label": "$dir_3p/test:my-config",
91            "cflags": ["-frobinator", "-fizzbuzzer"],
92            "defines": ["KEY=VAL"],
93            "public": true,
94            "usages": 1
95        }'''
96        )
97        self.writer.write_config(config)
98        lines = [
99            'config("my-config") {',
100            '  cflags = [',
101            '    "-fizzbuzzer",',
102            '    "-frobinator",',
103            '  ]',
104            '  defines = [',
105            '    "KEY=VAL",',
106            '  ]',
107            '}',
108        ]
109        self.assertEqual('\n'.join(lines), self.output.getvalue().strip())
110
111    def test_write_target(self):
112        """Tests writing the target using a GnWriter."""
113        target = GnTarget(
114            '$build',
115            '$src',
116            json='''{
117              "target_type": "custom_type",
118              "target_name": "my-target",
119              "package": "my-package"
120            }''',
121        )
122        target.add_visibility(bazel='//visibility:private')
123        target.add_visibility(bazel='//foo:__subpackages__')
124        target.add_path('public', bazel='//foo:my-header.h')
125        target.add_path('sources', bazel='//foo:my-source.cc')
126        target.add_path('inputs', bazel='//bar:my.data')
127        target.config.add('cflags', '-frobinator')
128        target.add_dep(public=True, bazel='//my-package:foo')
129        target.add_dep(public=True, bazel='@com_corp_repo//bar')
130        target.add_dep(bazel='//other-pkg/baz')
131        target.add_dep(bazel='@com_corp_repo//:top-level')
132
133        output = StringIO()
134        writer = GnWriter(output)
135        writer.repos = {'com_corp_repo': 'repo'}
136        writer.aliases = {'$build/other-pkg/baz': '$build/another-pkg/baz'}
137        writer.write_target(target)
138
139        self.assertEqual(
140            output.getvalue(),
141            '''
142# Generated from //my-package:my-target
143custom_type("my-target") {
144  visibility = [
145    "../foo/*",
146    ":*",
147  ]
148  public = [
149    "$src/foo/my-header.h",
150  ]
151  sources = [
152    "$src/foo/my-source.cc",
153  ]
154  inputs = [
155    "$src/bar/my.data",
156  ]
157  cflags = [
158    "-frobinator",
159  ]
160  public_deps = [
161    "$dir_pw_third_party/repo/bar",
162    ":foo",
163  ]
164  deps = [
165    "$dir_pw_third_party/repo:top-level",
166    "../another-pkg/baz",
167  ]
168}
169'''.lstrip(),
170        )
171
172    def test_write_target_public_visibility(self):
173        """Tests writing a globbaly visible target using a GnWriter."""
174        target = GnTarget(
175            '$build',
176            '$src',
177            json='''{
178              "target_type": "custom_type",
179              "target_name": "my-target",
180              "package": "my-package"
181            }''',
182        )
183        target.add_visibility(bazel='//visibility:private')
184        target.add_visibility(bazel='//visibility:public')
185
186        output = StringIO()
187        writer = GnWriter(output)
188        writer.repos = {'com_corp_repo': 'repo'}
189        writer.aliases = {'$build/other-pkg/baz': '$build/another-pkg/baz'}
190        writer.write_target(target)
191
192        self.assertEqual(
193            output.getvalue(),
194            '''
195# Generated from //my-package:my-target
196custom_type("my-target") {
197}
198'''.lstrip(),
199        )
200
201    def test_write_list(self):
202        """Writes a GN list assigned to a variable."""
203        self.writer.write_list('empty', [])
204        self.writer.write_list('items', ['foo', 'bar', 'baz'])
205        lines = [
206            'items = [',
207            '  "bar",',
208            '  "baz",',
209            '  "foo",',
210            ']',
211        ]
212        self.assertEqual('\n'.join(lines), self.output.getvalue().strip())
213
214    def test_write_scope(self):
215        """Writes a GN scope assigned to a variable."""
216        self.writer.write_scope('outer')
217        self.writer.write('key1 = "val1"')
218        self.writer.write_scope('inner')
219        self.writer.write('key2 = "val2"')
220        self.writer.write_end()
221        self.writer.write('key3 = "val3"')
222        self.writer.write_end()
223        lines = [
224            'outer = {',
225            '  key1 = "val1"',
226            '  inner = {',
227            '    key2 = "val2"',
228            '  }',
229            '',
230            '  key3 = "val3"',
231            '}',
232        ]
233        self.assertEqual('\n'.join(lines), self.output.getvalue().strip())
234
235    def test_write_if_else_end(self):
236        """Writes GN conditional statements."""
237        self.writer.write_if('current_os == "linux"')
238        self.writer.write('mascot = "penguin"')
239        self.writer.write_else_if('current_os == "mac"')
240        self.writer.write('mascot = "dogcow"')
241        self.writer.write_else_if('current_os == "win"')
242        self.writer.write('mascot = "clippy"')
243        self.writer.write_else()
244        self.writer.write('mascot = "dropbear"')
245        self.writer.write_end()
246        lines = [
247            'if (current_os == "linux") {',
248            '  mascot = "penguin"',
249            '} else if (current_os == "mac") {',
250            '  mascot = "dogcow"',
251            '} else if (current_os == "win") {',
252            '  mascot = "clippy"',
253            '} else {',
254            '  mascot = "dropbear"',
255            '}',
256        ]
257        self.assertEqual('\n'.join(lines), self.output.getvalue().strip())
258
259    def test_write_unclosed_target(self):
260        """Triggers an error from an unclosed GN scope."""
261        self.writer.write_target_start('unclosed', 'target')
262        with self.assertRaises(MalformedGnError):
263            self.writer.seal()
264
265    def test_write_unclosed_scope(self):
266        """Triggers an error from an unclosed GN scope."""
267        self.writer.write_scope('unclosed_scope')
268        with self.assertRaises(MalformedGnError):
269            self.writer.seal()
270
271    def test_write_unclosed_if(self):
272        """Triggers an error from an unclosed GN condition."""
273        self.writer.write_if('var == "unclosed-if"')
274        with self.assertRaises(MalformedGnError):
275            self.writer.seal()
276
277    def test_write_unclosed_else_if(self):
278        """Triggers an error from an unclosed GN condition."""
279        self.writer.write_if('var == "closed-if"')
280        self.writer.write_else_if('var == "unclosed-else-if"')
281        with self.assertRaises(MalformedGnError):
282            self.writer.seal()
283
284    def test_write_unclosed_else(self):
285        """Triggers an error from an unclosed GN condition."""
286        self.writer.write_if('var == "closed-if"')
287        self.writer.write_else_if('var == "closed-else-if"')
288        self.writer.write_else()
289        with self.assertRaises(MalformedGnError):
290            self.writer.seal()
291
292
293class TestGnFile(unittest.TestCase):
294    """Tests for gn_writer.GnFile."""
295
296    def test_format_on_close(self):
297        """Verifies the GN file is formatted when the file is closed."""
298        with TemporaryDirectory() as tmpdirname:
299            with GnFile(PurePath(tmpdirname, 'BUILD.gn')) as build_gn:
300                build_gn.write('  correct = "indent"')
301                build_gn.write_comment('newline before comment')
302                build_gn.write_scope('no_newline_before_item')
303                build_gn.write_list('single_item', ['is.inlined'])
304                build_gn.write_end()
305
306            filename = PurePath('pw_build', 'gn_writer.py')
307            expected = (
308                COPYRIGHT_HEADER
309                + f'''
310# This file was automatically generated by {filename}
311
312correct = "indent"
313
314# newline before comment
315no_newline_before_item = {{
316  single_item = [ "is.inlined" ]
317}}'''
318            )
319            with open(os.path.join(tmpdirname, 'BUILD.gn'), 'r') as build_gn:
320                self.assertEqual(expected.strip(), build_gn.read().strip())
321
322
323if __name__ == '__main__':
324    unittest.main()
325