mirror of
https://github.com/google/pebble.git
synced 2025-06-03 08:43:12 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
BIN
tools/tests/app_resources_v2.pbpack
Normal file
BIN
tools/tests/app_resources_v2.pbpack
Normal file
Binary file not shown.
127
tools/tests/json2commands_test.json
Normal file
127
tools/tests/json2commands_test.json
Normal 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]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
217
tools/tests/json2vibe_test.json
Normal file
217
tools/tests/json2vibe_test.json
Normal 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
|
||||
}
|
||||
}
|
110
tools/tests/test_app_header.py
Normal file
110
tools/tests/test_app_header.py
Normal 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()
|
145
tools/tests/test_check_elf_log_strings.py
Normal file
145
tools/tests/test_check_elf_log_strings.py
Normal 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()
|
143
tools/tests/test_deploy_pbz_to_pebblefw.py
Normal file
143
tools/tests/test_deploy_pbz_to_pebblefw.py
Normal 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
83
tools/tests/test_hdlc.py
Normal 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()
|
506
tools/tests/test_json2commands.py
Normal file
506
tools/tests/test_json2commands.py
Normal 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()
|
224
tools/tests/test_json2vibe.py
Normal file
224
tools/tests/test_json2vibe.py
Normal 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
155
tools/tests/test_pbpack.py
Normal 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()
|
261
tools/tests/test_pebble_commands.py
Normal file
261
tools/tests/test_pebble_commands.py
Normal 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()
|
305
tools/tests/test_svg2commands.py
Normal file
305
tools/tests/test_svg2commands.py
Normal 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
6
tools/tests/wscript
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue