| #!/usr/bin/env python2 |
| # Copyright 2017 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Unit tests for the config validator""" |
| |
| from __future__ import print_function |
| |
| import os |
| import tempfile |
| |
| from chromite.lib import cros_test_lib |
| |
| import validate_config |
| |
| |
| HEADER = '''/dts-v1/; |
| |
| / { |
| chromeos { |
| family: family { |
| }; |
| |
| models: models { |
| }; |
| }; |
| }; |
| ''' |
| |
| MODELS = ''' |
| &models { |
| reef: reef { |
| }; |
| pyro: pyro { |
| }; |
| snappy: snappy { |
| }; |
| }; |
| ''' |
| |
| FAMILY_FIRMWARE_MISSING = ''' |
| &family { |
| firmware { |
| script = "updater4.sh"; |
| shared: reef { |
| ec-image = "bcs://Reef_EC.9984.0.0.tbz2"; |
| main-image = "bcs://Reef.9984.0.0.tbz2"; |
| }; |
| }; |
| }; |
| ''' |
| |
| FAMILY_FIRMWARE_SCRIPT_MISSING = ''' |
| &family { |
| firmware { |
| }; |
| }; |
| ''' |
| |
| FAMILY_FIRMWARE_BAD_SHARES = ''' |
| &family { |
| firmware { |
| shared: reef { |
| shares = <&shared>; |
| }; |
| }; |
| }; |
| ''' |
| |
| FAMILY_FIRMWARE = FAMILY_FIRMWARE_MISSING + ''' |
| &family { |
| firmware { |
| script = "updater4.sh"; |
| shared: reef { |
| bcs-overlay = "overlay-reef-private"; |
| build-targets { |
| coreboot = "reef"; |
| ec = "reef"; |
| depthcharge = "reef"; |
| libpayload = "reef"; |
| }; |
| }; |
| }; |
| }; |
| ''' |
| |
| MODEL_FIRMWARE_SHARED_WRONG_TARGET = ''' |
| &family { |
| firmware { |
| shared: reef { |
| build_targets: build-targets { |
| coreboot = "reef"; |
| ec = "reef"; |
| depthcharge = "reef"; |
| libpayload = "reef"; |
| }; |
| }; |
| }; |
| }; |
| |
| &models { |
| reef { |
| firmware { |
| shares = < &build_targets >; |
| key-id = "REEF"; |
| }; |
| }; |
| }; |
| ''' |
| |
| MODEL_FIRMWARE_SHARED = ''' |
| &models { |
| reef { |
| firmware { |
| shares = < &shared >; |
| key-id = "REEF"; |
| }; |
| }; |
| }; |
| ''' |
| |
| MODEL_FIRMWARE_SHARE_EXTRA_PROPS = ''' |
| &models { |
| reef { |
| firmware { |
| shares = < &shared >; |
| key-id = "REEF"; |
| bcs-overlay = "overlay-reef-private"; |
| }; |
| }; |
| }; |
| ''' |
| |
| MODEL_FIRMWARE_SHARE_EXTRA_NODES = ''' |
| &models { |
| reef { |
| firmware { |
| shares = < &shared >; |
| key-id = "REEF"; |
| build-targets { |
| coreboot = "reef"; |
| }; |
| }; |
| }; |
| }; |
| ''' |
| |
| MODEL_BRAND_CODE = ''' |
| &models { |
| reef { |
| brand-code = "ABCD"; |
| }; |
| pyro{ |
| brand-code = "abcd"; |
| }; |
| snappy{ |
| brand-code = "AB1"; |
| }; |
| }; |
| ''' |
| |
| MODEL_THERMAL = ''' |
| &models { |
| reef { |
| thermal { |
| dptf-dv = "reef/dptf.dv"; |
| }; |
| }; |
| pyro{ |
| thermal { |
| dptf-dv = "dptf.dv"; |
| }; |
| }; |
| snappy{ |
| thermal { |
| dptf-dv = "reef/bad.dv"; |
| }; |
| }; |
| }; |
| ''' |
| |
| TOUCH = r''' |
| &family { |
| touch { |
| elan_touchscreen: elan-touchscreen { |
| vendor = "elan"; |
| firmware-bin = "${vendor}/${pid}_${version}.bin"; |
| firmware-symlink = "${vendor}ts_i2c_${pid}.bin"; |
| }; |
| weida_touchscreen: weida-touchscreen { |
| vendor = "weida"; |
| firmware-bin = "weida/${pid}_${version}_${date-code}.bin"; |
| firmware-symlink = "wdt87xx.bin"; |
| }; |
| }; |
| }; |
| &models { |
| reef { |
| touch { |
| #address-cells = <1>; |
| #size-cells = <0>; |
| present = "probe"; |
| probe-regex = "[Tt]ouchscreen\|WCOMNTN2"; |
| touchscreen@0 { |
| reg = <0>; |
| touch-type = <&elan_touchscreen>; |
| pid = "0a97"; |
| version = "1012"; |
| }; |
| touchscreen@1 { |
| reg = <1>; |
| touch-type = <&shared>; |
| pid = "0b10"; |
| version = "002n"; |
| }; |
| touchscreen@2 { |
| reg = <2>; |
| touch-type = <&weida_touchscreen>; |
| pid = "01017401"; |
| version = "2082"; |
| date-code = "0133c65b"; |
| }; |
| bad { |
| }; |
| }; |
| }; |
| }; |
| ''' |
| |
| AUDIO = r''' |
| &family { |
| audio { |
| audio_type: audio-type { |
| card = "bxtda7219max"; |
| volume = "cras-config/${cras-config-dir}/${card}"; |
| dsp-ini = "cras-config/${cras-config-dir}/dsp.ini"; |
| hifi-conf = "ucm-config/${card}.${ucm-suffix}/HiFi.conf"; |
| alsa-conf = "ucm-config/${card}.${ucm-suffix}/${card}.${ucm-suffix}.conf"; |
| topology-xml = "topology/${topology-name}_topology.xml"; |
| topology-bin = "topology/5a98-reef-${topology-name}-8-tplg.bin"; |
| }; |
| bad_audio_type: bad-audio-type { |
| volume = "cras-config/${cras-config-dir}/${card}"; |
| dsp-ini = "cras-config/${cras-config-dir}/dsp.ini"; |
| hifi-conf = "ucm-config/${card}.${ucm-suffix}/HiFi.conf"; |
| alsa-conf = "ucm-config/${card}.${ucm-suffix}/${card}.${ucm-suffix}.conf"; |
| topology-xml = "topology/${topology-name}_topology.xml"; |
| topology-bin = "topology/5a98-reef-${topology-name}-8-tplg.bin"; |
| }; |
| }; |
| }; |
| &models { |
| pyro: pyro { |
| powerd-prefs = "pyro_snappy"; |
| wallpaper = "alien_invasion"; |
| brand-code = "ABCE"; |
| audio { |
| main { |
| audio-type = <&audio_type>; |
| cras-config-dir = "pyro"; |
| ucm-suffix = "pyro"; |
| topology-name = "pyro"; |
| }; |
| }; |
| }; |
| pyro: pyro { |
| powerd-prefs = "pyro_snappy"; |
| wallpaper = "alien_invasion"; |
| brand-code = "ABCE"; |
| audio { |
| main { |
| audio-type = <&audio_type>; |
| ucm-suffix = "pyro"; |
| topology-name = "pyro"; |
| }; |
| }; |
| }; |
| snappy: snappy { |
| powerd-prefs = "pyro_snappy"; |
| wallpaper = "chocolate"; |
| brand-code = "ABCF"; |
| audio { |
| main { |
| cras-config-dir = "snappy"; |
| ucm-suffix = "snappy"; |
| topology-name = "snappy"; |
| }; |
| }; |
| }; |
| }; |
| ''' |
| |
| MAPPING = ''' |
| &family { |
| mapping { |
| #address-cells = <1>; |
| #size-cells = <0>; |
| sku-map@0 { |
| reg = <0>; |
| platform-name = "Reef"; |
| smbios-name-match = "reef"; |
| /* This is an example! It does not match any real family */ |
| simple-sku-map = < |
| 0 &reef |
| 4 &reef_4 |
| 4 &snappy /* duplicate */ |
| 5 &reef_5 |
| 8 &shared |
| 256 &reef_5>; |
| }; |
| sku-map@1 { |
| reg = <1>; |
| platform-name = "Pyro"; |
| smbios-name-match = "pyro"; |
| single-sku = <&pyro>; |
| }; |
| sku-map@2 { |
| reg = <2>; |
| platform-name = "Snappy"; |
| smbios-name-match = "snappy"; |
| single-sku = <&snappy>; |
| }; |
| }; |
| }; |
| &models { |
| reef { |
| /* |
| * We don't have submodel validation, but add this in here so that mapping |
| * validation is complete. |
| */ |
| submodels { |
| reef_4: reef-touchscreen { |
| }; |
| reef_5: reef-notouch { |
| }; |
| }; |
| }; |
| }; |
| ''' |
| |
| WHITELABEL = ''' |
| &models { |
| /* Whitelabel model */ |
| whitetip: whitetip { |
| firmware { |
| shares = <&shared>; |
| }; |
| }; |
| |
| whitetip1 { |
| whitelabel = <&whitetip>; |
| wallpaper = "shark"; |
| brand-code = "SHAR"; |
| firmware { |
| key-id = "WHITELABEL1"; |
| }; |
| }; |
| |
| whitetip2 { |
| whitelabel = <&whitetip>; |
| wallpaper = "more_shark"; |
| brand-code = "SHAQ"; |
| firmware { |
| key-id = "WHITELABEL2"; |
| }; |
| }; |
| |
| bad { |
| whitelabel = <&whitetip>; |
| firmware { |
| shares = <&shared>; |
| key-id = "WHITELABEL2"; |
| }; |
| thermal { |
| dptf-dv = "bad/dptf.dv"; |
| }; |
| }; |
| }; |
| ''' |
| |
| class UnitTests(cros_test_lib.TestCase): |
| """Unit tests for CrosConfigValidator""" |
| def setUp(self): |
| self.val = validate_config.CrosConfigValidator(validate_config.SCHEMA, |
| False) |
| |
| def Run(self, dts_source): |
| dts = tempfile.NamedTemporaryFile(suffix='.dts', delete=False) |
| dts.write(dts_source) |
| dts.close() |
| errors = self.val.Start(dts.name) |
| if errors: |
| return errors |
| if dts: |
| os.unlink(dts.name) |
| return [] |
| |
| def _CheckAllIn(self, err_msg_list, result_lines): |
| """Check that the given messages appear in the validation result |
| |
| All messages must appear, and all lines must be matches. |
| |
| Args: |
| result_lines: List of validation results to check, each a string |
| err_msg_list: List of error messages to check for |
| """ |
| err_msg_set = set(err_msg_list) |
| for line in result_lines: |
| found = False |
| for err_msg in err_msg_set: |
| if err_msg in line: |
| err_msg_set.remove(err_msg) |
| found = True |
| break |
| if not found: |
| self.fail("Found unexpected result: %s" % line) |
| if err_msg_set: |
| self.fail("Expected '%s'\n but not found in result: %s" % |
| (err_msg_set.pop(), '\n'.join(result_lines))) |
| |
| def testBase(self): |
| """Test a skeleton file""" |
| self.assertEqual([], self.Run(HEADER)) |
| |
| def testModels(self): |
| """Test a skeleton file with models""" |
| self.assertEqual([], self.Run(HEADER + MODELS)) |
| |
| def testFamilyFirmwareMissing(self): |
| """Test family firmware with missing pieces""" |
| self.assertEqual([ |
| "/chromeos/family/firmware/reef: Required property 'bcs-overlay' " + |
| "missing", |
| "/chromeos/family/firmware/reef: Missing subnode 'build-targets'", |
| ], self.Run(HEADER + MODELS + FAMILY_FIRMWARE_MISSING)) |
| |
| def testFamilyFirmwareScriptMissing(self): |
| """Test a family firmware without a script""" |
| self.assertIn( |
| "/chromeos/family/firmware: Required property 'script' missing", |
| self.Run(HEADER + MODELS + FAMILY_FIRMWARE_SCRIPT_MISSING)) |
| |
| def testFamilyFirmware(self): |
| """Test valid family firmware""" |
| self.assertEqual([], self.Run(HEADER + MODELS + FAMILY_FIRMWARE)) |
| |
| def testFamilyFirmwareSharedWrongTarget(self): |
| """Test a model trying to share an invalid firmware target""" |
| result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE + |
| MODEL_FIRMWARE_SHARED_WRONG_TARGET) |
| self.assertEqual( |
| ['/chromeos/family/firmware/reef/build-targets: phandle target not ' |
| 'valid for this node', |
| "/chromeos/models/reef/firmware: Phandle 'shares' " + |
| "targets node '/chromeos/family/firmware/reef/build-targets' " |
| "which does not match pattern '/chromeos/family/firmware/MODEL'", |
| ], result) |
| |
| def testFamilyFirmwareBadShares(self): |
| """Test a model trying to share an invalid firmware target""" |
| result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE + |
| FAMILY_FIRMWARE_BAD_SHARES) |
| self.assertEqual( |
| ["/chromeos/family/firmware/reef: Unexpected property 'shares', " + |
| "valid list is (phandle, bcs-overlay, ec-image, main-image, " + |
| "main-rw-image, pd-image, extra)"], result) |
| |
| def testFamilyFirmwareShared(self): |
| """Test valid shared firmware""" |
| result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE + |
| MODEL_FIRMWARE_SHARED) |
| self.assertEqual([], result) |
| |
| def testFamilyFirmwareSharedExtraProps(self): |
| """Test the model trying to specify properties that should be shared""" |
| result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE + |
| MODEL_FIRMWARE_SHARE_EXTRA_PROPS) |
| self.assertEqual( |
| ["/chromeos/models/reef/firmware: Unexpected property 'bcs-overlay', " |
| "valid list is (shares, key-id)"], result) |
| |
| def testFamilyFirmwareSharedExtraNodes(self): |
| """Test the model trying to specify nodes that should be shared""" |
| result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE + |
| MODEL_FIRMWARE_SHARE_EXTRA_NODES) |
| self.assertEqual( |
| ["/chromeos/models/reef/firmware: Unexpected subnode 'build-targets', " |
| "valid list is ()"], result) |
| |
| def testBrandCode(self): |
| """Test validation of brand codes""" |
| self.assertEqual([ |
| "/chromeos/models/pyro: 'brand-code' value 'abcd' does not match " + |
| "pattern '^[A-Z]{4}$'", |
| "/chromeos/models/snappy: 'brand-code' value 'AB1' does not match " + |
| "pattern '^[A-Z]{4}$'"], self.Run(HEADER + MODELS + MODEL_BRAND_CODE)) |
| |
| def testThermal(self): |
| """Test validation of the thermal node""" |
| self.assertEqual([ |
| "/chromeos/models/pyro/thermal: 'dptf-dv' value 'dptf.dv' does not " |
| "match pattern '^\\w+/dptf.dv$'", |
| "/chromeos/models/snappy/thermal: 'dptf-dv' value 'reef/bad.dv' does " + |
| "not match pattern '^\\w+/dptf.dv$'" |
| ], self.Run(HEADER + MODELS + MODEL_THERMAL)) |
| |
| def testTouch(self): |
| """Test validation of the thermal node""" |
| result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE + TOUCH) |
| self._CheckAllIn([ |
| "Node name 'bad' does not match pattern", |
| "/bad: Required property 'version' missing", |
| "/bad: Required property 'firmware-bin' missing", |
| "/bad: Required property 'firmware-symlink' missing", |
| "/touchscreen@1: Phandle 'touch-type' targets node", |
| ], result) |
| |
| def testAudio(self): |
| """Test validation of the audio nodes""" |
| result = self.Run(HEADER + MODELS + AUDIO) |
| self._CheckAllIn([ |
| "snappy/audio/main: Required property 'card' missing", |
| "snappy/audio/main: Required property 'volume' missing", |
| "snappy/audio/main: Required property 'dsp-ini' missing", |
| "snappy/audio/main: Required property 'hifi-conf' missing", |
| "snappy/audio/main: Required property 'alsa-conf' missing", |
| "bad-audio-type: Required property 'card' missing", |
| ], result) |
| |
| def testMapping(self): |
| """Test validation of the mapping node""" |
| result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE + MAPPING) |
| self._CheckAllIn([ |
| 'mapping/sku-map@0: Duplicate sku_id 4', |
| 'mapping/sku-map@0: sku_id 256 out of range', |
| "mapping/sku-map@0: Phandle 'simple-sku-map' sku-id 8 must target a " + |
| 'model or submodel', |
| ], result) |
| |
| def testWhiteLabel(self): |
| result = self.Run(HEADER + MODELS + FAMILY_FIRMWARE + WHITELABEL) |
| self._CheckAllIn([ |
| "/bad: Unexpected subnode 'thermal', valid list is (firmware)", |
| "bad/firmware: Unexpected property 'shares', valid list is (key-id)", |
| ], result) |
| |
| if __name__ == '__main__': |
| cros_test_lib.main(module=__name__) |