Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

Binary file not shown.

View file

@ -0,0 +1,127 @@
{
"compData": {
"frameDuration": 0.028914
},
"lineData": [
{
"lineData":[
{
"fillGroup": "A",
"startPoint": [2.0, 2.0],
"endPoint": [2.0, 3.0],
"thickness": 3,
"color": [0, 0, 0, 1]
},
{
"fillGroup": "A",
"startPoint": [2.0, 3.0],
"endPoint": [3.0, 2.0],
"thickness": 3,
"color": [0, 0, 0, 1]
},
{
"fillGroup": "_",
"startPoint": [2.0, 3.0],
"endPoint": [4.0, 5.0],
"thickness": 3,
"color": [0, 0, 0, 1]
},
{
"fillGroup": "B",
"startPoint": [4.0, 4.0],
"endPoint": [4.0, 5.0],
"thickness": 3,
"color": [0, 0, 0, 1]
},
{
"fillGroup": "A",
"startPoint": [2.0, 2.0],
"endPoint": [3.0, 2.0],
"thickness": 3,
"color": [0, 0, 0, 1]
},
{
"fillGroup": "B",
"startPoint": [5.0, 4.0],
"endPoint": [4.0, 4.0],
"thickness": 3,
"color": [0, 0, 0, 1]
},
{
"fillGroup": "B",
"startPoint": [5.0, 4.0],
"endPoint": [4.0, 5.0],
"thickness": 3,
"color": [0, 0, 0, 1]
},
{
"fillGroup": "_",
"startPoint": [5.0, 4.0],
"endPoint": [3.0, 2.0],
"thickness": 3,
"color": [0, 0, 0, 1]
}
]
},
{
"lineData":[
{
"fillGroup": "A",
"startPoint": [123123123.0, 2.0],
"endPoint": [2.0, 3.0],
"thickness": 3,
"color": [0, 0, 0, 1]
},
{
"fillGroup": "A",
"startPoint": [123123123.0, 3.0],
"endPoint": [3.0, 2.0],
"thickness": 3,
"color": [0, 0, 0, 1]
},
{
"fillGroup": "_",
"startPoint": [456456456.0, 3.0],
"endPoint": [4.0, 5.0],
"thickness": 3,
"color": [0, 0, 0, 1]
},
{
"fillGroup": "B",
"startPoint": [4.0, 4.0],
"endPoint": [4.0, 5.0],
"thickness": 3,
"color": [0, 0, 0, 1]
},
{
"fillGroup": "A",
"startPoint": [123123123.0, 2.0],
"endPoint": [3.0, 2.0],
"thickness": 3,
"color": [0, 0, 0, 1]
},
{
"fillGroup": "B",
"startPoint": [5.0, 4.0],
"endPoint": [4.0, 4.0],
"thickness": 3,
"color": [0, 0, 0, 1]
},
{
"fillGroup": "B",
"startPoint": [5.0, 4.0],
"endPoint": [4.0, 5.0],
"thickness": 3,
"color": [0, 0, 0, 1]
},
{
"fillGroup": "_",
"startPoint": [5.0, 4.0],
"endPoint": [3.0, 2.0],
"thickness": 3,
"color": [0, 0, 0, 1]
}
]
}
]
}

View file

@ -0,0 +1,217 @@
{
"good_using_string_ids":{
"comments":"Haptic double-pulse",
"notes":[
{
"id":"short_pulse",
"vibe_duration_ms":15,
"brake_duration_ms":9,
"strength":100
},
{
"id":"delay_100",
"vibe_duration_ms":100,
"brake_duration_ms":0,
"strength":0
}
],
"pattern":[
"short_pulse",
"delay_100",
"short_pulse"
]
},
"good_using_numeric_ids":{
"comments":"Numeric ids",
"notes":[
{
"id":17,
"vibe_duration_ms":15,
"brake_duration_ms":9,
"strength":100
},
{
"id":"delay_100",
"vibe_duration_ms":100,
"brake_duration_ms":0,
"strength":0
}
],
"pattern":[
17,
"delay_100",
17
]
},
"good_negative_strength":{
"comments":"Random pattern",
"notes":[
{
"id":"long_pulse",
"vibe_duration_ms":1700,
"brake_duration_ms":120,
"strength":-76
},
{
"id":"medium_pulse",
"vibe_duration_ms":900,
"brake_duration_ms":100,
"strength":-50
},
{
"id":"delay_2000",
"vibe_duration_ms":2000,
"brake_duration_ms":0,
"strength":0
}
],
"pattern":[
"medium_pulse",
"medium_pulse",
"delay_2000",
"long_pulse",
"delay_2000",
"long_pulse"
]
},
"bad_no_pattern":{
"comments":"No pattern",
"notes":[
{
"id":"short_pulse",
"vibe_duration_ms":15,
"brake_duration_ms":9,
"strength":100
},
{
"id":"delay_100",
"vibe_duration_ms":100,
"brake_duration_ms":0,
"strength":0
}
]
},
"bad_empty_pattern":{
"comments":"No pattern",
"notes":[
{
"id":"short_pulse",
"vibe_duration_ms":15,
"brake_duration_ms":9,
"strength":100
},
{
"id":"delay_100",
"vibe_duration_ms":100,
"brake_duration_ms":0,
"strength":0
}
],
"pattern":[]
},
"bad_reference_nonexistent_id":{
"comments":"Reference nonexistent id",
"notes":[
{
"id":"delay_100",
"vibe_duration_ms":100,
"brake_duration_ms":0,
"strength":0
}
],
"pattern":[
"short_pulse",
"delay_100",
"short_pulse"
]
},
"bad_negative_vibe_duration":{
"comments":"Negative duration",
"notes":[
{
"id":"short_pulse",
"vibe_duration_ms":-15,
"brake_duration_ms":9,
"strength":100
},
{
"id":"delay_100",
"vibe_duration_ms":100,
"brake_duration_ms":0,
"strength":0
}
],
"pattern":[
"short_pulse",
"delay_100",
"short_pulse"
]
},
"bad_negative_brake_duration":{
"comments":"Negative duration",
"notes":[
{
"id":"short_pulse",
"vibe_duration_ms":15,
"brake_duration_ms":-9,
"strength":100
},
{
"id":"delay_100",
"vibe_duration_ms":100,
"brake_duration_ms":0,
"strength":0
}
],
"pattern":[
"short_pulse",
"delay_100",
"short_pulse"
]
},
"bad_strength_greater_than_100":{
"comments":"Strength greater than 100",
"notes":[
{
"id":"short_pulse",
"vibe_duration_ms":15,
"brake_duration_ms":9,
"strength":150
},
{
"id":"delay_100",
"vibe_duration_ms":100,
"brake_duration_ms":0,
"strength":0
}
],
"pattern":[
"short_pulse",
"delay_100",
"short_pulse"
]
},
"nonzero_repeating_delay":{
"comments":"Haptic double-pulse",
"notes":[
{
"id":"short_pulse",
"vibe_duration_ms":15,
"brake_duration_ms":9,
"strength":100
},
{
"id":"delay_100",
"vibe_duration_ms":100,
"brake_duration_ms":0,
"strength":0
}
],
"pattern":[
"short_pulse",
"delay_100",
"short_pulse"
],
"repeat_delay_ms":1092
}
}

View file

