blob: 313f86a610fb08a2657ca07df9e87d86ca1c7b83 [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 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.
# pylint: disable=module-missing-docstring,class-missing-docstring
from __future__ import print_function
import json
import os
import re
import textwrap
import jsonschema # pylint: disable=import-error
from six.moves import zip_longest
import yaml # pylint: disable=import-error
from packaging import version # pylint: disable=import-error
import cros_config_schema
import libcros_schema
from chromite.lib import cros_test_lib
this_dir = os.path.dirname(__file__)
BASIC_CONFIG = """
reef-9042-fw: &reef-9042-fw
bcs-overlay: 'overlay-reef-private'
ec-ro-image: 'Reef_EC.9042.87.1.tbz2'
main-ro-image: 'Reef.9042.87.1.tbz2'
main-rw-image: 'Reef.9042.110.0.tbz2'
build-targets:
coreboot: 'reef'
chromeos:
devices:
- $name: 'basking'
products:
- $key-id: 'OEM2'
$brand-code: 'ASUN'
skus:
- $sku-id: 0
config:
audio:
main:
$card: 'bxtda7219max'
cras-config-dir: '{{$name}}'
ucm-suffix: '{{$name}}'
files:
- source: "{{$dsp-ini}}"
destination: "/etc/cras/{{$dsp-ini}}"
$dsp-ini: "{{cras-config-dir}}/dsp.ini"
brand-code: '{{$brand-code}}'
identity:
platform-name: "Reef"
smbios-name-match: "Reef"
sku-id: "{{$sku-id}}"
name: '{{$name}}'
firmware: *reef-9042-fw
firmware-signing:
key-id: '{{$key-id}}'
signature-id: '{{$name}}'
test-label: 'reef'
"""
class MergeDictionaries(cros_test_lib.TestCase):
def testBaseKeyMerge(self):
primary = {'a': {'b': 1, 'c': 2}}
overlay = {'a': {'c': 3}, 'b': 4}
cros_config_schema.MergeDictionaries(primary, overlay)
self.assertEqual({'a': {'b': 1, 'c': 3}, 'b': 4}, primary)
def testBaseListAppend(self):
primary = {'a': {'b': 1, 'c': [1, 2]}}
overlay = {'a': {'c': [3, 4]}}
cros_config_schema.MergeDictionaries(primary, overlay)
self.assertEqual({'a': {'b': 1, 'c': [1, 2, 3, 4]}}, primary)
class ParseArgsTests(cros_test_lib.TestCase):
def testParseArgs(self):
argv = ['-s', 'schema', '-c', 'config', '-o', 'output', '-f', 'True']
args = cros_config_schema.ParseArgs(argv)
self.assertEqual(args.schema, 'schema')
self.assertEqual(args.config, 'config')
self.assertEqual(args.output, 'output')
self.assertTrue(args.filter)
def testParseArgsForConfigs(self):
argv = ['-o', 'output', '-m', 'm1', 'm2', 'm3']
args = cros_config_schema.ParseArgs(argv)
self.assertEqual(args.output, 'output')
self.assertEqual(args.configs, ['m1', 'm2', 'm3'])
class TransformConfigTests(cros_test_lib.TestCase):
def testBasicTransform(self):
result = cros_config_schema.TransformConfig(BASIC_CONFIG)
json_dict = json.loads(result)
self.assertEqual(len(json_dict), 1)
json_obj = libcros_schema.GetNamedTuple(json_dict)
self.assertEqual(1, len(json_obj.chromeos.configs))
model = json_obj.chromeos.configs[0]
self.assertEqual('basking', model.name)
self.assertEqual('basking', model.audio.main.cras_config_dir)
# Check multi-level template variable evaluation
self.assertEqual('/etc/cras/basking/dsp.ini',
model.audio.main.files[0].destination)
def testTransformConfig_NoMatch(self):
result = cros_config_schema.TransformConfig(
BASIC_CONFIG, model_filter_regex='abc123')
json_dict = json.loads(result)
json_obj = libcros_schema.GetNamedTuple(json_dict)
self.assertEqual(0, len(json_obj.chromeos.configs))
def testTransformConfig_FilterMatch(self):
scoped_config = """
reef-9042-fw: &reef-9042-fw
bcs-overlay: 'overlay-reef-private'
ec-ro-image: 'Reef_EC.9042.87.1.tbz2'
main-ro-image: 'Reef.9042.87.1.tbz2'
main-rw-image: 'Reef.9042.110.0.tbz2'
build-targets:
coreboot: 'reef'
chromeos:
devices:
- $name: 'foo'
products:
- $key-id: 'OEM2'
skus:
- config:
identity:
sku-id: 0
audio:
main:
cras-config-dir: '{{$name}}'
ucm-suffix: '{{$name}}'
name: '{{$name}}'
firmware: *reef-9042-fw
firmware-signing:
key-id: '{{$key-id}}'
signature-id: '{{$name}}'
- $name: 'bar'
products:
- $key-id: 'OEM2'
skus:
- config:
identity:
sku-id: 0
audio:
main:
cras-config-dir: '{{$name}}'
ucm-suffix: '{{$name}}'
name: '{{$name}}'
firmware: *reef-9042-fw
firmware-signing:
key-id: '{{$key-id}}'
signature-id: '{{$name}}'
"""
result = cros_config_schema.TransformConfig(
scoped_config, model_filter_regex='bar')
json_dict = json.loads(result)
json_obj = libcros_schema.GetNamedTuple(json_dict)
self.assertEqual(1, len(json_obj.chromeos.configs))
model = json_obj.chromeos.configs[0]
self.assertEqual('bar', model.name)
def testTemplateVariableScope(self):
scoped_config = """
audio_common: &audio_common
main:
$ucm: "default"
$cras: "default"
ucm-suffix: "{{$ucm}}"
cras-config-dir: "{{$cras}}"
chromeos:
devices:
- $name: "some"
$ucm: "overridden-by-device-scope"
products:
- $key-id: 'SOME-KEY'
$brand-code: 'SOME-BRAND'
$cras: "overridden-by-product-scope"
skus:
- $sku-id: 0
config:
audio: *audio_common
brand-code: '{{$brand-code}}'
identity:
platform-name: "Some"
smbios-name-match: "Some"
name: '{{$name}}'
firmware:
no-firmware: True
"""
result = cros_config_schema.TransformConfig(scoped_config)
json_dict = json.loads(result)
json_obj = libcros_schema.GetNamedTuple(json_dict)
config = json_obj.chromeos.configs[0]
self.assertEqual(
'overridden-by-product-scope', config.audio.main.cras_config_dir)
self.assertEqual(
'overridden-by-device-scope', config.audio.main.ucm_suffix)
class ValidateConfigSchemaTests(cros_test_lib.TestCase):
def setUp(self):
self._schema = cros_config_schema.ReadSchema()
def testBasicSchemaValidation(self):
libcros_schema.ValidateConfigSchema(
self._schema, cros_config_schema.TransformConfig(BASIC_CONFIG))
def testMissingRequiredElement(self):
config = re.sub(r' *cras-config-dir: .*', '', BASIC_CONFIG)
config = re.sub(r' *volume: .*', '', BASIC_CONFIG)
try:
libcros_schema.ValidateConfigSchema(
self._schema, cros_config_schema.TransformConfig(config))
except jsonschema.ValidationError as err:
self.assertIn('required', err.__str__())
self.assertIn('cras-config-dir', err.__str__())
def testReferencedNonExistentTemplateVariable(self):
config = re.sub(r' *$card: .*', '', BASIC_CONFIG)
try:
libcros_schema.ValidateConfigSchema(
self._schema, cros_config_schema.TransformConfig(config))
except cros_config_schema.ValidationError as err:
self.assertIn('Referenced template variable', err.__str__())
self.assertIn('cras-config-dir', err.__str__())
def testSkuIdOutOfBound(self):
config = BASIC_CONFIG.replace('$sku-id: 0', '$sku-id: 0x80000000')
with self.assertRaises(jsonschema.ValidationError) as ctx:
libcros_schema.ValidateConfigSchema(
self._schema, cros_config_schema.TransformConfig(config))
if version.parse(jsonschema.__version__) >= version.Version('3.0.0'):
self.assertIn(
'%i is greater than the maximum' % 0x80000000, str(ctx.exception))
self.assertIn('sku-id', str(ctx.exception))
else:
self.assertIn("'sku-id': %i" % 0x80000000, str(ctx.exception))
self.assertIn('is not valid', str(ctx.exception))
class ValidateFingerprintSchema(cros_test_lib.TestCase):
def setUp(self):
self._schema = cros_config_schema.ReadSchema()
def testROVersion(self):
config = {
'chromeos': {
'configs': [
{'identity': {'platform-name': 'foo',
'sku-id': 1},
'name': 'foo',
'fingerprint': {
'board': 'dartmonkey',
'ro-version': '123'}
},
],
},
}
libcros_schema.ValidateConfigSchema(self._schema,
libcros_schema.FormatJson(config))
def testROVersionMissingBoardName(self):
config = {
'chromeos': {
'configs': [
{'identity': {'platform-name': 'foo',
'sku-id': 1},
'name': 'foo',
'fingerprint': {
# "ro-version" only allowed if "board" is also specified.
'ro-version': '123'}
},
],
},
}
with self.assertRaises(jsonschema.exceptions.ValidationError) as ctx:
libcros_schema.ValidateConfigSchema(self._schema,
libcros_schema.FormatJson(config))
self.assertEqual(ctx.exception.message,
"'board' is a dependency of 'ro-version'")
class ValidateCameraSchema(cros_test_lib.TestCase):
def setUp(self):
self._schema = cros_config_schema.ReadSchema()
def testDevices(self):
config = {
'chromeos': {
'configs': [
{
'identity': {'platform-name': 'foo', 'sku-id': 1},
'name': 'foo',
'camera': {
'count': 2,
'devices': [
{
'interface': 'usb',
'facing': 'front',
'orientation': 180,
'flags': {
'support-1080p': False,
'support-autofocus': False,
},
'ids': ['0123:abcd', '4567:efef'],
},
{
'interface': 'mipi',
'facing': 'back',
'orientation': 0,
'flags': {
'support-1080p': True,
'support-autofocus': True,
},
},
],
}
},
],
},
}
libcros_schema.ValidateConfigSchema(self._schema,
libcros_schema.FormatJson(config))
def testInvalidUsbId(self):
if version.parse(jsonschema.__version__) < version.Version('3.0.0'):
self.skipTest('jsonschema needs upgrade to support conditionals')
for invalid_usb_id in ('0123-abcd', '0123:Abcd', '123:abcd'):
config = {
'chromeos': {
'configs': [
{
'identity': {'platform-name': 'foo', 'sku-id': 1},
'name': 'foo',
'camera': {
'count': 1,
'devices': [
{
'interface': 'usb',
'facing': 'front',
'orientation': 0,
'flags': {
'support-1080p': False,
'support-autofocus': True,
},
'ids': [invalid_usb_id],
},
],
}
},
],
},
}
with self.assertRaises(jsonschema.ValidationError) as ctx:
libcros_schema.ValidateConfigSchema(self._schema,
libcros_schema.FormatJson(config))
self.assertIn('%r does not match' % invalid_usb_id, str(ctx.exception))
WHITELABEL_CONFIG = """
chromeos:
devices:
- $name: 'whitelabel'
products:
- $key-id: 'DEFAULT'
$wallpaper: 'DEFAULT_WALLPAPER'
$regulatory-label: 'DEFAULT_LABEL'
$whitelabel-tag: ''
$brand-code: 'DEFAULT_BRAND_CODE'
$stylus-category: 'none'
$test-label: 'DEFAULT_TEST_LABEL'
- $key-id: 'WL1'
$wallpaper: 'WL1_WALLPAPER'
$regulatory-label: 'WL1_LABEL'
$whitelabel-tag: 'WL1_TAG'
$brand-code: 'WL1_BRAND_CODE'
$stylus-category: 'none'
$test-label: 'WL1_TEST_LABEL'
$marketing-name: 'BRAND1_MARKETING_NAME1'
- $key-id: 'WL2'
$wallpaper: 'WL2_WALLPAPER'
$regulatory-label: 'WL2_LABEL'
$whitelabel-tag: 'WL2_TAG'
$brand-code: 'WL2_BRAND_CODE'
$stylus-category: 'external'
$test-label: 'WL2_TEST_LABEL'
$marketing-name: 'BRAND2_MARKETING_NAME2'
skus:
- config:
identity:
sku-id: 0
whitelabel-tag: '{{$whitelabel-tag}}'
name: '{{$name}}'
brand-code: '{{$brand-code}}'
wallpaper: '{{$wallpaper}}'
regulatory-label: '{{$regulatory-label}}'
hardware-properties:
stylus-category: '{{$stylus-category}}'
arc:
build-properties:
$marketing-name: ''
marketing-name: '{{$marketing-name}}'
"""
INVALID_WHITELABEL_CONFIG = """
# THIS WILL CAUSE THE FAILURE
test-label: '{{$test-label}}'
"""
class ValidateConfigTests(cros_test_lib.TestCase):
def testBasicValidation(self):
cros_config_schema.ValidateConfig(
cros_config_schema.TransformConfig(BASIC_CONFIG))
def testIdentitiesNotUnique(self):
config = """
reef-9042-fw: &reef-9042-fw
bcs-overlay: 'overlay-reef-private'
ec-ro-image: 'Reef_EC.9042.87.1.tbz2'
main-ro-image: 'Reef.9042.87.1.tbz2'
main-rw-image: 'Reef.9042.110.0.tbz2'
build-targets:
coreboot: 'reef'
chromeos:
devices:
- $name: 'astronaut'
products:
- $key-id: 'OEM2'
skus:
- config:
identity:
sku-id: 0
audio:
main:
cras-config-dir: '{{$name}}'
ucm-suffix: '{{$name}}'
name: '{{$name}}'
firmware: *reef-9042-fw
firmware-signing:
key-id: '{{$key-id}}'
signature-id: '{{$name}}'
- $name: 'astronaut'
products:
- $key-id: 'OEM2'
skus:
- config:
identity:
sku-id: 0
audio:
main:
cras-config-dir: '{{$name}}'
ucm-suffix: '{{$name}}'
name: '{{$name}}'
firmware: *reef-9042-fw
firmware-signing:
key-id: '{{$key-id}}'
signature-id: '{{$name}}'
"""
with self.assertRaises(cros_config_schema.ValidationError) as ctx:
cros_config_schema.ValidateConfig(
cros_config_schema.TransformConfig(config))
self.assertIn('Identities are not unique', str(ctx.exception))
def testWhitelabelWithExternalStylus(self):
config = WHITELABEL_CONFIG
cros_config_schema.ValidateConfig(
cros_config_schema.TransformConfig(config))
def testWhitelabelWithOtherThanBrandChanges(self):
config = WHITELABEL_CONFIG + INVALID_WHITELABEL_CONFIG
with self.assertRaises(cros_config_schema.ValidationError) as ctx:
cros_config_schema.ValidateConfig(
cros_config_schema.TransformConfig(config))
self.assertIn('Whitelabel configs can only', str(ctx.exception))
def testHardwarePropertiesInvalid(self):
config = \
"""
chromeos:
devices:
- $name: 'bad_device'
skus:
- config:
identity:
sku-id: 0
# THIS WILL CAUSE THE FAILURE
hardware-properties:
has-base-accelerometer: true
has-base-gyroscope: 7
has-lid-accelerometer: false
is-lid-convertible: false
has-base-magnetometer: false
has-touchscreen: true
"""
try:
cros_config_schema.ValidateConfig(
cros_config_schema.TransformConfig(config))
except cros_config_schema.ValidationError as err:
self.assertIn('must be boolean', err.__str__())
else:
self.fail('ValidationError not raised')
def testHardwarePropertiesBoolean(self):
config = \
"""
chromeos:
devices:
- $name: 'good_device'
skus:
- config:
identity:
sku-id: 0
hardware-properties:
has-base-accelerometer: true
has-base-gyroscope: true
has-lid-accelerometer: true
is-lid-convertible: false
has-base-magnetometer: true
has-touchscreen: false
"""
cros_config_schema.ValidateConfig(
cros_config_schema.TransformConfig(config))
def testMultipleMosysPlatformsInvalid(self):
config = {
'chromeos': {
'configs': [
{'identity': {'platform-name': 'SomePlatform',
'sku-id': 1}},
{'identity': {'platform-name': 'SomePlatform',
'sku-id': 2}},
# This causes the ValidationError.
{'identity': {'platform-name': 'AnotherPlatform',
'sku-id': 3}},
],
},
}
with self.assertRaises(cros_config_schema.ValidationError):
cros_config_schema.ValidateConfig(json.dumps(config))
# Removing the offending config should clear the ValidationError.
config['chromeos']['configs'].pop()
try:
cros_config_schema.ValidateConfig(json.dumps(config))
except cros_config_schema.ValidationError:
self.fail('Removing the offending config should have cleared the '
'ValidationError.')
def testMultipleFingerprintFirmwareROVersionInvalid(self):
config = {
'chromeos': {
'configs': [
{'identity': {'platform-name': 'foo',
'sku-id': 1},
'fingerprint': {
'board': 'bloonchipper',
'ro-version': '123'}
},
{'identity': {'platform-name': 'foo',
'sku-id': 2},
'fingerprint': {
'board': 'bloonchipper',
'ro-version': '123'}
},
# This causes the ValidationError.
{'identity': {'platform-name': 'foo',
'sku-id': 3},
'fingerprint': {
'board': 'bloonchipper',
'ro-version': '456'}
}
],
},
}
with self.assertRaises(cros_config_schema.ValidationError) as ctx:
cros_config_schema.ValidateConfig(json.dumps(config))
self.assertRegex(str(ctx.exception), re.compile(
'You may not use different fingerprint firmware RO versions on the '
'same board:.*'))
def testMultipleFingerprintFirmwareROVersionsValid(self):
config = {
'chromeos': {
'configs': [
{'identity': {'platform-name': 'foo',
'sku-id': 1},
'fingerprint': {
'board': 'bloonchipper',
'ro-version': '123'}
},
{'identity': {'platform-name': 'foo',
'sku-id': 2},
'fingerprint': {
'board': 'dartmonkey',
'ro-version': '456'}
},
],
},
}
cros_config_schema.ValidateConfig(json.dumps(config))
def testFingerprintFirmwareROVersionsValid(self):
config = {
'chromeos': {
'configs': [
{'identity': {'platform-name': 'foo',
'sku-id': 1},
'fingerprint': {
'ro-version': '123'}
},
# This device does not have fingerprint
{'identity': {'platform-name': 'foo',
'sku-id': 2},
},
],
},
}
cros_config_schema.ValidateConfig(json.dumps(config))
class FilterBuildElements(cros_test_lib.TestCase):
def testBasicFilterBuildElements(self):
json_dict = json.loads(
cros_config_schema.FilterBuildElements(
cros_config_schema.TransformConfig(BASIC_CONFIG), ['/firmware']))
self.assertNotIn('firmware', json_dict['chromeos']['configs'][0])
class GetValidSchemaProperties(cros_test_lib.TestCase):
def testGetValidSchemaProperties(self):
schema_props = cros_config_schema.GetValidSchemaProperties()
self.assertIn('cras-config-dir', schema_props['/audio/main'])
self.assertIn('key-id', schema_props['/firmware-signing'])
self.assertIn('files', schema_props['/audio/main'])
self.assertIn('has-touchscreen', schema_props['/hardware-properties'])
self.assertIn('count', schema_props['/camera'])
class MainTests(cros_test_lib.TempDirTestCase):
def _GetSchemaYaml(self):
with open(os.path.join(
this_dir, 'cros_config_schema.yaml')) as schema_stream:
schema_contents = schema_stream.read()
return yaml.load(schema_contents, Loader=yaml.SafeLoader)
def assertFileEqual(self, file_expected, file_actual, regen_cmd=''):
self.assertTrue(os.path.isfile(file_expected),
'Expected file does not exist at path: {}'
.format(file_expected))
self.assertTrue(os.path.isfile(file_actual),
'Actual file does not exist at path: {}'
.format(file_actual))
with open(file_expected, 'r') as expected, open(file_actual, 'r') as actual:
for line_num, (line_expected, line_actual) in \
enumerate(zip_longest(expected, actual)):
self.assertEqual(line_expected, line_actual, \
('Files differ at line {0}\n'
'Expected: {1}\n'
'Actual : {2}\n'
'Path of expected output file: {3}\n'
'Path of actual output file: {4}\n'
'{5}').format(line_num, repr(line_expected), repr(line_actual),
file_expected, file_actual, regen_cmd))
def assertMultilineStringEqual(self, str_expected, str_actual):
expected = str_expected.strip().split('\n')
actual = str_actual.strip().split('\n')
for line_num, (line_expected, line_actual) in \
enumerate(zip_longest(expected, actual)):
self.assertEqual(line_expected, line_actual, \
('Strings differ at line {0}\n'
'Expected: {1}\n'
'Actual : {2}\n').format(line_num, repr(line_expected),
repr(line_actual)))
def testMainWithExampleWithBuildAndMosysCBindings(self):
json_output = os.path.join(self.tempdir, 'output.json')
c_output = os.path.join(self.tempdir, 'config.c')
cros_config_schema.Main(
None,
os.path.join(this_dir, '../test_data/test.yaml'),
json_output,
gen_c_output_dir=self.tempdir)
regen_cmd = ('To regenerate the expected output, run:\n'
'\tpython -m cros_config_host.cros_config_schema '
'-c test_data/test.yaml '
'-o test_data/test_build.json '
'-g test_data')
expected_json_file = \
os.path.join(this_dir, '../test_data/test_build.json')
self.assertFileEqual(expected_json_file, json_output, regen_cmd)
expected_c_file = os.path.join(this_dir, '../test_data/test.c')
self.assertFileEqual(expected_c_file, c_output, regen_cmd)
def testMainWithExampleWithoutBuild(self):
output = os.path.join(self.tempdir, 'output')
cros_config_schema.Main(
None,
os.path.join(this_dir, '../test_data/test.yaml'),
output,
filter_build_details=True)
regen_cmd = ('To regenerate the expected output, run:\n'
'\tpython -m cros_config_host.cros_config_schema '
'-f True '
'-c test_data/test.yaml '
'-o test_data/test.json')
expected_file = os.path.join(this_dir, '../test_data/test.json')
self.assertFileEqual(expected_file, output, regen_cmd)
def testMainArmExample(self):
json_output = os.path.join(self.tempdir, 'output.json')
c_output = os.path.join(self.tempdir, 'config.c')
cros_config_schema.Main(
None,
os.path.join(this_dir, '../test_data/test_arm.yaml'),
json_output,
filter_build_details=True,
gen_c_output_dir=self.tempdir)
regen_cmd = ('To regenerate the expected output, run:\n'
'\tpython -m cros_config_host.cros_config_schema '
'-f True '
'-c test_data/test_arm.yaml '
'-o test_data/test_arm.json '
'-g test_data')
expected_json_file = \
os.path.join(this_dir, '../test_data/test_arm.json')
self.assertFileEqual(expected_json_file, json_output, regen_cmd)
expected_c_file = os.path.join(this_dir, '../test_data/test_arm.c')
self.assertFileEqual(expected_c_file, c_output, regen_cmd)
def testMainImportExample(self):
output = os.path.join(self.tempdir, 'output')
cros_config_schema.Main(
None,
os.path.join(this_dir, '../test_data/test_import.yaml'),
output)
regen_cmd = ('To regenerate the expected output, run:\n'
'\tpython -m cros_config_host.cros_config_schema '
'-c test_data/test_import.yaml '
'-o test_data/test_import.json')
expected_file = os.path.join(this_dir, '../test_data/test_import.json')
self.assertFileEqual(expected_file, output, regen_cmd)
def testMainMergeExample(self):
output = os.path.join(self.tempdir, 'output')
base_path = os.path.join(this_dir, '../test_data')
cros_config_schema.Main(
None,
None,
output,
configs=[os.path.join(base_path, 'test_merge_base.yaml'),
os.path.join(base_path, 'test_merge_overlay.yaml')])
regen_cmd = ('To regenerate the expected output, run:\n'
'\tpython -m cros_config_host.cros_config_schema '
'-o test_data/test_merge.json '
'-m test_data/test_merge_base.yaml '
'test_data/test_merge_overlay.yaml')
expected_file = os.path.join(this_dir, '../test_data/test_merge.json')
self.assertFileEqual(expected_file, output, regen_cmd)
def testClangFormat(self):
test_input = 'int main(int argc, char **argv) { return 0; }'
expected = textwrap.dedent("""\
int main(int argc, char** argv) {
return 0;
}""")
formatted = cros_config_schema.ClangFormat(test_input)
self.assertEqual(formatted, expected)
if __name__ == '__main__':
cros_test_lib.main(module=__name__)