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_config module.""" 15 16import unittest 17 18from typing import Iterable 19 20from pw_build.gn_config import ( 21 consolidate_configs, 22 GnConfig, 23 GN_CONFIG_FLAGS, 24) 25from pw_build.gn_utils import GnLabel, MalformedGnError 26 27 28class TestGnConfig(unittest.TestCase): 29 """Tests for gn_config.GnConfig. 30 31 Attributes: 32 config: A common config for testing. 33 """ 34 35 def compare(self, actual: Iterable[str], *expected: str): 36 """Sorts lists and compares them.""" 37 self.assertEqual(sorted(expected), sorted(list(actual))) 38 39 def setUp(self): 40 self.config = GnConfig() 41 42 def test_bool_empty(self): 43 """Identifies an empty config correctly.""" 44 self.assertFalse(self.config) 45 46 def test_bool_nonempty(self): 47 """Identifies a non-empty config correctly.""" 48 self.config.add('defines', 'KEY=VAL') 49 self.assertTrue(self.config) 50 51 def test_has_none(self): 52 """Indicates a config does not have a flag when it has no values.""" 53 self.assertFalse(self.config.has('cflags')) 54 55 def test_has_single(self): 56 """Indicates a config has a flag when it has one value.""" 57 self.config.add('cflags', '-frobinator') 58 self.assertTrue(self.config.has('cflags')) 59 60 def test_has_multiple(self): 61 """Indicates a config has a flag when it has multiple values.""" 62 self.config.add('cflags', '-frobinator') 63 self.config.add('cflags', '-fizzbuzzer') 64 self.assertTrue(self.config.has('cflags')) 65 66 def test_has_two_flags(self): 67 """Indicates a config has a flag when it has multiple flags.""" 68 self.config.add('cflags', '-frobinator') 69 self.config.add('cflags_c', '-foobarbaz') 70 self.assertTrue(self.config.has('cflags')) 71 72 def test_add_and_get(self): 73 """Tests ability to add valid flags to a config.""" 74 for flag in GN_CONFIG_FLAGS: 75 self.config.add(flag, f'{flag}_value1') 76 self.config.add(flag, f'{flag}_value2', f'{flag}_value3') 77 for flag in GN_CONFIG_FLAGS: 78 self.compare( 79 self.config.get(flag), 80 f'{flag}_value1', 81 f'{flag}_value2', 82 f'{flag}_value3', 83 ) 84 85 def test_add_and_get_invalid(self): 86 """Tests ability to add only valid flags to a config.""" 87 with self.assertRaises(MalformedGnError): 88 self.config.add('invalid', 'value') 89 90 def test_take_missing(self): 91 """Tests ability to return an empty list when taking a missing flag.""" 92 self.compare(self.config.take('cflags')) 93 self.assertFalse(self.config.has('defines')) 94 self.assertFalse(self.config.has('cflags')) 95 96 def test_take(self): 97 """Tests ability to remove and return values from a config.""" 98 self.config.add('defines', 'KEY1=VAL1', 'KEY2=VAL2') 99 self.config.add('cflags', '-frobinator', '-fizzbuzzer') 100 self.assertTrue(self.config.has('defines')) 101 self.assertTrue(self.config.has('cflags')) 102 103 self.compare(self.config.take('cflags'), '-frobinator', '-fizzbuzzer') 104 self.assertTrue(self.config.has('defines')) 105 self.assertFalse(self.config.has('cflags')) 106 107 def test_deduplicate_no_label(self): 108 """Tests raising an error from deduplicating a labelless config.""" 109 cfg1 = GnConfig() 110 cfg1.add('defines', 'KEY1=VAL1') 111 with self.assertRaises(ValueError): 112 list(self.config.deduplicate(cfg1)) 113 114 def test_deduplicate_empty(self): 115 """Tests deduplicating an empty config.""" 116 cfg2 = GnConfig() 117 cfg2.label = ':cfg2' 118 self.assertFalse(list(self.config.deduplicate(cfg2))) 119 120 def test_deduplicate_not_subset(self): 121 """Tests deduplicating a config whose values are not a subset.""" 122 self.config.add('defines', 'KEY1=VAL1', 'KEY2=VAL2') 123 self.config.add('cflags', '-frobinator', '-fizzbuzzer', '-foobarbaz') 124 125 cfg3 = GnConfig() 126 cfg3.label = ':cfg3' 127 cfg3.add('defines', 'KEY1=VAL1', 'KEY3=VAL3') 128 self.assertFalse(list(self.config.deduplicate(cfg3))) 129 130 def test_deduplicate(self): 131 """Tests deduplicating multiple overlapping configs.""" 132 self.config.add('defines', 'KEY1=VAL1', 'KEY2=VAL2') 133 self.config.add('cflags', '-frobinator', '-fizzbuzzer', '-foobarbaz') 134 135 cfg4 = GnConfig() 136 cfg4.label = ':cfg4' 137 cfg4.add('defines', 'KEY1=VAL1') 138 cfg4.add('cflags', '-frobinator') 139 140 cfg5 = GnConfig() 141 cfg5.label = ':cfg5' 142 cfg5.add('defines', 'KEY1=VAL1') 143 cfg5.add('cflags', '-foobarbaz') 144 145 cfg6 = GnConfig() 146 cfg6.label = ':skipped' 147 cfg6.add('cflags', '-foobarbaz') 148 149 cfgs = [ 150 str(cfg.label) for cfg in self.config.deduplicate(cfg4, cfg5, cfg6) 151 ] 152 self.assertEqual(cfgs, [':cfg4', ':cfg5']) 153 self.compare(self.config.get('defines'), 'KEY2=VAL2') 154 self.compare(self.config.get('cflags'), '-fizzbuzzer') 155 156 def test_extract_public(self): 157 """Tests ability to removes and return `public_config` values.""" 158 self.config.add('public_defines', 'KEY1=VAL1', 'KEY2=VAL2') 159 self.config.add('defines', 'KEY3=VAL3', 'KEY4=VAL4') 160 self.config.add('include_dirs', 'foo', 'bar', 'baz') 161 self.config.add('cflags', '-frobinator', '-fizzbuzzer') 162 163 config = self.config.extract_public() 164 165 self.assertFalse(self.config.has('public_defines')) 166 self.assertFalse(self.config.has('include_dirs'), 'KEY2=VAL2') 167 self.compare(self.config.get('defines'), 'KEY3=VAL3', 'KEY4=VAL4') 168 self.compare(self.config.get('cflags'), '-frobinator', '-fizzbuzzer') 169 170 self.assertFalse(config.has('public_defines')) 171 self.compare(config.get('defines'), 'KEY1=VAL1', 'KEY2=VAL2') 172 self.compare(config.get('include_dirs'), 'foo', 'bar', 'baz') 173 self.assertFalse(config.has('cflags')) 174 175 def test_consolidate_configs(self): 176 """Tests ability to merges configs into the smallest exact cover.""" 177 cfg1 = GnConfig() 178 cfg1.add('cflags', 'one', 'a', 'b') 179 cfg1.add('defines', 'k=1') 180 cfg1.add('include_dirs', 'foo', 'bar') 181 182 cfg2 = GnConfig() 183 cfg2.add('cflags', 'one', 'a', 'b', 'c') 184 cfg2.add('defines', 'k=1') 185 cfg2.add('include_dirs', 'foo', 'baz') 186 187 cfg3 = GnConfig() 188 cfg3.add('cflags', 'one', 'two', 'a', 'b') 189 cfg3.add('defines', 'k=1') 190 cfg3.add('include_dirs', 'foo', 'bar') 191 192 cfg4 = GnConfig() 193 cfg4.add('cflags', 'one', 'b', 'c') 194 cfg4.add('defines', 'k=0') 195 cfg4.add('include_dirs', 'foo', 'baz') 196 197 label = GnLabel('//foo/bar') 198 common = list(consolidate_configs(label, cfg1, cfg2, cfg3, cfg4)) 199 self.assertEqual(len(common), 3) 200 self.assertEqual(str(common[0].label), '//foo/bar:bar_public_config1') 201 self.assertEqual(str(common[1].label), '//foo/bar:bar_config1') 202 self.assertEqual(str(common[2].label), '//foo/bar:bar_config2') 203 self.compare(common[0].get('include_dirs'), 'foo') 204 self.compare(common[1].get('cflags'), 'one', 'b') 205 self.compare(common[2].get('cflags'), 'a') 206 self.compare(common[2].get('defines'), 'k=1') 207 208 209if __name__ == '__main__': 210 unittest.main() 211