@ -0,0 +1,110 @@
# Copyright 2024 Google LLC
#
# 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
#
# http://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.
import os
import sys
import tempfile
import unittest
# Allow us to run even if not at the `tools` directory.
root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
sys.path.insert(0, root_dir)
from app_header import PebbleAppHeader
from uuid import UUID
V1_APP_HEADER = "\x50\x42\x4C\x41\x50\x50\x00\x00\x08\x01\x03\x01\x03\x00" \
"\xD8\x1A\x34\x0A\x00\x00\xC6\xF1\x2E\x8B\x57\x46\x47\x20" \
"\x44\x65\x6D\x6F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x57\x61\x74\x63\x68\x66\x61\x63\x65\x20\x47\x65\x6E\x65" \
"\x72\x61\x74\x6F\x72\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x01\x00\x00\x00\x00\x13\x00\x00\x01\x00" \
"\x00\x00\xD8\x1A\x00\x00\x22\x00\x00\x00\xC9\x5A\x9A\x75" \
"\x6E\x8C\x01\x59\xD3\xE0\x2F\x94\x1F\xA6\xB9\x75"
V2_APP_HEADER = "\x50\x42\x4C\x41\x50\x50\x00\x00\x10\x00\x05\x00\x01\x00" \
"\xA1\x0C\x08\x05\x00\x00\x06\x3E\x92\x94\x57\x46\x47\x20" \
"\x44\x65\x6D\x6F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x57\x61\x74\x63\x68\x66\x61\x63\x65\x2D\x47\x65\x6E\x65" \
"\x72\x61\x74\x6F\x72\x2E\x64\x65\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x01\x00\x00\x00\x94\x00\x00\x00\x01\x00" \
"\x00\x00\x04\x00\x00\x00\x13\x37\x13\x37\xD7\xAA\x1F\xEB" \
"\xB8\x78\x99\x91\x62\x89\xCD\x1E\x07\x60\x88\xCF\xCE\x4A" \
"\xD0\x52\xC8\x0D"
class TestAppHeader(unittest.TestCase):
def test_deserialize_v1_header(self):
h = PebbleAppHeader(V1_APP_HEADER)
self.assertEquals(h.sentinel, "PBLAPP\x00\x00")
self.assertEquals(h.struct_version_major,
PebbleAppHeader.V1_STRUCT_VERSION[0])
self.assertEquals(h.struct_version_minor,
PebbleAppHeader.V1_STRUCT_VERSION[1])
self.assertEquals(h.sdk_version_major, 0x03)
self.assertEquals(h.sdk_version_minor, 0x01)
self.assertEquals(h.app_version_major, 0x03)
self.assertEquals(h.app_version_minor, 0x00)
self.assertEquals(h.app_size, 6872)
self.assertEquals(h.offset, 2612)
self.assertEquals(h.crc, 0x8b2ef1c6)
self.assertEquals(h.app_name, "WFG Demo")
self.assertEquals(h.company_name, "Watchface Generator")
self.assertEquals(h.icon_resource_id, 1)
self.assertEquals(h.symbol_table_addr, 4864)
self.assertEquals(h.flags, 1)
self.assertEquals(h.relocation_list_index, 6872)
self.assertEquals(h.num_relocation_entries, 34)
self.assertEquals(h.uuid, UUID('c95a9a75-6e8c-0159-d3e0-2f941fa6b975'))
def test_deserialize_v2_header(self):
h = PebbleAppHeader(V2_APP_HEADER)
self.assertEquals(h.sentinel, "PBLAPP\x00\x00")
self.assertEquals(h.struct_version_major,
PebbleAppHeader.V2_STRUCT_VERSION[0])
self.assertEquals(h.struct_version_minor,
PebbleAppHeader.V2_STRUCT_VERSION[1])
self.assertEquals(h.sdk_version_major, 0x05)
self.assertEquals(h.sdk_version_minor, 0x00)
self.assertEquals(h.app_version_major, 0x01)
self.assertEquals(h.app_version_minor, 0x00)
self.assertEquals(h.app_size, 3233)
self.assertEquals(h.offset, 1288)
self.assertEquals(h.crc, 0x94923e06)
self.assertEquals(h.app_name, "WFG Demo")
self.assertEquals(h.company_name, "Watchface-Generator.de")
self.assertEquals(h.icon_resource_id, 1)
self.assertEquals(h.symbol_table_addr, 148)
self.assertEquals(h.flags, 1)
self.assertEquals(h.num_relocation_entries, 4)
self.assertEquals(h.uuid, UUID('13371337-d7aa-1feb-b878-99916289cd1e'))
self.assertEquals(h.resource_crc, 0xcf886007)
self.assertEquals(h.resource_timestamp, 1389382350)
self.assertEquals(h.virtual_size, 3528)
def test_deserialize_serialize_v1(self):
h = PebbleAppHeader(V1_APP_HEADER)
bytes = h.serialize()
self.assertEquals(bytes, V1_APP_HEADER)
def test_deserialize_serialize_v2(self):
h = PebbleAppHeader(V2_APP_HEADER)
bytes = h.serialize()
self.assertEquals(bytes, V2_APP_HEADER)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,145 @@
# Copyright 2024 Google LLC
#
# 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
#
# http://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.
import os
import sys
import unittest
# Allow us to run even if not at the `tools` directory.
root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
sys.path.insert(0, root_dir)
from log_hashing.check_elf_log_strings import check_dict_log_strings
class TestCheckLogStrings(unittest.TestCase):
def test_some_acceptible_strings(self):
log_dict = {
1: {'file': 'test.c', 'line': '1', 'msg': 'test %s'},
2: {'file': 'test.c', 'line': '2', 'msg': 'test %-2hx'},
3: {'file': 'test.c', 'line': '3', 'msg': 'test %08X'},
4: {'file': 'test.c', 'line': '4', 'msg': 'test %14d'},
5: {'file': 'test.c', 'line': '5', 'msg': 'test %p %d %02X %x'}
}
output = check_dict_log_strings(log_dict)
self.assertEquals(output, '')
def test_rule_no_backtick(self):
log_dict = {1: {'file': 'test.c', 'line': '1', 'msg': 'test ` '}}
file_line = ':'.join((log_dict[1]['file'], log_dict[1]['line']))
output = check_dict_log_strings(log_dict)
self.assertTrue(file_line in output and "PBL_LOG contains '`'" in output)
def test_rule_no_percent(self):
log_dict = {1: {'file': 'test.c', 'line': '1', 'msg': 'test 10%%'}}
file_line = ':'.join((log_dict[1]['file'], log_dict[1]['line']))
output = check_dict_log_strings(log_dict)
self.assertTrue(file_line in output and "PBL_LOG contains '%%'" in output)
def test_rule_no_64_bit(self):
log_dict = {1: {'file': 'test.c', 'line': '1', 'msg': 'test %llu'}}
file_line = ':'.join((log_dict[1]['file'], log_dict[1]['line']))
output = check_dict_log_strings(log_dict)
self.assertTrue(file_line in output and "PBL_LOG contains 64 bit value" in output)
def test_rule_no_floats(self):
log_dict = {1: {'file': 'test.c', 'line': '1', 'msg': 'test %6.2f'}}
file_line = ':'.join((log_dict[1]['file'], log_dict[1]['line']))
output = check_dict_log_strings(log_dict)
self.assertTrue(file_line in output and
"PBL_LOG contains floating point specifier" in output)
def test_rule_no_formatted_strings(self):
log_dict = {1: {'file': 'test.c', 'line': '1', 'msg': 'test %.*s'}}
file_line = ':'.join((log_dict[1]['file'], log_dict[1]['line']))
output = check_dict_log_strings(log_dict)
self.assertTrue(file_line in output and
"PBL_LOG contains a formatted string conversion" in output)
def test_rule_dynamic_width(self):
log_dict = {1: {'file': 'test.c', 'line': '1', 'msg': 'test %*d'}}
file_line = ':'.join((log_dict[1]['file'], log_dict[1]['line']))
output = check_dict_log_strings(log_dict)
self.assertTrue(file_line in output and
"PBL_LOG contains a dynamic width" in output)
def test_rule_no_flagged_strings(self):
log_dict = {1: {'file': 'test.c', 'line': '1', 'msg': 'test %-10s'}}
file_line = ':'.join((log_dict[1]['file'], log_dict[1]['line']))
output = check_dict_log_strings(log_dict)
self.assertTrue(file_line in output and
"PBL_LOG contains a formatted string conversion" in output)
def test_rule_7_conversions(self):
log_dict = {1: {'file': 'test.c', 'line': '1', 'msg': 'test %s %d %0d %x %08X %s %p %ul'}}
file_line = ':'.join((log_dict[1]['file'], log_dict[1]['line']))
output = check_dict_log_strings(log_dict)
self.assertTrue(file_line in output and
"PBL_LOG contains more than 7 format conversions" in output)
def test_rule_2_string_conversions(self):
log_dict = {1: {'file': 'test.c', 'line': '1', 'msg': 'test %s %08X %s %s %ul'}}
file_line = ':'.join((log_dict[1]['file'], log_dict[1]['line']))
output = check_dict_log_strings(log_dict)
self.assertTrue(file_line in output and
"PBL_LOG contains more than 2 string conversions" in output)
def test_rule_unknown_specifier(self):
log_dict = {1: {'file': 'test.c', 'line': '1', 'msg': 'test %Z'}}
file_line = ':'.join((log_dict[1]['file'], log_dict[1]['line']))
output = check_dict_log_strings(log_dict)
self.assertTrue(file_line in output and
"PBL_LOG contains unknown format specifier" in output)
def test_rule_no_specifier(self):
log_dict = {1: {'file': 'test.c', 'line': '1', 'msg': '%'}}
file_line = ':'.join((log_dict[1]['file'], log_dict[1]['line']))
output = check_dict_log_strings(log_dict)
self.assertTrue(file_line in output and
"PBL_LOG contains unknown format specifier" in output)
def test_valid_level_number(self):
log_dict = {1: {'file': 'test.c', 'line': '1', 'msg': '%', 'level': '69'}}
file_line = ':'.join((log_dict[1]['file'], log_dict[1]['line']))
output = check_dict_log_strings(log_dict)
self.assertTrue(file_line in output and
"PBL_LOG contains a non-constant LOG_LEVEL_ value '69'" in output)
def test_valid_level_line(self):
log_dict = {1: {'file': 'test.c', 'line': '1', 'msg': '%', 'level': 'variable_name'}}
file_line = ':'.join((log_dict[1]['file'], log_dict[1]['line']))
output = check_dict_log_strings(log_dict)
self.assertTrue(file_line in output and
"PBL_LOG contains a non-constant LOG_LEVEL_ value 'variable_name'"
in output)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,143 @@
# Copyright 2024 Google LLC
#
# 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
#
# http://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.
import json
import mock
import nose
import os
import sys
import unittest
# Allow us to run even if not at the `tools` directory.
root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
sys.path.insert(0, root_dir)
import deploy_pbz_to_pebblefw
class TestDeployPbzToPebbleFw(unittest.TestCase):
DUMMY_MANIFEST_CONTENT = {
'versionTag': 'v3.1',
'timestamp': 1377981535,
'hwrev': 'bb2',
'type': 'normal'
}
@mock.patch('boto.connect_s3')
@mock.patch('boto.s3')
@mock.patch('deploy_pbz_to_pebblefw.open')
@mock.patch('deploy_pbz_to_pebblefw._sha256_file')
@mock.patch('requests.get')
@mock.patch('libpebble2.util.bundle.PebbleBundle')
def test_no_latest_exists_dry_run(self, mock_pebble_bundle, mock_requests_get, mock_sha256_file,
mock_open, mock_boto_s3, mock_boto_connect_s3):
# Set up our mocks
mock_pebble_bundle_instance = mock_pebble_bundle.return_value
mock_pebble_bundle_instance.get_manifest.return_value = {
'firmware': self.DUMMY_MANIFEST_CONTENT
}
dummy_sha = '620cfcadc8d28240048ffa01eb6984b06774a584349f178564e1548ecc813903'
mock_sha256_file.return_value = dummy_sha
mock_requests_get.return_value.status_code = 403
dummy_notes = 'Dummy notes'
mock.mock_open(mock_open, read_data=dummy_notes)
# Run the code under test
dummy_pbz_path = 'dummy_pbz.pbz'
dummy_notes_path = 'dummy_notes.txt'
deploy_pbz_to_pebblefw.deploy_bundle(dummy_pbz_path, 'pebblefw-staging', 'porksmoothie',
dummy_notes_path, dry_run=True)
# Check out mocks to make sure everything worked
mock_sha256_file.assert_called_with(dummy_pbz_path)
mock_open.assert_called_with(dummy_notes_path, 'r')
mock_requests_get.assert_called_with(
'https://pebblefw-staging.s3.amazonaws.com/pebble/bb2/porksmoothie/latest.json')
# We're using dry_run=True, we better not talk to s3
assert not mock_boto_connect_s3.called
@mock.patch('boto.connect_s3')
@mock.patch('boto.s3')
@mock.patch('deploy_pbz_to_pebblefw.open')
@mock.patch('deploy_pbz_to_pebblefw._sha256_file')
@mock.patch('requests.get')
@mock.patch('libpebble2.util.bundle.PebbleBundle')
def test_no_latest_exists_no_dry_run(self, mock_pebble_bundle, mock_requests_get,
mock_sha256_file, mock_open, mock_boto_s3,
mock_boto_connect_s3):
# Set up our mocks
mock_pebble_bundle_instance = mock_pebble_bundle.return_value
mock_pebble_bundle_instance.get_manifest.return_value = {
'firmware': self.DUMMY_MANIFEST_CONTENT
}
dummy_sha = '620cfcadc8d28240048ffa01eb6984b06774a584349f178564e1548ecc813903'
mock_sha256_file.return_value = dummy_sha
mock_requests_get.return_value.status_code = 403
dummy_notes = 'Dummy notes'
mock.mock_open(mock_open, read_data=dummy_notes)
mock_latest_key = mock.MagicMock()
def boto_key_func(boto_bucket, key):
if key == 'pebble/bb2/porksmoothie/latest.json':
# Only validate us uploading to latest.json and return unamed mocks for the other
# paths.
return mock_latest_key
return mock.DEFAULT
mock_boto_s3.key.Key.side_effect = boto_key_func
# Run the code under test
dummy_pbz_path = 'dummy_pbz.pbz'
dummy_notes_path = 'dummy_notes.txt'
deploy_pbz_to_pebblefw.deploy_bundle(dummy_pbz_path, 'pebblefw-staging', 'porksmoothie',
dummy_notes_path)
# Check out mocks to make sure everything worked
mock_sha256_file.assert_called_with(dummy_pbz_path)
mock_open.assert_called_with(dummy_notes_path, 'r')
mock_requests_get.assert_called_with(
'https://pebblefw-staging.s3.amazonaws.com/pebble/bb2/porksmoothie/latest.json')
assert mock_boto_connect_s3.called
expected_new_latest_json = {
'normal': {
'url': 'https://pebblefw-staging.s3.amazonaws.com/pebble/bb2/porksmoothie/pbz/' +
os.path.basename(dummy_pbz_path),
'timestamp': self.DUMMY_MANIFEST_CONTENT['timestamp'],
'notes': dummy_notes,
'friendlyVersion': self.DUMMY_MANIFEST_CONTENT['versionTag'],
'sha-256': dummy_sha
}
}
actual_new_latest_json = mock_latest_key.set_contents_from_string.call_args[0][0]
# Send it through the json loader to normalize any formatting
actual_new_latest_json = json.dumps(json.loads(actual_new_latest_json))
nose.tools.eq_(json.dumps(expected_new_latest_json), actual_new_latest_json)

