blob: 88da2d0401725aa9e0ae5168eedbd0d3e63cd8c6 [file] [log] [blame]
# Copyright 2023 The Pigweed Authors
#
# 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
#
# https://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.
"""Tests for the pw_build.gn_writer module."""
import os
import unittest
from io import StringIO
from pathlib import PurePath
from tempfile import TemporaryDirectory
from pw_build.gn_config import GnConfig
from pw_build.gn_writer import (
COPYRIGHT_HEADER,
GnFile,
GnWriter,
)
from pw_build.gn_target import GnTarget
from pw_build.gn_utils import MalformedGnError
class TestGnWriter(unittest.TestCase):
"""Tests for gn_writer.GnWriter."""
def setUp(self):
"""Creates a GnWriter that writes to a StringIO."""
self.reset()
def reset(self):
"""Resets the writer and output."""
self.output = StringIO()
self.writer = GnWriter(self.output)
def test_write_comment(self):
"""Writes a GN comment."""
self.writer.write_comment('hello, world!')
self.assertEqual(
self.output.getvalue(),
'# hello, world!\n',
)
def test_write_comment_wrap(self):
"""Writes a GN comment that is exactly 80 characters."""
extra_long = (
"This line is a " + ("really, " * 5) + "REALLY extra long comment"
)
self.writer.write_comment(extra_long)
self.assertEqual(
self.output.getvalue(),
'# This line is a really, really, really, really, really, REALLY '
'extra long\n# comment\n',
)
def test_write_comment_nowrap(self):
"""Writes a long GN comment without whitespace to wrap on."""
no_breaks = 'A' + ('a' * 76) + 'h!'
self.writer.write_comment(no_breaks)
self.assertEqual(
self.output.getvalue(),
'# Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
'aaaaaaaaaaaaah!\n',
)
def test_write_imports(self):
"""Writes GN import statements."""
self.writer.write_import('foo.gni')
self.writer.write_imports(['bar.gni', 'baz.gni'])
lines = [
'import("foo.gni")',
'import("bar.gni")',
'import("baz.gni")',
]
self.assertEqual('\n'.join(lines), self.output.getvalue().strip())
def test_write_config(self):
"""Writes a GN config."""
config = GnConfig(
json='''{
"label": "$dir_3p/test:my-config",
"cflags": ["-frobinator", "-fizzbuzzer"],
"defines": ["KEY=VAL"],
"public": true,
"usages": 1
}'''
)
self.writer.write_config(config)
lines = [
'config("my-config") {',
' cflags = [',
' "-fizzbuzzer",',
' "-frobinator",',
' ]',
' defines = [',
' "KEY=VAL",',
' ]',
'}',
]
self.assertEqual('\n'.join(lines), self.output.getvalue().strip())
def test_write_target(self):
"""Tests writing the target using a GnWriter."""
target = GnTarget(
'$build',
'$src',
json='''{
"target_type": "custom_type",
"target_name": "my-target",
"package": "my-package"
}''',
)
target.add_visibility(bazel='//visibility:private')
target.add_visibility(bazel='//foo:__subpackages__')
target.add_path('public', bazel='//foo:my-header.h')
target.add_path('sources', bazel='//foo:my-source.cc')
target.add_path('inputs', bazel='//bar:my.data')
target.config.add('cflags', '-frobinator')
target.add_dep(public=True, bazel='//my-package:foo')
target.add_dep(public=True, bazel='@com_corp_repo//bar')
target.add_dep(bazel='//other-pkg/baz')
target.add_dep(bazel='@com_corp_repo//:top-level')
output = StringIO()
writer = GnWriter(output)
writer.repos = {'com_corp_repo': 'repo'}
writer.aliases = {'$build/other-pkg/baz': '$build/another-pkg/baz'}
writer.write_target(target)
self.assertEqual(
output.getvalue(),
'''
# Generated from //my-package:my-target
custom_type("my-target") {
visibility = [
"../foo/*",
":*",
]
public = [
"$src/foo/my-header.h",
]
sources = [
"$src/foo/my-source.cc",
]
inputs = [
"$src/bar/my.data",
]
cflags = [
"-frobinator",
]
public_deps = [
"$dir_pw_third_party/repo/bar",
":foo",
]
deps = [
"$dir_pw_third_party/repo:top-level",
"../another-pkg/baz",
]
}
'''.lstrip(),
)
def test_write_target_public_visibility(self):
"""Tests writing a globbaly visible target using a GnWriter."""
target = GnTarget(
'$build',
'$src',
json='''{
"target_type": "custom_type",
"target_name": "my-target",
"package": "my-package"
}''',
)
target.add_visibility(bazel='//visibility:private')
target.add_visibility(bazel='//visibility:public')
output = StringIO()
writer = GnWriter(output)
writer.repos = {'com_corp_repo': 'repo'}
writer.aliases = {'$build/other-pkg/baz': '$build/another-pkg/baz'}
writer.write_target(target)
self.assertEqual(
output.getvalue(),
'''
# Generated from //my-package:my-target
custom_type("my-target") {
}
'''.lstrip(),
)
def test_write_list(self):
"""Writes a GN list assigned to a variable."""
self.writer.write_list('empty', [])
self.writer.write_list('items', ['foo', 'bar', 'baz'])
lines = [
'items = [',
' "bar",',
' "baz",',
' "foo",',
']',
]
self.assertEqual('\n'.join(lines), self.output.getvalue().strip())
def test_write_scope(self):
"""Writes a GN scope assigned to a variable."""
self.writer.write_scope('outer')
self.writer.write('key1 = "val1"')
self.writer.write_scope('inner')
self.writer.write('key2 = "val2"')
self.writer.write_end()
self.writer.write('key3 = "val3"')
self.writer.write_end()
lines = [
'outer = {',
' key1 = "val1"',
' inner = {',
' key2 = "val2"',
' }',
'',
' key3 = "val3"',
'}',
]
self.assertEqual('\n'.join(lines), self.output.getvalue().strip())
def test_write_if_else_end(self):
"""Writes GN conditional statements."""
self.writer.write_if('current_os == "linux"')
self.writer.write('mascot = "penguin"')
self.writer.write_else_if('current_os == "mac"')
self.writer.write('mascot = "dogcow"')
self.writer.write_else_if('current_os == "win"')
self.writer.write('mascot = "clippy"')
self.writer.write_else()
self.writer.write('mascot = "dropbear"')
self.writer.write_end()
lines = [
'if (current_os == "linux") {',
' mascot = "penguin"',
'} else if (current_os == "mac") {',
' mascot = "dogcow"',
'} else if (current_os == "win") {',
' mascot = "clippy"',
'} else {',
' mascot = "dropbear"',
'}',
]
self.assertEqual('\n'.join(lines), self.output.getvalue().strip())
def test_write_unclosed_target(self):
"""Triggers an error from an unclosed GN scope."""
self.writer.write_target_start('unclosed', 'target')
with self.assertRaises(MalformedGnError):
self.writer.seal()
def test_write_unclosed_scope(self):
"""Triggers an error from an unclosed GN scope."""
self.writer.write_scope('unclosed_scope')
with self.assertRaises(MalformedGnError):
self.writer.seal()
def test_write_unclosed_if(self):
"""Triggers an error from an unclosed GN condition."""
self.writer.write_if('var == "unclosed-if"')
with self.assertRaises(MalformedGnError):
self.writer.seal()
def test_write_unclosed_else_if(self):
"""Triggers an error from an unclosed GN condition."""
self.writer.write_if('var == "closed-if"')
self.writer.write_else_if('var == "unclosed-else-if"')
with self.assertRaises(MalformedGnError):
self.writer.seal()
def test_write_unclosed_else(self):
"""Triggers an error from an unclosed GN condition."""
self.writer.write_if('var == "closed-if"')
self.writer.write_else_if('var == "closed-else-if"')
self.writer.write_else()
with self.assertRaises(MalformedGnError):
self.writer.seal()
class TestGnFile(unittest.TestCase):
"""Tests for gn_writer.GnFile."""
def test_format_on_close(self):
"""Verifies the GN file is formatted when the file is closed."""
with TemporaryDirectory() as tmpdirname:
with GnFile(PurePath(tmpdirname, 'BUILD.gn')) as build_gn:
build_gn.write(' correct = "indent"')
build_gn.write_comment('newline before comment')
build_gn.write_scope('no_newline_before_item')
build_gn.write_list('single_item', ['is.inlined'])
build_gn.write_end()
filename = PurePath('pw_build', 'gn_writer.py')
expected = (
COPYRIGHT_HEADER
+ f'''
# This file was automatically generated by {filename}
correct = "indent"
# newline before comment
no_newline_before_item = {{
single_item = [ "is.inlined" ]
}}'''
)
with open(os.path.join(tmpdirname, 'BUILD.gn'), 'r') as build_gn:
self.assertEqual(expected.strip(), build_gn.read().strip())
if __name__ == '__main__':
unittest.main()