mirror of
https://github.com/eerimoq/moblin.git
synced 2026-07-04 15:06:48 +00:00
Test suite started.
This commit is contained in:
@@ -9,6 +9,12 @@ jobs:
|
||||
runs-on: macos-26
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.14'
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
pip install -r requirements.txt
|
||||
- name: Create user config
|
||||
run: |
|
||||
cp User.template.xcconfig Config/User.xcconfig
|
||||
|
||||
+2
-2
@@ -98,5 +98,5 @@ Config/User.xcconfig
|
||||
|
||||
# Node.js
|
||||
node_modules/
|
||||
utils/all.*
|
||||
utils/logs
|
||||
test/all.*
|
||||
test/logs
|
||||
@@ -7,6 +7,10 @@ SWIFTFORMAT_ARGS = \
|
||||
SWIFTLINT_ARGS = --strict --quiet
|
||||
OXFMT_ARGS = "WebRemoteControlFrontend"
|
||||
OXLINT_ARGS = "WebRemoteControlFrontend"
|
||||
PYTHON_DIRS = \
|
||||
test \
|
||||
utils
|
||||
BLACK_ARGS = $(PYTHON_DIRS)
|
||||
PERIPHERY_ARGS = \
|
||||
--index-exclude "Moblin/Integrations/Tesla/Protobuf/*" \
|
||||
--index-exclude "**/PrepareLicenseList/**" \
|
||||
@@ -21,8 +25,9 @@ PYLINT_ARGS = \
|
||||
--disable broad-exception-caught \
|
||||
--disable too-many-locals \
|
||||
--disable duplicate-code \
|
||||
--disable missing-class-docstring \
|
||||
--recursive yes \
|
||||
.
|
||||
$(PYTHON_DIRS)
|
||||
|
||||
CODE_FOLDERS += "Common"
|
||||
CODE_FOLDERS += "Moblin"
|
||||
@@ -35,24 +40,26 @@ CODE_FOLDERS += "WebRemoteControlFrontend"
|
||||
|
||||
SHELL = /usr/bin/env bash
|
||||
|
||||
.PHONY: test
|
||||
|
||||
default:
|
||||
|
||||
style:
|
||||
swiftformat $(CODE_FOLDERS) $(SWIFTFORMAT_ARGS)
|
||||
oxfmt $(OXFMT_ARGS)
|
||||
black $(BLACK_ARGS) || true
|
||||
|
||||
style-check:
|
||||
swiftformat $(CODE_FOLDERS) $(SWIFTFORMAT_ARGS) --lint
|
||||
oxfmt $(OXFMT_ARGS) --check
|
||||
black $(BLACK_ARGS) --check
|
||||
|
||||
lint:
|
||||
swiftlint lint $(SWIFTLINT_ARGS) $(CODE_FOLDERS)
|
||||
oxlint $(OXLINT_ARGS)
|
||||
pylint $(PYLINT_ARGS) || true
|
||||
python3 utils/xcstringslint.py Common/Localizable.xcstrings
|
||||
|
||||
pylint:
|
||||
pylint $(PYLINT_ARGS)
|
||||
|
||||
lint-fix:
|
||||
python3 utils/xcstringslint.py --fix Common/Localizable.xcstrings
|
||||
|
||||
@@ -63,7 +70,7 @@ spell-check:
|
||||
codespell $(CODESPELL_ARGS) $(CODE_FOLDERS)
|
||||
|
||||
test:
|
||||
cd utils && python test.py
|
||||
cd test && python test.py
|
||||
|
||||
machine-translate:
|
||||
python3 utils/translate.py Common/Localizable.xcstrings
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
systest
|
||||
black
|
||||
pylint
|
||||
deep_translator
|
||||
@@ -1,19 +1,28 @@
|
||||
import logging
|
||||
import systest
|
||||
import subprocess
|
||||
import time
|
||||
import systest
|
||||
|
||||
|
||||
REMOTE_CONTROL_PORT = '2345'
|
||||
REMOTE_CONTROL_PORT = "2345"
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Moblin:
|
||||
def __init__(self):
|
||||
self._server = None
|
||||
|
||||
def __enter__(self):
|
||||
self._server = subprocess.Popen(["moblin_assistant",
|
||||
"--port", REMOTE_CONTROL_PORT,
|
||||
"run",
|
||||
"--password", "1234"])
|
||||
self._server = subprocess.Popen(
|
||||
[
|
||||
"moblin_assistant",
|
||||
"--port",
|
||||
REMOTE_CONTROL_PORT,
|
||||
"run",
|
||||
"--password",
|
||||
"1234",
|
||||
]
|
||||
)
|
||||
time.sleep(1)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
@@ -23,10 +32,15 @@ class Moblin:
|
||||
self._execute("go_live")
|
||||
|
||||
def _execute(self, command):
|
||||
subprocess.run(["moblin_assistant", "--port", REMOTE_CONTROL_PORT, command])
|
||||
subprocess.run(
|
||||
["moblin_assistant", "--port", REMOTE_CONTROL_PORT, command], check=True
|
||||
)
|
||||
|
||||
|
||||
class MediaMtx:
|
||||
def __init__(self):
|
||||
self._server = None
|
||||
|
||||
def __enter__(self):
|
||||
self._server = subprocess.Popen(["mediamtx"])
|
||||
return self
|
||||
@@ -36,6 +50,9 @@ class MediaMtx:
|
||||
|
||||
|
||||
class Ffmpeg:
|
||||
def __init__(self):
|
||||
self._server = None
|
||||
|
||||
def __enter__(self):
|
||||
self._server = subprocess.Popen(["ffmpeg"])
|
||||
return self
|
||||
@@ -46,13 +63,12 @@ class Ffmpeg:
|
||||
|
||||
class RtmpFromMoblinToMediaMtx(systest.TestCase):
|
||||
def __init__(self, moblin: Moblin):
|
||||
super(RtmpFromMoblinToMediaMtx, self).__init__()
|
||||
super().__init__()
|
||||
self.moblin = moblin
|
||||
|
||||
def run(self):
|
||||
time.sleep(5)
|
||||
self.moblin.go_live()
|
||||
time.sleep(1)
|
||||
with MediaMtx():
|
||||
self.moblin.go_live()
|
||||
|
||||
|
||||
def main():
|
||||
@@ -64,4 +80,4 @@ def main():
|
||||
sequencer.report_and_exit()
|
||||
|
||||
|
||||
main()
|
||||
main()
|
||||
+29
-34
@@ -8,23 +8,23 @@ import argparse
|
||||
|
||||
def set_bitrate_and_loss(bitrate, loss):
|
||||
if bitrate is None and loss is None:
|
||||
print('Neither bitrate nor loss given. Aborting...')
|
||||
print("Neither bitrate nor loss given. Aborting...")
|
||||
return
|
||||
|
||||
print(time.ctime())
|
||||
args = ''
|
||||
args = ""
|
||||
|
||||
if bitrate is not None:
|
||||
print(f" - Bitrate {bitrate} Mbit")
|
||||
args += f' rate {bitrate}Mbit'
|
||||
args += f" rate {bitrate}Mbit"
|
||||
|
||||
if loss is not None:
|
||||
print(f" - Loss {loss} %")
|
||||
args += f' loss {loss}%'
|
||||
args += f" loss {loss}%"
|
||||
|
||||
subprocess.run('sudo tc qdisc replace dev eno1 root netem' + args,
|
||||
shell=True,
|
||||
check=True)
|
||||
subprocess.run(
|
||||
"sudo tc qdisc replace dev eno1 root netem" + args, shell=True, check=True
|
||||
)
|
||||
|
||||
|
||||
def do_constant(args):
|
||||
@@ -49,47 +49,42 @@ def do_random(args):
|
||||
|
||||
|
||||
def do_reset(_args):
|
||||
subprocess.run('sudo tc qdisc del dev eno1 root', shell=True, check=True)
|
||||
subprocess.run("sudo tc qdisc del dev eno1 root", shell=True, check=True)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
# Workaround to make the subparser required in Python 3.
|
||||
subparsers = parser.add_subparsers(title='subcommands',
|
||||
dest='subcommand')
|
||||
subparsers = parser.add_subparsers(title="subcommands", dest="subcommand")
|
||||
subparsers.required = True
|
||||
|
||||
subparser = subparsers.add_parser('reset')
|
||||
subparser = subparsers.add_parser("reset")
|
||||
subparser.set_defaults(func=do_reset)
|
||||
|
||||
subparser = subparsers.add_parser('constant')
|
||||
subparser.add_argument('--bitrate', type=float, help='Bitrate in Mbps.')
|
||||
subparser.add_argument('--loss', type=float, help='Loss in %%.')
|
||||
subparser = subparsers.add_parser("constant")
|
||||
subparser.add_argument("--bitrate", type=float, help="Bitrate in Mbps.")
|
||||
subparser.add_argument("--loss", type=float, help="Loss in %%.")
|
||||
subparser.set_defaults(func=do_constant)
|
||||
|
||||
subparser = subparsers.add_parser('square')
|
||||
subparser.add_argument('--low-bitrate',
|
||||
default=2,
|
||||
type=float,
|
||||
help='Low bitrate in Mbps.')
|
||||
subparser.add_argument('--high-bitrate',
|
||||
default=10,
|
||||
type=float,
|
||||
help='High bitrate in Mbps.')
|
||||
subparser.add_argument('--low-time',
|
||||
default=15,
|
||||
type=float,
|
||||
help='Low bitrate time in seconds.')
|
||||
subparser.add_argument('--high-time',
|
||||
default=15,
|
||||
type=float,
|
||||
help='High bitrate time in seconds.')
|
||||
subparser.add_argument('--loss', type=float, help='Loss in %%.')
|
||||
subparser = subparsers.add_parser("square")
|
||||
subparser.add_argument(
|
||||
"--low-bitrate", default=2, type=float, help="Low bitrate in Mbps."
|
||||
)
|
||||
subparser.add_argument(
|
||||
"--high-bitrate", default=10, type=float, help="High bitrate in Mbps."
|
||||
)
|
||||
subparser.add_argument(
|
||||
"--low-time", default=15, type=float, help="Low bitrate time in seconds."
|
||||
)
|
||||
subparser.add_argument(
|
||||
"--high-time", default=15, type=float, help="High bitrate time in seconds."
|
||||
)
|
||||
subparser.add_argument("--loss", type=float, help="Loss in %%.")
|
||||
subparser.set_defaults(func=do_square)
|
||||
|
||||
subparser = subparsers.add_parser('random')
|
||||
subparser.add_argument('--loss', type=float, help='Loss in %%.')
|
||||
subparser = subparsers.add_parser("random")
|
||||
subparser.add_argument("--loss", type=float, help="Loss in %%.")
|
||||
subparser.set_defaults(func=do_random)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
+6
-5
@@ -6,17 +6,18 @@ import argparse
|
||||
|
||||
def analyze(video):
|
||||
pts_time_lines = subprocess.run(
|
||||
f'ffprobe {video} -show_frames -hide_banner -loglevel warning -select_streams v:0 '
|
||||
'| grep pts_time',
|
||||
f"ffprobe {video} -show_frames -hide_banner -loglevel warning -select_streams v:0 "
|
||||
"| grep pts_time",
|
||||
text=True,
|
||||
shell=True,
|
||||
check=True,
|
||||
capture_output=True).stdout.splitlines()
|
||||
capture_output=True,
|
||||
).stdout.splitlines()
|
||||
|
||||
prev_pts_time = None
|
||||
|
||||
for pts_time_line in pts_time_lines:
|
||||
pts_time = float(pts_time_line.split('=')[1])
|
||||
pts_time = float(pts_time_line.split("=")[1])
|
||||
|
||||
if prev_pts_time is not None:
|
||||
delta_ms = 1000 * (pts_time - prev_pts_time)
|
||||
@@ -27,7 +28,7 @@ def analyze(video):
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('video', help='The video to analyze.')
|
||||
parser.add_argument("video", help="The video to analyze.")
|
||||
args = parser.parse_args()
|
||||
|
||||
analyze(args.video)
|
||||
|
||||
+19
-18
@@ -25,27 +25,27 @@ LANGUAGES = [
|
||||
("ko", "ko"),
|
||||
("ru", "ru"),
|
||||
("uk", "uk"),
|
||||
("sk", "sk")
|
||||
("sk", "sk"),
|
||||
]
|
||||
|
||||
|
||||
def needs_translation(item):
|
||||
state = item['stringUnit']['state']
|
||||
state = item["stringUnit"]["state"]
|
||||
|
||||
return state not in ['translated', 'needs_review']
|
||||
return state not in ["translated", "needs_review"]
|
||||
|
||||
|
||||
def main():
|
||||
localizable_xcstrings_path = Path(sys.argv[1])
|
||||
localizable = json.loads(localizable_xcstrings_path.read_text(encoding='utf-8'))
|
||||
localizable = json.loads(localizable_xcstrings_path.read_text(encoding="utf-8"))
|
||||
|
||||
try:
|
||||
for english, value in localizable['strings'].items():
|
||||
localizations = value.get('localizations')
|
||||
for english, value in localizable["strings"].items():
|
||||
localizations = value.get("localizations")
|
||||
|
||||
if localizations is None:
|
||||
localizations = {}
|
||||
value['localizations'] = localizations
|
||||
value["localizations"] = localizations
|
||||
|
||||
for xcode_languages, google_language in LANGUAGES:
|
||||
translated = None
|
||||
@@ -61,8 +61,12 @@ def main():
|
||||
continue
|
||||
|
||||
if translated is None:
|
||||
print(f'Translating "{english}" to {", ".join(xcode_languages)}')
|
||||
translator = GoogleTranslator(source='en', target=google_language)
|
||||
print(
|
||||
f'Translating "{english}" to {", ".join(xcode_languages)}'
|
||||
)
|
||||
translator = GoogleTranslator(
|
||||
source="en", target=google_language
|
||||
)
|
||||
|
||||
try:
|
||||
translated = translator.translate(english)
|
||||
@@ -70,18 +74,15 @@ def main():
|
||||
translated = english
|
||||
|
||||
localizations[xcode_language] = {
|
||||
'stringUnit': {
|
||||
'state': 'needs_review',
|
||||
'value': translated
|
||||
}
|
||||
"stringUnit": {"state": "needs_review", "value": translated}
|
||||
}
|
||||
finally:
|
||||
localizable_xcstrings_path.write_text(
|
||||
json.dumps(localizable,
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
separators=(',', ' : ')),
|
||||
encoding='utf-8')
|
||||
json.dumps(
|
||||
localizable, indent=2, ensure_ascii=False, separators=(",", " : ")
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
main()
|
||||
|
||||
+33
-31
@@ -7,7 +7,7 @@ from pathlib import Path
|
||||
|
||||
# Matches iOS/Swift format specifiers with an optional positional prefix.
|
||||
# Handles: %@, %lld, %llu, %d, %f, %u and positional forms like %1$@, %2$lld.
|
||||
SPECIFIER_RE = re.compile(r'%(\d+\$)?(@|lld|llu|[dfu])')
|
||||
SPECIFIER_RE = re.compile(r"%(\d+\$)?(@|lld|llu|[dfu])")
|
||||
|
||||
|
||||
def extract_specifiers(s):
|
||||
@@ -24,26 +24,26 @@ def check_format_specifiers(string_in_code, localized_string, language_code):
|
||||
|
||||
if code_types != loc_types:
|
||||
errors.append(
|
||||
f' [{language_code}] Format specifier mismatch: '
|
||||
f'code={[t for _, t in code_specs]}, '
|
||||
f'localized={[t for _, t in loc_specs]}'
|
||||
f" [{language_code}] Format specifier mismatch: "
|
||||
f"code={[t for _, t in code_specs]}, "
|
||||
f"localized={[t for _, t in loc_specs]}"
|
||||
)
|
||||
elif len(code_specs) > 1:
|
||||
if any(pos is None for pos, _ in loc_specs):
|
||||
errors.append(
|
||||
f' [{language_code}] Missing positional prefixes in: '
|
||||
f'{localized_string}'
|
||||
f" [{language_code}] Missing positional prefixes in: "
|
||||
f"{localized_string}"
|
||||
)
|
||||
else:
|
||||
n = len(code_specs)
|
||||
positions = [int(pos.rstrip('$')) for pos, _ in loc_specs]
|
||||
positions = [int(pos.rstrip("$")) for pos, _ in loc_specs]
|
||||
expected = set(range(1, n + 1))
|
||||
actual = set(positions)
|
||||
if actual != expected:
|
||||
errors.append(
|
||||
f' [{language_code}] Invalid positional prefix numbers '
|
||||
f'(expected 1..{n}, got {sorted(actual)}): '
|
||||
f'{localized_string}'
|
||||
f" [{language_code}] Invalid positional prefix numbers "
|
||||
f"(expected 1..{n}, got {sorted(actual)}): "
|
||||
f"{localized_string}"
|
||||
)
|
||||
|
||||
return errors
|
||||
@@ -68,29 +68,31 @@ def fix_localized_string(string_in_code, localized_string):
|
||||
idx = itertools.count(1)
|
||||
|
||||
def replacer(m):
|
||||
return f'%{next(idx)}${m.group(2)}'
|
||||
return f"%{next(idx)}${m.group(2)}"
|
||||
|
||||
return SPECIFIER_RE.sub(replacer, localized_string)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Lint format specifiers in xcstrings localization files.'
|
||||
description="Lint format specifiers in xcstrings localization files."
|
||||
)
|
||||
parser.add_argument("xcstrings_path", help="Path to the .xcstrings file")
|
||||
parser.add_argument(
|
||||
"--fix",
|
||||
action="store_true",
|
||||
help="Fix found problems and write the result back to the file",
|
||||
)
|
||||
parser.add_argument('xcstrings_path', help='Path to the .xcstrings file')
|
||||
parser.add_argument('--fix',
|
||||
action='store_true',
|
||||
help='Fix found problems and write the result back to the file')
|
||||
args = parser.parse_args()
|
||||
|
||||
xcstrings_path = Path(args.xcstrings_path)
|
||||
localizable = json.loads(xcstrings_path.read_text(encoding='utf-8'))
|
||||
localizable = json.loads(xcstrings_path.read_text(encoding="utf-8"))
|
||||
|
||||
errors_found = False
|
||||
modified = False
|
||||
|
||||
for string_in_code, value in localizable['strings'].items():
|
||||
localizations = value.get('localizations')
|
||||
for string_in_code, value in localizable["strings"].items():
|
||||
localizations = value.get("localizations")
|
||||
|
||||
if not localizations:
|
||||
continue
|
||||
@@ -98,15 +100,15 @@ def main():
|
||||
string_errors = []
|
||||
|
||||
for language_code, localization_value in localizations.items():
|
||||
string_unit = localization_value.get('stringUnit')
|
||||
string_unit = localization_value.get("stringUnit")
|
||||
|
||||
if not string_unit:
|
||||
continue
|
||||
|
||||
localized_string = string_unit.get('value', '')
|
||||
errors = check_format_specifiers(string_in_code,
|
||||
localized_string,
|
||||
language_code)
|
||||
localized_string = string_unit.get("value", "")
|
||||
errors = check_format_specifiers(
|
||||
string_in_code, localized_string, language_code
|
||||
)
|
||||
|
||||
if errors:
|
||||
string_errors.extend(errors)
|
||||
@@ -115,23 +117,23 @@ def main():
|
||||
fixed = fix_localized_string(string_in_code, localized_string)
|
||||
|
||||
if fixed is not None and fixed != localized_string:
|
||||
string_unit['value'] = fixed
|
||||
string_unit["value"] = fixed
|
||||
modified = True
|
||||
|
||||
if string_errors:
|
||||
errors_found = True
|
||||
print(f'Error in {repr(string_in_code)}:')
|
||||
print(f"Error in {repr(string_in_code)}:")
|
||||
|
||||
for error in string_errors:
|
||||
print(error)
|
||||
|
||||
if args.fix and modified:
|
||||
xcstrings_path.write_text(
|
||||
json.dumps(localizable,
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
separators=(',', ' : ')),
|
||||
encoding='utf-8')
|
||||
json.dumps(
|
||||
localizable, indent=2, ensure_ascii=False, separators=(",", " : ")
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
if errors_found and not args.fix:
|
||||
sys.exit(1)
|
||||
|
||||
+8
-9
@@ -1,29 +1,28 @@
|
||||
import argparse
|
||||
from xml.etree import ElementTree
|
||||
|
||||
NS = {
|
||||
'': "urn:oasis:names:tc:xliff:document:1.2"
|
||||
}
|
||||
NS = {"": "urn:oasis:names:tc:xliff:document:1.2"}
|
||||
|
||||
ElementTree.register_namespace("", "urn:oasis:names:tc:xliff:document:1.2")
|
||||
|
||||
ElementTree.register_namespace('', "urn:oasis:names:tc:xliff:document:1.2")
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('xliff')
|
||||
parser.add_argument("xliff")
|
||||
args = parser.parse_args()
|
||||
|
||||
tree = ElementTree.parse(args.xliff)
|
||||
|
||||
for body in tree.findall('./file/body', namespaces=NS):
|
||||
for body in tree.findall("./file/body", namespaces=NS):
|
||||
sorted_trans_units = []
|
||||
|
||||
for trans_unit in body.findall('./trans-unit', namespaces=NS):
|
||||
for trans_unit in body.findall("./trans-unit", namespaces=NS):
|
||||
target = trans_unit.find("./target", namespaces=NS)
|
||||
|
||||
if target is None:
|
||||
sorted_trans_units.insert(0, trans_unit)
|
||||
else:
|
||||
if target.attrib.get('state') == 'translated':
|
||||
if target.attrib.get("state") == "translated":
|
||||
sorted_trans_units.append(trans_unit)
|
||||
else:
|
||||
sorted_trans_units.insert(0, trans_unit)
|
||||
@@ -32,7 +31,7 @@ def main():
|
||||
body.extend(sorted_trans_units)
|
||||
|
||||
ElementTree.indent(tree)
|
||||
tree.write(args.xliff, xml_declaration=True, encoding='utf-8')
|
||||
tree.write(args.xliff, xml_declaration=True, encoding="utf-8")
|
||||
|
||||
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user