83
tools/tests/test_hdlc.py Normal file
View file

@ -0,0 +1,83 @@
# Copyright 2024 Google LLC
#
# 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
#
# http://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.
import os
import sys
import tempfile
import unittest
# Allow us to run even if not at the `tools` directory.
root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
sys.path.insert(0, root_dir)
from hdlc import HDLCDecoder, hdlc_encode_data
class TestHDLCDecode(unittest.TestCase):
def test_line_noise(self):
decoder = HDLCDecoder()
decoder.write(b'This is a bunch')
decoder.write(b'of line noise!!\x7e')
decoder.write(b'\x7eFollowed by a valid frame\x7e')
self.assertEquals(decoder.get_frame(), 'Followed by a valid frame')
self.assertIsNone(decoder.get_frame())
def test_segmented_frames(self):
decoder = HDLCDecoder()
decoder.write(b'\x7eFrame ')
decoder.write(b'one\x7eFrame two')
decoder.write(b'\x7eFrame thr')
decoder.write(b'ee\x7e')
self.assertEquals(decoder.get_frame(), 'Frame one')
self.assertEquals(decoder.get_frame(), 'Frame two')
self.assertEquals(decoder.get_frame(), 'Frame three')
self.assertIsNone(decoder.get_frame())
def test_empty(self):
decoder = HDLCDecoder()
decoder.write(b'\x7e\x7e\x7e\x7e')
self.assertIsNone(decoder.get_frame())
def test_escape(self):
decoder = HDLCDecoder()
decoder.write(b'\x7eHow about escaping?\x7d\x5e\x7e')
decoder.write(b'\x7eAny ch\x7dArac\x7dTer can be \x7dE\x7dS\x7dC\x7dA\x7dPed!\x7e')
decoder.write(b'\x7eEven a \x7d\x7d\x7e')
self.assertEquals(decoder.get_frame(), 'How about escaping?\x7e')
self.assertEquals(decoder.get_frame(), 'Any character can be escaped!')
self.assertEquals(decoder.get_frame(), 'Even a \x5d')
self.assertIsNone(decoder.get_frame())
def test_invalid(self):
decoder = HDLCDecoder()
decoder.write(b'\x7eInvalid termination\x7d\x7e')
decoder.write(b'\x7eI am valid\x7e')
decoder.write(b'partial frame')
self.assertEquals(decoder.get_frame(), 'I am valid')
self.assertIsNone(decoder.get_frame())
class TestHDLCEncodeData(unittest.TestCase):
def test_simple(self):
self.assertEquals(hdlc_encode_data('This is easy'), '\x7eThis is easy\x7e')
def test_escape(self):
self.assertEquals(hdlc_encode_data('Escape \x7d\x7e!'), '\x7eEscape \x7d\x5d\x7d\x5e!\x7e')
def test_empty(self):
self.assertEquals(hdlc_encode_data(''), '\x7e\x7e')
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,506 @@
# Copyright 2024 Google LLC
#
# 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
#
# http://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.
import os
import sys
import unittest
import array
from struct import pack
from generate_pdcs import json2commands, pebble_commands
class MyTestCase(unittest.TestCase):
def test_parse_color(self):
truncate = True
# Test valid values
color_opacity = [0, 0.333, 0.666, 1]
color = json2commands.parse_color(color_opacity, truncate)
self.assertEqual(color, pebble_commands.convert_color(0, 85, 170, 255, truncate))
# Test invalid values
color_opacity = [0, 0.333, 0.666, 2]
color = json2commands.parse_color(color_opacity, truncate)
self.assertEqual(color, 0)
color_opacity = [0, 0.333, 0.666, -2]
color = json2commands.parse_color(color_opacity, truncate)
self.assertEqual(color, 0)
def test_parse_json_line_data(self):
# Test skipping of invisible segments
json_line_data = [{
"startPoint": [json2commands.INVISIBLE_POINT_THRESHOLD + 1, 1.0],
"endPoint": [2.0, 1.0]
},{
"startPoint": [1.0, 1.0],
"endPoint": [2.0, 1.0]
}]
bidirectional_lines = json2commands.parse_json_line_data(json_line_data)
self.assertEqual(bidirectional_lines, [ ((1.0, 1.0), (2.0, 1.0)),
((2.0, 1.0), (1.0, 1.0))])
json_line_data = [{
"startPoint": [2.0, 1.0],
"endPoint": [2.0, -json2commands.INVISIBLE_POINT_THRESHOLD - 1]
}]
bidirectional_lines = json2commands.parse_json_line_data(json_line_data)
self.assertEqual(bidirectional_lines, [])
# Test skipping of duplicate segments
json_line_data = [{
"startPoint": [1.0, 1.0],
"endPoint": [2.0, 1.0]
},{
"startPoint": [2.0, 1.0],
"endPoint": [1.0, 1.0]
},{
"startPoint": [1.0, 1.0],
"endPoint": [2.0, 1.0]
}]
bidirectional_lines = json2commands.parse_json_line_data(json_line_data)
self.assertEqual(bidirectional_lines, [ ((1.0, 1.0), (2.0, 1.0)),
((2.0, 1.0), (1.0, 1.0))])
def test_determine_longest_path(self):
# Test point
json_line_data = [{
"startPoint": [1.0, 1.0],
"endPoint": [1.0, 1.0]
}]
bidirectional_lines = json2commands.parse_json_line_data(json_line_data)
longest_path = json2commands.determine_longest_path(bidirectional_lines)
self.assertEqual(longest_path, [(1.0, 1.0), (1.0, 1.0)])
self.assertEqual(len(bidirectional_lines), 0)
# Test simplest line segment
json_line_data = [{
"startPoint": [1.0, 1.0],
"endPoint": [2.0, 1.0]
}]
bidirectional_lines = json2commands.parse_json_line_data(json_line_data)
longest_path = json2commands.determine_longest_path(bidirectional_lines)
self.assertEqual(longest_path, [(1.0, 1.0), (2.0, 1.0)])
self.assertEqual(len(bidirectional_lines), 0)
# Test ordered connected line segments
json_line_data = [{
"startPoint": [1.0, 1.0],
"endPoint": [2.0, 1.0]
},{
"startPoint": [2.0, 1.0],
"endPoint": [2.0, 2.0]
}]
bidirectional_lines = json2commands.parse_json_line_data(json_line_data)
longest_path = json2commands.determine_longest_path(bidirectional_lines)
self.assertEqual(longest_path, [(1.0, 1.0), (2.0, 1.0), (2.0, 2.0)])
self.assertEqual(len(bidirectional_lines), 0)
# Test unordered connected acyclic line segments
json_line_data = [{
"startPoint": [1.0, 1.0],
"endPoint": [2.0, 1.0]
},{
"startPoint": [2.0, 2.0],
"endPoint": [2.0, 1.0]
}]
bidirectional_lines = json2commands.parse_json_line_data(json_line_data)
longest_path = json2commands.determine_longest_path(bidirectional_lines)
self.assertEqual(longest_path, [(1.0, 1.0), (2.0, 1.0), (2.0, 2.0)])
self.assertEqual(len(bidirectional_lines), 0)
# Test connected simplest cyclic segments (circle)
json_line_data = [{
"startPoint": [1.0, 1.0],
"endPoint": [2.0, 1.0]
},{
"startPoint": [2.0, 1.0],
"endPoint": [2.0, 2.0]
},{
"startPoint": [2.0, 2.0],
"endPoint": [1.0, 1.0]
}]
bidirectional_lines = json2commands.parse_json_line_data(json_line_data)
longest_path = json2commands.determine_longest_path(bidirectional_lines)
self.assertEqual(longest_path, [(1.0, 1.0), (2.0, 1.0), (2.0, 2.0), (1.0, 1.0)])
self.assertEqual(len(bidirectional_lines), 0)
# Test connected complex cyclic line segments
json_line_data = [{
"startPoint": [1.0, 1.0],
"endPoint": [2.0, 1.0]
},{
"startPoint": [2.0, 1.0],
"endPoint": [2.0, 2.0]
},{
"startPoint": [2.0, 2.0],
"endPoint": [1.0, 1.0]
},{
"startPoint": [1.0, 2.0],
"endPoint": [1.0, 1.0]
}]
bidirectional_lines = json2commands.parse_json_line_data(json_line_data)
longest_path = json2commands.determine_longest_path(bidirectional_lines)
self.assertEqual(longest_path, [(1.0, 2.0), (1.0, 1.0), (2.0, 1.0), (2.0, 2.0), (1.0, 1.0)])
self.assertEqual(len(bidirectional_lines), 0)
# Test connected segments with more than one path
json_line_data = [{
"startPoint": [1.0, 1.0],
"endPoint": [2.0, 1.0]
},{
"startPoint": [2.0, 2.0],
"endPoint": [2.0, 1.0]
},{
"startPoint": [3.0, 1.0],
"endPoint": [2.0, 1.0]
}]
bidirectional_lines = json2commands.parse_json_line_data(json_line_data)
longest_path = json2commands.determine_longest_path(bidirectional_lines)
self.assertEqual(longest_path, [(3.0, 1.0), (2.0, 1.0), (1.0, 1.0)])
self.assertEqual(bidirectional_lines, [((2.0, 2.0), (2.0, 1.0)), ((2.0, 1.0), (2.0, 2.0))])
longest_path = json2commands.determine_longest_path(bidirectional_lines)
self.assertEqual(longest_path, [(2.0, 1.0), (2.0, 2.0)])
self.assertEqual(len(bidirectional_lines), 0)
# Test (ordered) unconnected segments (implicitly more than one path)
json_line_data = [{
"startPoint": [1.0, 1.0],
"endPoint": [2.0, 1.0]
},{
"startPoint": [1.0, 2.0],
"endPoint": [2.0, 2.0]
}]
bidirectional_lines = json2commands.parse_json_line_data(json_line_data)
longest_path = json2commands.determine_longest_path(bidirectional_lines)
self.assertEqual(longest_path, [(1.0, 2.0), (2.0, 2.0)])
self.assertEqual(bidirectional_lines, [((1.0, 1.0), (2.0, 1.0)), ((2.0, 1.0), (1.0, 1.0))])
longest_path = json2commands.determine_longest_path(bidirectional_lines)
self.assertEqual(longest_path, [(1.0, 1.0), (2.0, 1.0)])
self.assertEqual(len(bidirectional_lines), 0)
def test_process_fill(self):
# Test that line style is taken from first segment
fillGroup_data = [{
"thickness": 3,
"color": [0, 0, 0, 1],
"startPoint": [1.0, 1.0],
"endPoint": [2.0, 1.0]
},{
"thickness": 3,
"color": [0, 0, 0.666, 0.333],
"startPoint": [2.0, 1.0],
"endPoint": [2.0, 2.0]
},{
"thickness": 3,
"color": [0.666, 0.333, 0, 0],
"startPoint": [2.0, 2.0],
"endPoint": [1.0, 1.0]
}]
truncate_color = True
fill_commands, error = json2commands.process_fill(
fillGroup_data,
(0, 0),
(json2commands.DISPLAY_DIM_X, json2commands.DISPLAY_DIM_Y),
False,
False,
False,
truncate_color)
self.assertFalse(error)
fill_command = fill_commands[0]
self.assertIsInstance(fill_command, pebble_commands.PathCommand)
self.assertEqual(len(fill_command.points), 4)
self.assertEqual(fill_command.fill_color, json2commands.parse_color([1, 1, 1, 1], truncate_color))
self.assertEqual(fill_command.stroke_color, json2commands.parse_color([0, 0, 0, 1], truncate_color))
self.assertEqual(fill_command.stroke_width, 3)
self.assertTrue(pebble_commands.compare_points(fill_command.points[0], (1.0, 1.0)))
self.assertTrue(pebble_commands.compare_points(fill_command.points[1], (2.0, 1.0)))
self.assertTrue(pebble_commands.compare_points(fill_command.points[2], (2.0, 2.0)))
self.assertTrue(pebble_commands.compare_points(fill_command.points[3], (1.0, 1.0)))
self.assertFalse(fill_command.open)
# Test that fill with no stroke width has no stroke color
fillGroup_data = [{
"thickness": 0,
"color": [0.666, 0, 0.666, 1],
"startPoint": [1.0, 1.0],
"endPoint": [2.0, 1.0]
},{
"thickness": 3,
"color": [0, 0, 0, 1],
"startPoint": [2.0, 1.0],
"endPoint": [2.0, 2.0]
},{
"thickness": 3,
"color": [0, 0, 0, 1],
"startPoint": [2.0, 2.0],
"endPoint": [1.0, 1.0]
}]
truncate_color = True
fill_commands, error = json2commands.process_fill(
fillGroup_data,
(0, 0),
(json2commands.DISPLAY_DIM_X, json2commands.DISPLAY_DIM_Y),
False,
False,
False,
truncate_color)
self.assertFalse(error)
fill_command = fill_commands[0]
self.assertIsInstance(fill_command, pebble_commands.PathCommand)
self.assertEqual(len(fill_command.points), 4)
self.assertEqual(fill_command.fill_color, json2commands.parse_color([1, 1, 1, 1], truncate_color))
self.assertEqual(fill_command.stroke_color, 0)
self.assertEqual(fill_command.stroke_width, 0)
self.assertTrue(pebble_commands.compare_points(fill_command.points[0], (1.0, 1.0)))
self.assertTrue(pebble_commands.compare_points(fill_command.points[1], (2.0, 1.0)))
self.assertTrue(pebble_commands.compare_points(fill_command.points[2], (2.0, 2.0)))
self.assertTrue(pebble_commands.compare_points(fill_command.points[3], (1.0, 1.0)))
self.assertFalse(fill_command.open)
# Test that fill with no stroke color has no stroke width
fillGroup_data = [{
"thickness": 3,
"color": [0, 1, 0.666, 0],
"startPoint": [1.0, 1.0],
"endPoint": [2.0, 1.0]
},{
"thickness": 3,
"color": [0, 0, 0, 1],
"startPoint": [2.0, 1.0],
"endPoint": [2.0, 2.0]
},{
"thickness": 3,
"color": [0, 0, 0, 1],
"startPoint": [2.0, 2.0],
"endPoint": [1.0, 1.0]
}]
truncate_color = True
fill_commands, error = json2commands.process_fill(
fillGroup_data,
(0, 0),
(json2commands.DISPLAY_DIM_X, json2commands.DISPLAY_DIM_Y),
False,
False,
False,
truncate_color)
self.assertFalse(error)
fill_command = fill_commands[0]
self.assertIsInstance(fill_command, pebble_commands.PathCommand)
self.assertEqual(len(fill_command.points), 4)
self.assertEqual(fill_command.fill_color, json2commands.parse_color([1, 1, 1, 1], truncate_color))
self.assertEqual(fill_command.stroke_color, 0)
self.assertEqual(fill_command.stroke_width, 0)
self.assertTrue(pebble_commands.compare_points(fill_command.points[0], (1.0, 1.0)))
self.assertTrue(pebble_commands.compare_points(fill_command.points[1], (2.0, 1.0)))
self.assertTrue(pebble_commands.compare_points(fill_command.points[2], (2.0, 2.0)))
self.assertTrue(pebble_commands.compare_points(fill_command.points[3], (1.0, 1.0)))
self.assertFalse(fill_command.open)
def test_process_open_paths(self):
# Test group of varying stroke width and stroke color.
fillGroup_data = [{
"thickness": 3,
"color": [0, 0, 0, 1], # 192
"startPoint": [1.0, 1.0],
"endPoint": [2.0, 2.0]
},{
"thickness": 5,
"color": [0, 0, 0.333, 0.333], # 65
"startPoint": [3.0, 3.0],
"endPoint": [4.0, 4.0]
},{
"thickness": 5,
"color": [0, 0.666, 0, 0.333], # 72
"startPoint": [4.0, 4.0],
"endPoint": [4.0, 5.0]
},{
"thickness": 3,
"color": [0, 0, 0.666, 0.333], # 66
"startPoint": [2.0, 1.0],
"endPoint": [2.0, 2.0]
},{
"thickness": 2,
"color": [0.666, 0.333, 0, 1], # 228
"startPoint": [10.0, 10.0],
"endPoint": [11.0, 11.0]
},{
"thickness": 3,
"color": [0, 0, 0, 1], # 192
"startPoint": [1.0, 2.0],
"endPoint": [2.0, 2.0]
}]
truncate_color = True
open_path_commands, error = json2commands.process_open_paths(
fillGroup_data,
(0, 0),
(json2commands.DISPLAY_DIM_X, json2commands.DISPLAY_DIM_Y),
True,
False,
False,
truncate_color)
self.assertFalse(error)
self.assertEqual(len(open_path_commands), 5)
width_2_color_228_command = open_path_commands[0]
self.assertIsInstance(width_2_color_228_command, pebble_commands.PathCommand)
self.assertEqual(width_2_color_228_command.stroke_width, 2)
self.assertEqual(width_2_color_228_command.stroke_color, json2commands.parse_color([0.666, 0.333, 0, 1],
truncate_color))
self.assertEqual(len(width_2_color_228_command.points), 2)
self.assertEqual(width_2_color_228_command.fill_color, json2commands.parse_color([0, 0, 0, 0],
truncate_color))
self.assertTrue(pebble_commands.compare_points(width_2_color_228_command.points[0], (11.0, 11.0)))
self.assertTrue(pebble_commands.compare_points(width_2_color_228_command.points[1], (10.0, 10.0)))
self.assertTrue(width_2_color_228_command.open)
width_3_color_192_command = open_path_commands[1]
self.assertIsInstance(width_3_color_192_command, pebble_commands.PathCommand)
self.assertEqual(width_3_color_192_command.stroke_width, 3)
self.assertEqual(width_3_color_192_command.stroke_color, json2commands.parse_color([0, 0, 0, 1],
truncate_color))
self.assertEqual(len(width_3_color_192_command.points), 3)
self.assertEqual(width_3_color_192_command.fill_color, json2commands.parse_color([0, 0, 0, 0],
truncate_color))
self.assertTrue(pebble_commands.compare_points(width_3_color_192_command.points[0], (1.0, 2.0)))
self.assertTrue(pebble_commands.compare_points(width_3_color_192_command.points[1], (2.0, 2.0)))
self.assertTrue(pebble_commands.compare_points(width_3_color_192_command.points[2], (1.0, 1.0)))
self.assertTrue(width_3_color_192_command.open)
width_3_color_66_command = open_path_commands[2]
self.assertIsInstance(width_3_color_66_command, pebble_commands.PathCommand)
self.assertEqual(width_3_color_66_command.stroke_width, 3)
self.assertEqual(width_3_color_66_command.stroke_color, json2commands.parse_color([0, 0, 0.666, 0.333],
truncate_color))
self.assertEqual(len(width_3_color_66_command.points), 2)
self.assertEqual(width_3_color_66_command.fill_color, json2commands.parse_color([0, 0, 0, 0],
truncate_color))
self.assertTrue(pebble_commands.compare_points(width_3_color_66_command.points[0], (2.0, 1.0)))
self.assertTrue(pebble_commands.compare_points(width_3_color_66_command.points[1], (2.0, 2.0)))
self.assertTrue(width_3_color_66_command.open)
width_5_color_65_command = open_path_commands[3]
self.assertIsInstance(width_5_color_65_command, pebble_commands.PathCommand)
self.assertEqual(width_5_color_65_command.stroke_width, 5)
self.assertEqual(width_5_color_65_command.stroke_color, json2commands.parse_color([0, 0, 0.333, 0.333],
truncate_color))
self.assertEqual(len(width_5_color_65_command.points), 2)
self.assertEqual(width_5_color_65_command.fill_color, json2commands.parse_color([0, 0, 0, 0],
truncate_color))
self.assertTrue(pebble_commands.compare_points(width_5_color_65_command.points[0], (4.0, 4.0)))
self.assertTrue(pebble_commands.compare_points(width_5_color_65_command.points[1], (3.0, 3.0)))
self.assertTrue(width_5_color_65_command.open)
width_5_color_72_command = open_path_commands[4]
self.assertIsInstance(width_5_color_72_command, pebble_commands.PathCommand)
self.assertEqual(width_5_color_72_command.stroke_width, 5)
self.assertEqual(width_5_color_72_command.stroke_color, json2commands.parse_color([0, 0.666, 0, 0.333],
truncate_color))
self.assertEqual(len(width_5_color_72_command.points), 2)
self.assertEqual(width_5_color_72_command.fill_color, json2commands.parse_color([0, 0, 0, 0],
truncate_color))
self.assertTrue(pebble_commands.compare_points(width_5_color_72_command.points[0], (4.0, 5.0)))
self.assertTrue(pebble_commands.compare_points(width_5_color_72_command.points[1], (4.0, 4.0)))
self.assertTrue(width_5_color_72_command.open)
# Test that open with no stroke width has no stroke color
fillGroup_data = [{
"thickness": 0,
"color": [0.666, 0, 0.666, 1],
"startPoint": [1.0, 1.0],
"endPoint": [2.0, 1.0]
}]
truncate_color = True
open_path_commands, error = json2commands.process_open_paths(
fillGroup_data,
(0, 0),
(json2commands.DISPLAY_DIM_X, json2commands.DISPLAY_DIM_Y),
True,
False,
False,
truncate_color)
self.assertFalse(error)
open_path_command = open_path_commands[0]
self.assertIsInstance(open_path_command, pebble_commands.PathCommand)
self.assertEqual(len(open_path_command.points), 2)
self.assertEqual(open_path_command.fill_color, json2commands.parse_color([0, 0, 0, 0], truncate_color))
self.assertEqual(open_path_command.stroke_color, 0)
self.assertEqual(open_path_command.stroke_width, 0)
self.assertTrue(pebble_commands.compare_points(open_path_command.points[0], (1.0, 1.0)))
self.assertTrue(pebble_commands.compare_points(open_path_command.points[1], (2.0, 1.0)))
self.assertTrue(open_path_command.open)
# Test that open with no stroke color has no stroke width
fillGroup_data = [{
"thickness": 3,
"color": [0, 1, 0.666, 0],
"startPoint": [1.0, 1.0],
"endPoint": [2.0, 1.0]
}]
truncate_color = True
open_path_commands, error = json2commands.process_open_paths(
fillGroup_data,
(0, 0),
(json2commands.DISPLAY_DIM_X, json2commands.DISPLAY_DIM_Y),
True,
False,
False,
truncate_color)
self.assertFalse(error)
open_path_command = open_path_commands[0]
self.assertIsInstance(open_path_command, pebble_commands.PathCommand)
self.assertEqual(len(open_path_command.points), 2)
self.assertEqual(open_path_command.fill_color, json2commands.parse_color([0, 0, 0, 0], truncate_color))
self.assertEqual(open_path_command.stroke_color, 0)
self.assertEqual(open_path_command.stroke_width, 0)
self.assertTrue(pebble_commands.compare_points(open_path_command.points[0], (1.0, 1.0)))
self.assertTrue(pebble_commands.compare_points(open_path_command.points[1], (2.0, 1.0)))
self.assertTrue(open_path_command.open)
def test_parse_json_sequence(self):
# Test mix of fills and open paths with mulitple frames
current_path = os.path.dirname(os.path.realpath(__file__))
filename = current_path + '/json2commands_test.json'
frames, errors, frame_duration = json2commands.parse_json_sequence(filename, (80, 80), False, False)
self.assertEqual(frame_duration, 28)
self.assertEqual(len(frames), 2)
self.assertEqual(len(errors), 0)
frame_1 = frames[0]
self.assertEqual(len(frame_1), 4)
self.assertFalse(frame_1[0].open)
self.assertFalse(frame_1[1].open)
self.assertTrue(frame_1[2].open)
self.assertTrue(frame_1[3].open)
frame_2 = frames[1]
self.assertEqual(len(frame_2), 2)
self.assertFalse(frame_2[0].open)
self.assertTrue(frame_2[1].open)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,224 @@
# Copyright 2024 Google LLC
#
# 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
#
# http://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.
# See pebbletechnology.atlassian.net/wiki/display/DEV/Project%3A+Vibe+Pattern+Format
import os
import sys
import unittest
import json
import struct
# Allow us to run even if not at the `tools` directory.
root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
sys.path.insert(0, root_dir)
from json2vibe import *
class TestJsonToVibe(unittest.TestCase):
def get_json_data(self, json_key):
with open(os.path.join(os.path.dirname(__file__), 'json2vibe_test.json'), 'r') as f:
return json.load(f)[json_key]
def test_check_vibe_file_serialization(self):
serialized_vibe_file = VibeFile(score=VibeScore(
version=1,
attr_list=VibeAttributeList(
attributes=[
VibeAttribute(
attribute=VibeNoteList(
notes=[
VibeNote(
vibe_duration_ms=1234,
brake_duration_ms=99,
strength=-33),
VibeNote(
vibe_duration_ms=999,
brake_duration_ms=0,
strength=0),
VibeNote(
vibe_duration_ms=55,
brake_duration_ms=19,
strength=76)])),
VibeAttribute(
attribute=VibePattern(
indices=[0, 1, 2, 2, 1, 0])),
VibeAttribute(
attribute=VibePatternRepeatDelay(
duration=500))]))).serialise()
to_test_byte_array = bytearray()
to_test_byte_array.extend(b'VIBE') # fourcc = 'VIBE'
to_test_byte_array.extend('\x01\x00') # version = 1
to_test_byte_array.extend('\x00\x00\x00\x00') # reserved
to_test_byte_array.extend('\x1E\x00') # att_list_size = 25
# GenericAttributeList
to_test_byte_array.extend('\x03') # num_attributes = 3
to_test_byte_array.extend('\x01') # VibeAttributeIdVibeNotes
to_test_byte_array.extend('\x0C\x00') # 3 notes * 4 bytes per note = 12 bytes
# First note
to_test_byte_array.extend('\xD2\x04') # vibe_duration_ms = 1234
to_test_byte_array.extend('\x63') # brake_duration_ms = 99
to_test_byte_array.extend('\xDF') # strength = -33
# Second note
to_test_byte_array.extend('\xE7\x03') # vibe_duration_ms = 999
to_test_byte_array.extend('\x00') # brake_duration_ms = 0
to_test_byte_array.extend('\x00') # strength = 0
# Third note
to_test_byte_array.extend('\x37\x00') # vibe_duration_ms = 55
to_test_byte_array.extend('\x13') # brake_duration_ms = 19
to_test_byte_array.extend('\x4C') # strength = 76
to_test_byte_array.extend('\x02') # VibeAttributeIdVibePattern
to_test_byte_array.extend('\x06\x00') # pattern contains 6 items
to_test_byte_array.extend('\x00\x01\x02\x02\x01\x00') # pattern = [0,1,2,2,1,0]
to_test_byte_array.extend('\x03') # VibeAttributeId_RepeatDelay
to_test_byte_array.extend('\x02\x00') # uint16 size in bytes
to_test_byte_array.extend('\xF4\x01') # pattern = [0,1,2,2,1,0]
self.assertEquals(bytearray(serialized_vibe_file), to_test_byte_array)
def check_proper_vibe_resource(self, serialized_data):
parsed_vibe_file, parsed_length = VibeFile().parse(serialized_data)
self.assertEquals(parsed_length, 30)
to_compare = VibeFile(fourcc='VIBE', score=VibeScore(
version=1,
reserved=None,
length=18,
attr_list=VibeAttributeList(
num_attributes=2,
attributes=[
VibeAttribute(
id=0x01,
length=8,
attribute=VibeNoteList(
notes=[
VibeNote(
vibe_duration_ms=15,
brake_duration_ms=9,
strength=100),
VibeNote(
vibe_duration_ms=100,
brake_duration_ms=0,
strength=0)])),
VibeAttribute(
id=0x02,
length=3,
attribute=VibePattern(
indices=[0, 1, 0]))])))
self.assertEquals(parsed_vibe_file, to_compare)
def test_proper_vibe_resource_string_ids(self):
json_data = self.get_json_data('good_using_string_ids')
self.check_proper_vibe_resource(serialize(json_data))
def test_vibe_resource_numeric_ids(self):
json_data = self.get_json_data('good_using_numeric_ids')
self.check_proper_vibe_resource(serialize(json_data))
def test_vibe_resource_negative_strengths(self):
json_data = self.get_json_data('good_negative_strength')
parsed_vibe_file, parsed_length = VibeFile().parse(serialize(json_data))
self.assertEquals(parsed_length, 37)
to_compare = VibeFile(fourcc='VIBE', score=VibeScore(
version=1,
reserved=None,
length=25,
attr_list=VibeAttributeList(
num_attributes=2,
attributes=[
VibeAttribute(
id=0x01,
length=12,
attribute=VibeNoteList(
notes=[
VibeNote(
vibe_duration_ms=1700,
brake_duration_ms=120,
strength=-76),
VibeNote(
vibe_duration_ms=900,
brake_duration_ms=100,
strength=-50),
VibeNote(
vibe_duration_ms=2000,
brake_duration_ms=0,
strength=0)])),
VibeAttribute(
id=0x02,
length=6,
attribute=VibePattern(
indices=[1, 1, 2, 0, 2, 0]))])))
def test_nonzero_repeating_delay(self):
json_data = self.get_json_data('nonzero_repeating_delay')
parsed_vibe_file, parsed_length = VibeFile().parse(serialize(json_data))
self.assertEquals(parsed_length, 35)
to_compare = VibeFile(fourcc='VIBE', score=VibeScore(
version=1,
reserved=None,
length=23,
attr_list=VibeAttributeList(
num_attributes=3,
attributes=[
VibeAttribute(
id=0x01,
length=8,
attribute=VibeNoteList(
notes=[
VibeNote(
vibe_duration_ms=15,
brake_duration_ms=9,
strength=100),
VibeNote(
vibe_duration_ms=100,
brake_duration_ms=0,
strength=0)])),
VibeAttribute(
id=0x02,
length=3,
attribute=VibePattern(indices=[0, 1, 0])),
VibeAttribute(
id=0x03,
length=2,
attribute=VibePatternRepeatDelay(duration=1092))])))
def test_no_pattern_throws_error(self):
with self.assertRaises(KeyError):
serialize(self.get_json_data('bad_no_pattern'))
def test_nonexistent_id_throws_error(self):
with self.assertRaises(KeyError):
serialize(self.get_json_data('bad_reference_nonexistent_id'))
def test_negative_vibe_duration_throws_error(self):
with self.assertRaisesRegexp(struct.error, 'integer out of range'):
serialize(self.get_json_data('bad_negative_vibe_duration'))
def test_negative_brake_duration_throws_error(self):
with self.assertRaisesRegexp(struct.error, 'ubyte format requires 0 <= number <= 255'):
serialize(self.get_json_data('bad_negative_brake_duration'))
def test_strength_above_100_throws_error(self):
with self.assertRaisesRegexp(
ValueError,
'"strength" 150 out of bounds. Values between -100 and 100 only.'):
serialize(self.get_json_data('bad_strength_greater_than_100'))
if __name__ == '__main__':
unittest.main()

