wwvbpy/test/testcli.py

305 lines
10 KiB
Python

#!/usr/bin/python3
"""Test most wwvblib commandline programs"""
# ruff: noqa: N802 D102
# SPDX-FileCopyrightText: 2021-2024 Jeff Epler
#
# SPDX-License-Identifier: GPL-3.0-only
from __future__ import annotations
import json
import os
import subprocess
import sys
import unittest
# These imports must remain, even though the module contents are not used directly!
import wwvb.dut1table
import wwvb.gen
# The asserts below are to help prevent their removal by a linter.
assert wwvb.dut1table.__name__ == "wwvb.dut1table"
assert wwvb.gen.__name__ == "wwvb.gen"
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Sequence
from wwvb import JsonMinute
class CLITestCase(unittest.TestCase):
"""Test various CLI commands within wwvbpy"""
def programOutput(self, *args: str) -> str:
env = os.environ.copy()
env["PYTHONIOENCODING"] = "utf-8"
return subprocess.check_output(args, stdin=subprocess.DEVNULL, encoding="utf-8", env=env)
def moduleArgs(self, *args: str) -> Sequence[str]:
return (sys.executable, "-m", *args)
def moduleOutput(self, *args: str) -> str:
return self.programOutput(sys.executable, "-m", *args)
def assertProgramOutput(self, expected: str, *args: str) -> None:
"""Check the output from invoking a program matches the expected"""
actual = self.programOutput(*args)
self.assertMultiLineEqual(expected, actual, f"args={args}")
def assertProgramOutputStarts(self, expected: str, *args: str) -> None:
"""Check the output from invoking a program matches the expected"""
actual = self.programOutput(*args)
self.assertMultiLineEqual(expected, actual[: len(expected)], f"args={args}")
def assertModuleOutput(self, expected: str, *args: str) -> None:
"""Check the output from invoking a `python -m modulename` program matches the expected"""
actual = self.moduleOutput(*args)
self.assertMultiLineEqual(expected, actual, f"args={args}")
def assertStarts(self, expected: str, actual: str, *args: str) -> None:
self.assertMultiLineEqual(expected, actual[: len(expected)], f"args={args}")
def assertModuleJson(self, expected: list[JsonMinute], *args: str) -> None:
"""Check the output from invoking a `python -m modulename` program matches the expected"""
actual = self.moduleOutput(*args)
# Note: in mypy, revealed type of json.loads is typing.Any!
self.assertEqual(json.loads(actual), expected)
def assertModuleOutputStarts(self, expected: str, *args: str) -> None:
"""Check the output from invoking a `python -m modulename` program matches the expected"""
actual = self.moduleOutput(*args)
self.assertStarts(expected, actual, *args)
def assertProgramError(self, *args: str) -> None:
"""Check the output from invoking a program fails"""
env = os.environ.copy()
env["PYTHONIOENCODING"] = "utf-8"
with self.assertRaises(subprocess.SubprocessError):
subprocess.check_output(
args,
stdin=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
encoding="utf-8",
env=env,
)
def assertModuleError(self, *args: str) -> None:
"""Check the output from invoking a `python -m modulename` program fails"""
self.assertProgramError(*self.moduleArgs(*args))
def test_gen(self) -> None:
"""Test wwvb.gen"""
self.assertModuleOutput(
"""\
WWVB timecode: year=2020 days=001 hour=12 min=30 dst=0 ut1=-200 ly=1 ls=0
2020-001 12:30 201100000200010001020000000002000100010200100001020000010002
""",
"wwvb.gen",
"-m",
"1",
"2020-1-1 12:30",
)
self.assertModuleOutput(
"""\
WWVB timecode: year=2020 days=001 hour=12 min=30 dst=0 ut1=-200 ly=1 ls=0
2020-001 12:30 201100000200010001020000000002000100010200100001020000010002
""",
"wwvb.gen",
"-m",
"1",
"2020",
"1",
"12",
"30",
)
self.assertModuleOutput(
"""\
WWVB timecode: year=2020 days=001 hour=12 min=30 dst=0 ut1=-200 ly=1 ls=0
2020-001 12:30 201100000200010001020000000002000100010200100001020000010002
""",
"wwvb.gen",
"-m",
"1",
"2020",
"1",
"1",
"12",
"30",
)
self.assertModuleError("wwvb.gen", "-m", "1", "2021", "7")
# Asserting a leap second
self.assertModuleOutput(
"""\
WWVB timecode: year=2020 days=001 hour=12 min=30 dst=0 ut1=-500 ly=1 ls=1
2020-001 12:30 201100000200010001020000000002000100010201010001020000011002
""",
"wwvb.gen",
"-m",
"1",
"-s",
"2020-1-1 12:30",
)
# Asserting a different ut1 value
self.assertModuleOutput(
"""\
WWVB timecode: year=2020 days=001 hour=12 min=30 dst=0 ut1=-300 ly=1 ls=0
2020-001 12:30 201100000200010001020000000002000100010200110001020000010002
""",
"wwvb.gen",
"-m",
"1",
"-d",
"-300",
"2020-1-1 12:30",
)
def test_dut1table(self) -> None:
"""Test the dut1table program"""
self.assertModuleOutputStarts(
"""\
1972-01-01 -0.2 182 LS on 1972-06-30 23:59:60 UTC
1972-07-01 0.8 123
1972-11-01 0.0 30
1972-12-01 -0.2 31 LS on 1972-12-31 23:59:60 UTC
""",
"wwvb.dut1table",
)
def test_json(self) -> None:
"""Test the JSON output format"""
self.assertModuleJson(
[
{
"year": 2021,
"days": 340,
"hour": 3,
"minute": 40,
"amplitude": "210000000200000001120011001002000000010200010001020001000002",
"phase": "111110011011010101000100100110011110001110111010111101001011",
},
{
"year": 2021,
"days": 340,
"hour": 3,
"minute": 41,
"amplitude": "210000001200000001120011001002000000010200010001020001000002",
"phase": "001010011100100011000101110000100001101000001111101100000010",
},
],
"wwvb.gen",
"-m",
"2",
"--style",
"json",
"--channel",
"both",
"2021-12-6 3:40",
)
self.assertModuleJson(
[
{
"year": 2021,
"days": 340,
"hour": 3,
"minute": 40,
"amplitude": "210000000200000001120011001002000000010200010001020001000002",
},
{
"year": 2021,
"days": 340,
"hour": 3,
"minute": 41,
"amplitude": "210000001200000001120011001002000000010200010001020001000002",
},
],
"wwvb.gen",
"-m",
"2",
"--style",
"json",
"--channel",
"amplitude",
"2021-12-6 3:40",
)
self.assertModuleJson(
[
{
"year": 2021,
"days": 340,
"hour": 3,
"minute": 40,
"phase": "111110011011010101000100100110011110001110111010111101001011",
},
{
"year": 2021,
"days": 340,
"hour": 3,
"minute": 41,
"phase": "001010011100100011000101110000100001101000001111101100000010",
},
],
"wwvb.gen",
"-m",
"2",
"--style",
"json",
"--channel",
"phase",
"2021-12-6 3:40",
)
def test_sextant(self) -> None:
"""Test the sextant output format"""
self.assertModuleOutput(
"""\
WWVB timecode: year=2021 days=340 hour=03 min=40 dst=0 ut1=-100 ly=0 ls=0 --style=sextant
2021-340 03:40 \
🬋🬩🬋🬹🬩🬹🬩🬹🬩🬹🬍🬎🬍🬎🬩🬹🬩🬹🬋🬍🬩🬹🬩🬹🬍🬎🬩🬹🬍🬎🬩🬹🬍🬎🬋🬹🬋🬎🬋🬍🬍🬎🬩🬹🬋🬎🬋🬎🬩🬹🬍🬎🬋🬎🬩🬹🬩🬹🬋🬍🬍🬎🬩🬹🬩🬹🬩🬹🬩🬹🬍🬎🬍🬎🬋🬎🬩🬹🬋🬩🬩🬹🬍🬎🬩🬹🬋🬹🬩🬹🬍🬎🬩🬹🬋🬎🬩🬹🬋🬩🬩🬹🬩🬹🬍🬎🬋🬹🬍🬎🬍🬎🬩🬹🬍🬎🬩🬹🬋🬩
2021-340 03:41 \
🬋🬍🬋🬎🬩🬹🬍🬎🬩🬹🬍🬎🬍🬎🬩🬹🬋🬹🬋🬩🬍🬎🬍🬎🬩🬹🬍🬎🬍🬎🬍🬎🬩🬹🬋🬹🬋🬎🬋🬍🬍🬎🬩🬹🬋🬎🬋🬹🬩🬹🬩🬹🬋🬎🬍🬎🬍🬎🬋🬍🬩🬹🬍🬎🬍🬎🬍🬎🬍🬎🬩🬹🬩🬹🬋🬎🬩🬹🬋🬍🬍🬎🬍🬎🬍🬎🬋🬎🬩🬹🬩🬹🬩🬹🬋🬹🬩🬹🬋🬍🬩🬹🬩🬹🬍🬎🬋🬎🬍🬎🬍🬎🬍🬎🬍🬎🬩🬹🬋🬍
""",
"wwvb.gen",
"-m",
"2",
"--style",
"sextant",
"2021-12-6 3:40",
)
def test_now(self) -> None:
"""Test outputting timecodes for 'now'"""
self.assertModuleOutputStarts(
"WWVB timecode: year=",
"wwvb.gen",
"-m",
"1",
)
def test_decode(self) -> None:
"""Test the commandline decoder"""
self.assertModuleOutput(
"""\
201100000200100001020011001012000000010200010001020001000002
year=2021 days=350 hour=22 min=30 dst=0 ut1=-100 ly=0 ls=0
""",
"wwvb.decode",
"201100000200100001020011001012000000010200010001020001000002",
)
self.assertModuleOutput(
"""\
201101111200100001020011001012000000010200010001020001000002
""",
"wwvb.decode",
"201101111200100001020011001012000000010200010001020001000002",
)