155
tools/tests/test_pbpack.py Normal file
View file

@ -0,0 +1,155 @@
# Copyright 2024 Google LLC
#
# 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
#
# http://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.
import os
import sys
import tempfile
import unittest
# Allow us to run even if not at the `tools` directory.
root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
sys.path.insert(0, root_dir)
from pbpack import ResourcePack
import stm32_crc
SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
class TestResourcePack(unittest.TestCase):
def test_deserialize_serialize_v2(self):
filename = os.path.join(SCRIPT_DIR, 'app_resources_v2.pbpack')
self._test_deserialize_serialize_file(filename, is_system=False)
def test_deserialize_serialize_duplicate_resources(self):
is_system = False
pack = ResourcePack(is_system)
pack.add_resource('asdf')
pack.add_resource('xyz')
pack.add_resource('asdf')
after_pack = self._test_deserialize_serialize_pack(pack, is_system)
# Only one because we deduped it
self.assertEquals(len(after_pack.contents), 2)
# But we have three entries
self.assertEquals(len(after_pack.table_entries), 3)
def test_deserialize_serialize_all_duplicate_resources(self):
is_system = False
pack = ResourcePack(is_system)
pack.add_resource('asdf')
pack.add_resource('asdf')
pack.add_resource('asdf')
after_pack = self._test_deserialize_serialize_pack(pack, is_system)
# Only one because we deduped it
self.assertEquals(len(after_pack.contents), 1)
# But we have three entries
self.assertEquals(len(after_pack.table_entries), 3)
def test_deserialize_serialize_last_resource_is_a_dupe(self):
is_system = False
pack = ResourcePack(is_system)
pack.add_resource('1')
pack.add_resource('22')
pack.add_resource('333')
pack.add_resource('22')
after_pack = self._test_deserialize_serialize_pack(pack, is_system)
# Verify the content of the table
self.assertEquals(len(after_pack.contents), 3)
self.assertEquals(after_pack.contents[after_pack.table_entries[0].content_index], '1')
self.assertEquals(after_pack.contents[after_pack.table_entries[1].content_index], '22')
self.assertEquals(after_pack.contents[after_pack.table_entries[2].content_index], '333')
self.assertEquals(after_pack.contents[after_pack.table_entries[3].content_index], '22')
self.assertEquals(len(after_pack.table_entries), 4)
def test_add_empty_resources(self):
is_system = False
pack = ResourcePack(is_system)
pack.add_resource('')
pack.add_resource('asdf')
pack.add_resource('')
after_pack = self._test_deserialize_serialize_pack(pack, is_system)
# Make sure we deduped an empty resource
self.assertEquals(len(after_pack.contents), 2)
self.assertEquals(after_pack.contents[after_pack.table_entries[0].content_index], '')
self.assertEquals(after_pack.contents[after_pack.table_entries[1].content_index], 'asdf')
self.assertEquals(after_pack.contents[after_pack.table_entries[2].content_index], '')
self.assertEquals(len(after_pack.table_entries), 3)
def _test_deserialize_serialize_pack(self, pack, is_system):
"""
Serialize a given pack object to a file and then assert that if we deserialize and
serialize it again the contents remain equal. Returns a pack object after the first
deserialization round.
"""
try:
with tempfile.NamedTemporaryFile(delete=False) as f:
filename = f.name
pack.serialize(f)
# Don't call this before the file is closed, you'll confuse
# _test_deserialize_serialize_file which will try to open it again and will expect
# the contents to be available and flushed.
return self._test_deserialize_serialize_file(f.name, is_system)
finally:
os.remove(filename)
def _test_deserialize_serialize_file(self, f_in_name, is_system):
"""
Deserialize a given pack file and assert that if we serialize it again the contents
remain equal. Returns a pack object from deserializing the given file.
"""
# Read in our test file and deserialize it
with open(f_in_name, 'rb') as f_in:
resource_pack = ResourcePack.deserialize(f_in, is_system)
try:
# Write out a serialized version
with tempfile.NamedTemporaryFile(delete=False) as f_out:
f_out_name = f_out.name
resource_pack.serialize(f_out)
# Read the input and output files into buffers and make sure they're equal
def read_all(filename):
with open(filename, 'rb') as f:
f.seek(0)
return f.read()
contents_pair = map(read_all, (f_out_name, f_in_name))
self.assertEquals(*contents_pair)
finally:
os.remove(f_out_name)
return resource_pack
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,261 @@
# Copyright 2024 Google LLC
#
# 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
#
# http://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.
import os
import sys
import unittest
import array
from struct import pack
# Allow us to run even if not at the `tools` directory.
root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
sys.path.insert(0, root_dir)
from generate_pdcs import pebble_commands
circle_example_output = [2,
0,
pebble_commands.convert_color(0x00, 0x55, 0xFF, 0xFF),
1,
pebble_commands.convert_color(0xAA, 0xFF, 0x00, 0xFF),
0x2c, 0x01,
0x01, 0x00,
0xFA, 0xFF, 0x06, 0x00]
path_example_output = [1,
0,
pebble_commands.convert_color(0x00, 0x55, 0xFF, 0xFF),
1,
pebble_commands.convert_color(0xAA, 0xFF, 0x00, 0xFF),
1, 0,
0x02, 0x00,
0x01, 0x00, 0x06, 0x00, 0xFC, 0xFF, 0x02, 0x00]
class MyTestCase(unittest.TestCase):
def assertPointsEqual(self, p1, p2):
self.assertTrue(pebble_commands.compare_points(p1, p2), "({}, {}) != ({}, {})".format(p1[0],
p1[1], p2[0], p2[1]))
def test_convert_to_pebble_coordinates(self):
p, valid = pebble_commands.convert_to_pebble_coordinates((0.1, -0.1))
self.assertPointsEqual(p, (0.0, -1.0))
self.assertFalse(valid)
p, valid = pebble_commands.convert_to_pebble_coordinates((0.9, -0.9))
self.assertPointsEqual(p, (0.0, -1.0))
self.assertFalse(valid)
p, valid = pebble_commands.convert_to_pebble_coordinates((1.0, -1.0))
self.assertTrue(valid)
self.assertPointsEqual(p, (1.0, -1.0))
p, valid = pebble_commands.convert_to_pebble_coordinates((0.5, 0.5))
self.assertTrue(valid)
self.assertPointsEqual(p, (0.0, 0.0))
p, valid = pebble_commands.convert_to_pebble_coordinates((1.0, 0.5))
self.assertTrue(valid)
self.assertPointsEqual(p, (1.0, 0.0))
p, valid = pebble_commands.convert_to_pebble_coordinates((1.0, 0.5))
self.assertTrue(valid)
self.assertPointsEqual(p, (1.0, 0.0))
def test_convert_to_pebble_precise_coordinates(self):
p, valid = pebble_commands.convert_to_pebble_coordinates((0.1, -0.1), precise=True)
self.assertPointsEqual(p, (-3.0, -5.0))
self.assertFalse(valid)
p, valid = pebble_commands.convert_to_pebble_coordinates((0.9, -0.9), precise=True)
self.assertPointsEqual(p, (3.0, -11.0))
self.assertFalse(valid)
p, valid = pebble_commands.convert_to_pebble_coordinates((1.0, -1.0), precise=True)
self.assertTrue(valid)
self.assertPointsEqual(p, (4.0, -12.0))
p, valid = pebble_commands.convert_to_pebble_coordinates((0.5, 0.5), precise=True)
self.assertTrue(valid)
self.assertPointsEqual(p, (0.0, 0.0))
p, valid = pebble_commands.convert_to_pebble_coordinates((1.0, 0.5), precise=True)
self.assertTrue(valid)
self.assertPointsEqual(p, (4.0, 0.0))
p, valid = pebble_commands.convert_to_pebble_coordinates((1.125, 0.75), precise=True)
self.assertTrue(valid)
self.assertPointsEqual(p, (5.0, 2.0))
p, valid = pebble_commands.convert_to_pebble_coordinates((0.25, -0.25), precise=True)
self.assertTrue(valid)
self.assertPointsEqual(p, (-2.0, -6.0))
def test_find_nearest_valid_point(self):
p = (-0.1, 0.1)
self.assertTrue(pebble_commands.compare_points(pebble_commands.find_nearest_valid_point(p),
(0.0, 0.0)))
p = (-0.3, 0.25)
self.assertTrue(pebble_commands.compare_points(pebble_commands.find_nearest_valid_point(p),
(-0.5, 0.5)))
p = (-0.3, -0.25)
self.assertTrue(pebble_commands.compare_points(pebble_commands.find_nearest_valid_point(p),
(-0.5, -0.5)))
def test_find_nearest_valid_precise_point(self):
p = (-0.1, 0.1)
self.assertTrue(pebble_commands.compare_points(pebble_commands.find_nearest_valid_precise_point(p),
(-0.125, 0.125)))
p = (-0.3, 0.25)
self.assertTrue(pebble_commands.compare_points(pebble_commands.find_nearest_valid_precise_point(p),
(-0.25, 0.25)))
p = (1.05, -1.0625)
self.assertTrue(pebble_commands.compare_points(pebble_commands.find_nearest_valid_precise_point(p),
(1.0, -1.125)))
def test_path_serialize(self):
points = [(1.5, 6.5), (-3.5, 2.5)]
path = pebble_commands.PathCommand(
points,
True,
(0, 0),
1,
pebble_commands.convert_color(0x00, 0x55, 0xFF, 0xFF),
pebble_commands.convert_color(0xAA, 0xFF, 0x00, 0xFF))
result = path.serialize()
expected = path_example_output[:]
self.assertEqual(result, array.array('B', expected).tostring())
points = [(1.5, 6.5), (-3.5, 2.5)]
path = pebble_commands.PathCommand(
points,
False,
(0, 0),
1,
pebble_commands.convert_color(0x00, 0x55, 0xFF, 0xFF),
pebble_commands.convert_color(0xAA, 0xFF, 0x00, 0xFF))
expected[5] = 0
result = path.serialize()
self.assertEqual(result, array.array('B', expected).tostring())
def test_circle_serialize(self):
center = (-5.5, 6.5)
circle = pebble_commands.CircleCommand(
center,
300,
(0, 0),
1,
pebble_commands.convert_color(0x00, 0x55, 0xFF, 0xFF),
pebble_commands.convert_color(0xAA, 0xFF, 0x00, 0xFF))
result = circle.serialize()
expected = circle_example_output
self.assertEqual(result, array.array('B', expected).tostring())
def test_serialize_image(self):
points = [(1.5, 6.5), (-3.5, 2.5)]
path = pebble_commands.PathCommand(
points,
True,
(0, 0),
1,
pebble_commands.convert_color(0x00, 0x55, 0xFF, 0xFF),
pebble_commands.convert_color(0xAA, 0xFF, 0x00, 0xFF))
center = (-5.5, 6.5)
circle = pebble_commands.CircleCommand(
center,
300,
(0, 0),
1,
pebble_commands.convert_color(0x00, 0x55, 0xFF, 0xFF),
pebble_commands.convert_color(0xAA, 0xFF, 0x00, 0xFF))
image = pebble_commands.serialize_image([path, circle], (10, 400))
expected = [1,
0,
0x0A, 0x00, 0x90, 0x01,
0x02, 0x00]
expected = array.array('B', expected + path_example_output + circle_example_output).tostring()
image_expected = 'PDCI' + pack('I', len(expected)) + expected
self.assertEqual(image, image_expected)
def test_serialize_sequence(self):
points = [(1.5, 6.5), (-3.5, 2.5)]
path1 = pebble_commands.PathCommand(
points,
True,
(0, 0),
1,
pebble_commands.convert_color(0x00, 0x55, 0xFF, 0xFF),
pebble_commands.convert_color(0xAA, 0xFF, 0x00, 0xFF))
center = (-5.5, 6.5)
circle1 = pebble_commands.CircleCommand(
center,
300,
(0, 0),
1,
pebble_commands.convert_color(0x00, 0x55, 0xFF, 0xFF),
pebble_commands.convert_color(0xAA, 0xFF, 0x00, 0xFF))
frames = []
frames.append([path1, circle1])
points = [(1.5, 6.5), (-3.5, 2.5)]
path2 = pebble_commands.PathCommand(
points,
False,
(0, 0),
1,
pebble_commands.convert_color(0x00, 0x00, 0xFF, 0xFF),
pebble_commands.convert_color(0xAA, 0xFF, 0x55, 0xFF))
center = (-5.5, 6.5)
circle2 = pebble_commands.CircleCommand(
center,
280,
(0, 0),
1,
pebble_commands.convert_color(0x00, 0xAA, 0xFF, 0xFF),
pebble_commands.convert_color(0x55, 0xFF, 0x00, 0xFF))
frames.append([path2, circle2])
seq = pebble_commands.serialize_sequence(frames, (10, 400), 33, 5)
expected = [1,
0,
0x0A, 0x00, 0x90, 0x01,
0x05, 0x00,
0x02, 0x00,
0x21, 0x00,
0x02, 0x00]
expected += path_example_output + circle_example_output
path_ex2 = path_example_output[:]
path_ex2[2] = pebble_commands.convert_color(0x00, 0x00, 0xFF, 0xFF)
path_ex2[4] = pebble_commands.convert_color(0xAA, 0xFF, 0x55, 0xFF)
path_ex2[5] = 0
circle_ex2 = circle_example_output
circle_ex2[2] = pebble_commands.convert_color(0x00, 0xAA, 0xFF, 0xFF)
circle_ex2[4] = pebble_commands.convert_color(0x55, 0xFF, 0x00, 0xFF)
circle_ex2[5] = 0x18
expected += [0x21, 0x00,
0x02, 0x00]
expected += path_ex2 + circle_ex2
seq_expected = 'PDCS' + pack('I', len(expected)) + array.array('B', expected).tostring()
self.assertEqual(seq, seq_expected)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,305 @@
# Copyright 2024 Google LLC
#
# 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
#
# http://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.
import os
import sys
import unittest
import xml.etree.ElementTree as ET
import array
from struct import pack
# Allow us to run even if not at the `tools` directory.
root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
sys.path.insert(0, root_dir)
from generate_pdcs import svg2commands, pebble_commands
svg_header = '<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" ' \
'xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 144 168" ' \
'enable-background="new 0 0 144 168" xml:space="preserve">'
def create_root(s):
return ET.fromstring(svg_header + s + '</svg>')
def create_element(s):
return create_root(s).getchildren()[0]
class MyTestCase(unittest.TestCase):
def test_parse_path(self):
# test basic vertical line path
path_element = ET.fromstring('<path d="M-1.5,2.5v2"/>')
command = svg2commands.parse_path(path_element, (0, 0), 0, 0, 0, verbose=False,
precise=False, raise_error=False)
self.assertIsInstance(command, pebble_commands.PathCommand)
self.assertEqual(len(command.points), 2)
self.assertTrue(pebble_commands.compare_points(command.points[0], (-2.0, 2.0)), str(command.points[0]))
self.assertTrue(pebble_commands.compare_points(command.points[1], (-2.0, 4.0)), str(command.points[1]))
self.assertTrue(command.open)
# test basic multi-line open path described as a sequence of points
path_element = ET.fromstring('<path d="M -1.5,2.5 -3.5,6.5 4.5,6.5"/>')
command = svg2commands.parse_path(path_element, (0, 0), 0, 0, 0, verbose=False,
precise=False, raise_error=False)
self.assertIsInstance(command, pebble_commands.PathCommand)
self.assertEqual(len(command.points), 3)
self.assertTrue(pebble_commands.compare_points(command.points[0], (-2.0, 2.0)), str(command.points[0]))
self.assertTrue(pebble_commands.compare_points(command.points[1], (-4.0, 6.0)), str(command.points[1]))
self.assertTrue(pebble_commands.compare_points(command.points[2], (4.0, 6.0)), str(command.points[2]))
self.assertTrue(command.open)
# test basic multi-line closed path described as a sequence of points
path_element = ET.fromstring('<path d="M -1.5,2.5 -3.5,6.5 4.5,6.5 z"/>')
command = svg2commands.parse_path(path_element, (0, 0), 0, 0, 0, verbose=False,
precise=False, raise_error=False)
self.assertIsInstance(command, pebble_commands.PathCommand)
self.assertEqual(len(command.points), 3)
self.assertTrue(pebble_commands.compare_points(command.points[0], (-2.0, 2.0)), str(command.points[0]))
self.assertTrue(pebble_commands.compare_points(command.points[1], (-4.0, 6.0)), str(command.points[1]))
self.assertTrue(pebble_commands.compare_points(command.points[2], (4.0, 6.0)), str(command.points[2]))
self.assertFalse(command.open)
def test_parse_circle(self):
circle_element = ET.fromstring('<circle cx="72.5" cy="84.5" r="12"/>')
command = svg2commands.parse_circle(circle_element, (0, 0), 0, 0, 0, verbose=False,
precise=False, raise_error=False)
self.assertIsInstance(command, pebble_commands.CircleCommand)
self.assertEqual(len(command.points), 1)
self.assertTrue(pebble_commands.compare_points(command.points[0], (72.0, 84.0)), str(command.points[0]))
self.assertEqual(command.radius, 12.0)
def test_parse_polyline(self):
# test polyline element parsing
polyline_element = ET.fromstring('<polyline points="34.5,23.5 26.5,6.5 110.5,6.5 118.5,23.5 "/>')
command = svg2commands.parse_polyline(polyline_element, (0, 0), 0, 0, 0, verbose=False,
precise=False, raise_error=False)
self.assertIsInstance(command, pebble_commands.PathCommand)
self.assertEqual(len(command.points), 4)
self.assertTrue(pebble_commands.compare_points(command.points[0], (34.0, 23.0)), str(command.points[0]))
self.assertTrue(pebble_commands.compare_points(command.points[1], (26.0, 6.0)), str(command.points[1]))
self.assertTrue(pebble_commands.compare_points(command.points[2], (110.0, 6.0)), str(command.points[2]))
self.assertTrue(pebble_commands.compare_points(command.points[3], (118.0, 23.0)), str(command.points[3]))
self.assertTrue(command.open)
def test_parse_polygon(self):
# test polygon (closed path) element parsing
polygon_element = ET.fromstring('<polygon points="34.5,23.5 26.5,6.5 110.5,6.5 118.5,23.5 "/>')
command = svg2commands.parse_polygon(polygon_element, (0, 0), 0, 0, 0, verbose=False,
precise=False, raise_error=False)
self.assertIsInstance(command, pebble_commands.PathCommand)
self.assertEqual(len(command.points), 4)
self.assertTrue(pebble_commands.compare_points(command.points[0], (34.0, 23.0)), str(command.points[0]))
self.assertTrue(pebble_commands.compare_points(command.points[1], (26.0, 6.0)), str(command.points[1]))
self.assertTrue(pebble_commands.compare_points(command.points[2], (110.0, 6.0)), str(command.points[2]))
self.assertTrue(pebble_commands.compare_points(command.points[3], (118.0, 23.0)), str(command.points[3]))
self.assertFalse(command.open)
def test_parse_line(self):
# test line element parsing
line_element = ET.fromstring('<line x1="26.5" y1="139.5" x2="118.5" y2="139.5"/>')
command = svg2commands.parse_line(line_element, (0, 0), 0, 0, 0, verbose=False,
precise=False, raise_error=False)
self.assertIsInstance(command, pebble_commands.PathCommand)
self.assertEqual(len(command.points), 2)
self.assertTrue(pebble_commands.compare_points(command.points[0], (26.0, 139.0)), str(command.points[0]))
self.assertTrue(pebble_commands.compare_points(command.points[1], (118.0, 139.0)), str(command.points[1]))
self.assertTrue(command.open)
def test_parse_rect(self):
# test rect element parsing (converts to a closed path)
rect_element = ET.fromstring('<rect x="-1.5" y="2.5" width="4" height="5"/>')
command = svg2commands.parse_rect(rect_element, (0, 0), 0, 0, 0, verbose=False,
precise=False, raise_error=False)
self.assertIsInstance(command, pebble_commands.PathCommand)
self.assertEqual(len(command.points), 4)
self.assertTrue(pebble_commands.compare_points(command.points[0], (-2.0, 2.0)), str(command.points[0]))
self.assertTrue(pebble_commands.compare_points(command.points[1], (2.0, 2.0)), str(command.points[1]))
self.assertTrue(pebble_commands.compare_points(command.points[2], (2.0, 7.0)), str(command.points[2]))
self.assertTrue(pebble_commands.compare_points(command.points[3], (-2.0, 7.0)), str(command.points[3]))
self.assertFalse(command.open)
def test_ignore_display_none(self):
rect_element = create_element('<g><rect x="-1.5" y="2.5" width="4" stroke="#0055FF" stroke-width="3" height="5"/></g>')
commands, _ = svg2commands.get_commands((0, 0), rect_element, precise=True)
self.assertEqual(len(commands), 1)
rect_element_none = create_element('<g><rect x="-1.5" y="2.5" width="4" stroke="#0055FF" stroke-width="3" height="5" display="none"/></g>')
commands, _ = svg2commands.get_commands((0, 0), rect_element_none, precise=True)
self.assertEqual(len(commands), 0)
def test_create_command(self):
element = create_element('<polyline fill="#AAFF00" stroke="#0055FF" stroke-width="3" '
'points="34.5,23.5 26.5,6.5 110.5,6.5"/>')
# test that PathCommand is created from element correctly
command = svg2commands.create_command((0, 0), element)
self.assertIsInstance(command, pebble_commands.PathCommand)
self.assertEqual(len(command.points), 3)
self.assertEqual(command.fill_color, pebble_commands.convert_color(0xAA, 0xFF, 0x00, 0xFF))
self.assertEqual(command.stroke_color, pebble_commands.convert_color(0x00, 0x55, 0xFF, 0xFF))
self.assertEqual(command.stroke_width, 3)
self.assertTrue(pebble_commands.compare_points(command.points[0], (34.0, 23.0)), str(command.points[0]))
self.assertTrue(pebble_commands.compare_points(command.points[1], (26.0, 6.0)), str(command.points[1]))
self.assertTrue(pebble_commands.compare_points(command.points[2], (110.0, 6.0)), str(command.points[2]))
self.assertTrue(command.open)
# element should not be created if no color is specified (everything is transparent)
element = create_element('<polyline stroke-width="3" points="34.5,23.5 26.5,6.5 110.5,6.5"/>')
command = svg2commands.create_command((0, 0), element)
self.assertTrue(command is None)
# element with no stroke width is created
element = create_element('<polyline fill="#AAFF00" stroke-width="3" points="34.5,23.5 26.5,6.5 110.5,6.5"/>')
command = svg2commands.create_command((0, 0), element)
self.assertEqual(command.stroke_color, 0)
self.assertEqual(command.stroke_width, 0)
self.assertEqual(command.fill_color, pebble_commands.convert_color(0xAA, 0xFF, 0x00, 0xFF))
# element with no fill is created
element = create_element('<polyline stroke="#0055FF" stroke-width="3" points="34.5,23.5 26.5,6.5 110.5,6.5"/>')
command = svg2commands.create_command((0, 0), element)
self.assertEqual(command.stroke_color, pebble_commands.convert_color(0x00, 0x55, 0xFF, 0xFF))
self.assertEqual(command.stroke_width, 3)
self.assertEqual(command.fill_color, 0)
# test that opacity is assigned correctly (use opacity = 0.34 to ensure truncation to (255 / 3))
element = create_element('<polyline fill-opacity="0.34" stroke-opacity="0.5" fill="#AAFF00" stroke="#0055FF" '
'stroke-width="3" points="34.5,23.5 26.5,6.5 110.5,6.5"/>')
command = svg2commands.create_command((0, 0), element)
self.assertIsInstance(command, pebble_commands.PathCommand)
self.assertEqual(command.fill_color, pebble_commands.convert_color(0xAA, 0xFF, 0x00, 0x55))
self.assertEqual(command.stroke_color, pebble_commands.convert_color(0x00, 0x55, 0xFF, 0x55))
self.assertEqual(command.stroke_width, 3)
# test that opacity is compounded when 'opacity' tag is included
element = create_element('<polyline opacity="0.34" fill-opacity="0.67" stroke-opacity="1.0" fill="#AAFF00" '
'stroke="#0055FF" stroke-width="3" points="34.5,23.5 26.5,6.5 110.5,6.5"/>')
command = svg2commands.create_command((0, 0), element)
self.assertIsInstance(command, pebble_commands.PathCommand)
self.assertEqual(command.fill_color, 0)
self.assertEqual(command.stroke_color, pebble_commands.convert_color(0x00, 0x55, 0xFF, 0x55))
self.assertEqual(command.stroke_width, 3)
# stroke color should be set to clear when width is 0
element = create_element('<polyline fill="#AAFF00" stroke="#0055FF" stroke-width="0" '
'points="34.5,23.5 26.5,6.5 110.5,6.5"/>')
command = svg2commands.create_command((0, 0), element)
self.assertIsInstance(command, pebble_commands.PathCommand)
self.assertEqual(command.fill_color, pebble_commands.convert_color(0xAA, 0xFF, 0x00, 0xFF))
self.assertEqual(command.stroke_color, 0)
self.assertEqual(command.stroke_width, 0)
# test that all points are shifted when a translation is specified
element = create_element('<polyline fill="#AAFF00" stroke="#0055FF" stroke-width="3" '
'points="34.5,23.5 26.5,6.5 110.5,6.5"/>')
translate = (1, -2)
command = svg2commands.create_command(translate, element)
self.assertIsInstance(command, pebble_commands.PathCommand)
self.assertEqual(len(command.points), 3)
self.assertTrue(pebble_commands.compare_points(command.points[0], (35.0, 21.0)), str(command.points[0]))
self.assertTrue(pebble_commands.compare_points(command.points[1], (27.0, 4.0)), str(command.points[1]))
self.assertTrue(pebble_commands.compare_points(command.points[2], (111.0, 4.0)), str(command.points[2]))
self.assertTrue(command.open)
# test element other than polyline
element = create_element('<line fill="none" stroke="#0000FF" stroke-width="5" x1="26.5" y1="139.5" x2="118.5" '
'y2="139.5"/>')
command = svg2commands.create_command((0, 0), element)
self.assertIsInstance(command, pebble_commands.PathCommand)
self.assertEqual(command.fill_color, 0)
self.assertEqual(command.stroke_color, pebble_commands.convert_color(0x00, 0x00, 0xFF, 0xFF))
self.assertEqual(command.stroke_width, 5)
self.assertEqual(len(command.points), 2)
self.assertTrue(pebble_commands.compare_points(command.points[0], (26.0, 139.0)), str(command.points[0]))
self.assertTrue(pebble_commands.compare_points(command.points[1], (118.0, 139.0)), str(command.points[1]))
# elements at x.0 should stay at x.0
element = create_element('<line fill="none" stroke="#0000FF" stroke-width="5" x1="26.0" y1="139.5" x2="118.5" '
'y2="139.0"/>')
command = svg2commands.create_command((0, 0), element)
self.assertIsInstance(command, pebble_commands.PathCommand)
self.assertEqual(command.fill_color, 0)
self.assertEqual(command.stroke_color, pebble_commands.convert_color(0x00, 0x00, 0xFF, 0xFF))
self.assertEqual(command.stroke_width, 5)
self.assertEqual(len(command.points), 2)
self.assertTrue(pebble_commands.compare_points(command.points[0], (26.0, 139.0)), str(command.points[0]))
self.assertTrue(pebble_commands.compare_points(command.points[1], (118.0, 139.0)), str(command.points[1]))
# test elements that do not describe a path or circle are handled
element = create_element('<layer bla="5"/>')
command = svg2commands.create_command((0, 0), element)
self.assertTrue(command is None)
def test_parse_svg(self):
root = create_root('<line fill="none" stroke="#0000FF" stroke-width="5" x1="1.5" y1="5.5" x2="-6.5" y2="-1.5"/>'
'<polyline fill="#AAFF00" stroke="#0055FF" stroke-width="3" points="-3.5,4.5 9.5,6.5 '
'7.5,6.5"/>')
translate = (-1, 2)
commands, _ = svg2commands.get_commands(translate, root, precise=False)
self.assertEqual(len(commands[0].points), 2)
self.assertTrue(pebble_commands.compare_points(commands[0].points[0], (0.0, 7.0)), str(commands[0].points[0]))
self.assertTrue(pebble_commands.compare_points(commands[0].points[1], (-8.0, 0.0)), str(commands[0].points[1]))
self.assertEqual(commands[0].fill_color, 0)
self.assertEqual(commands[0].stroke_color, pebble_commands.convert_color(0x00, 0x00, 0xFF, 0xFF))
self.assertEqual(commands[0].stroke_width, 5)
self.assertEqual(len(commands[1].points), 3)
self.assertTrue(pebble_commands.compare_points(commands[1].points[0], (-5.0, 6.0)), str(commands[1].points[0]))
self.assertTrue(pebble_commands.compare_points(commands[1].points[1], (8.0, 8.0)), str(commands[1].points[1]))
self.assertTrue(pebble_commands.compare_points(commands[1].points[2], (6.0, 8.0)), str(commands[1].points[2]))
self.assertEqual(commands[1].fill_color, pebble_commands.convert_color(0xAA, 0xFF, 0x00, 0xFF))
self.assertEqual(commands[1].stroke_color, pebble_commands.convert_color(0x00, 0x55, 0xFF, 0xFF))
self.assertEqual(commands[1].stroke_width, 3)
def test_get_info(self):
xml = ET.fromstring(svg_header + '</svg>')
translate, size = svg2commands.get_info(xml)
self.assertEqual(translate[0], 0)
self.assertEqual(translate[1], 0)
self.assertEqual(size[0], 144)
self.assertEqual(size[1], 168)
def test_parse_precise_path(self):
# test that sub-pixel precision is achieved by scaling up by a factor of 8 for precise paths
line_element = ET.fromstring('<line x1="1.5" y1="2.125" x2="4.875" y2="9"/>')
command = svg2commands.parse_line(line_element, (0, 0), 0, 0, 0, verbose=False,
precise=True, raise_error=True)
self.assertIsInstance(command, pebble_commands.PathCommand)
self.assertEqual(len(command.points), 2)
self.assertTrue(pebble_commands.compare_points(command.points[0], (8.0, 13.0)), str(command.points[0]))
self.assertTrue(pebble_commands.compare_points(command.points[1], (35.0, 68.0)), str(command.points[1]))
self.assertTrue(command.open)
self.assertEqual(command.type, pebble_commands.DRAW_COMMAND_TYPE_PRECISE_PATH)
def test_parse_with_error_raising(self):
# test that inaccurate points are rejected with raise_error == True (pebble_commands.InvalidPointException raised)
line_element = ET.fromstring('<line x1="1.5" y1="2.1" x2="4.6" y2="9"/>')
with self.assertRaises(svg2commands.pebble_commands.InvalidPointException):
svg2commands.parse_line(line_element, (0, 0), 0, 0, 0, verbose=False, precise=True,
raise_error=True)
# test that using precise == False results in points being rejected with raise_error == True
# (pebble_commands.InvalidPointException raised)
line_element = ET.fromstring('<line x1="1.5" y1="2.125" x2="4.875" y2="9"/>')
with self.assertRaises(svg2commands.pebble_commands.InvalidPointException):
svg2commands.parse_line(line_element, (0, 0), 0, 0, 0, verbose=False, precise=False,
raise_error=True)
if __name__ == '__main__':
unittest.main()

6
tools/tests/wscript Normal file
View file

@ -0,0 +1,6 @@
def build(bld):
tests = bld.path.ant_glob('test_*.py')
bld(rule="python -m unittest discover -s {} -p 'test_*.py'".format(bld.path.abspath()),
source=tests)
# vim:filetype=python