Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion stone/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@
help=('Print a JSON manifest of generated output paths instead of writing '
'generated source file contents.'),
)
_cmdline_parser.add_argument(
'--expected-output-manifest',
type=str,
help='JSON file containing the exact relative output paths Stone must produce.',
)
_cmdline_parser.add_argument(
'--clean-build',
action='store_true',
Expand Down Expand Up @@ -140,6 +145,42 @@
help='If set, backends will not see any routes for the specified namespaces.',
)

def _actual_outputs(output_root):
# type: (str) -> typing.List[str]
outputs = []
for root, _, file_names in os.walk(output_root):
for file_name in file_names:
output_path = os.path.join(root, file_name)
relpath = os.path.relpath(output_path, output_root).replace(os.sep, '/')
outputs.append(relpath)
return sorted(outputs)


def _load_expected_output_manifest(path):
# type: (str) -> typing.List[str]
with open(path, encoding='utf-8') as manifest_file:
data = json.load(manifest_file)
if not isinstance(data, list) or not all(isinstance(item, str) for item in data):
_cmdline_parser.error(
'--expected-output-manifest must be a JSON list of strings: {}'.format(path))
return sorted(data)


def _validate_expected_output_manifest(expected, actual):
# type: (typing.List[str], typing.List[str]) -> None
if actual == expected:
return

missing = sorted(set(expected) - set(actual))
extra = sorted(set(actual) - set(expected))
print(
'error: Stone output manifest mismatch.\nMissing: {}\nExtra: {}'.format(
missing,
extra),
file=sys.stderr)
sys.exit(1)


def main():
"""The entry point for the program."""
if '--' in sys.argv:
Expand Down Expand Up @@ -361,8 +402,20 @@ def main():
file=sys.stderr)
sys.exit(1)

if args.output_manifest or args.expected_output_manifest:
if args.output_manifest:
actual_manifest = c.output_manifest()
else:
actual_manifest = _actual_outputs(args.output)
else:
actual_manifest = None

if args.expected_output_manifest:
expected_manifest = _load_expected_output_manifest(args.expected_output_manifest)
_validate_expected_output_manifest(expected_manifest, actual_manifest)

if args.output_manifest:
print(json.dumps(c.output_manifest(), indent=2))
print(json.dumps(actual_manifest, indent=2))

if not sys.argv[0].endswith('stone'):
# If we aren't running from an entry_point, then return api to make it
Expand Down
36 changes: 36 additions & 0 deletions test/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
#!/usr/bin/env python


import json
import os
import tempfile
import unittest

from stone.cli import (
_actual_outputs,
_load_expected_output_manifest,
_validate_expected_output_manifest,
)
from stone.cli_helpers import parse_route_attr_filter


Expand Down Expand Up @@ -126,6 +134,34 @@ def test_parse_route_attr_filter(self):
self.assertFalse(expr.eval(MockRoute({'a': 1})))
self.assertFalse(expr.eval(MockRoute({'a': 1, 'b': 3})))

def test_actual_outputs(self):
with tempfile.TemporaryDirectory() as output_root:
os.makedirs(os.path.join(output_root, 'nested'))
with open(os.path.join(output_root, 'Generated.py'), 'w', encoding='utf-8'):
pass
with open(
os.path.join(output_root, 'nested', 'Generated.swift'),
'w',
encoding='utf-8'):
pass

self.assertEqual(
_actual_outputs(output_root),
['Generated.py', 'nested/Generated.swift'])

def test_load_expected_output_manifest(self):
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8') as manifest_file:
json.dump(['b.py', 'a.py'], manifest_file)
manifest_file.flush()

self.assertEqual(_load_expected_output_manifest(manifest_file.name), ['a.py', 'b.py'])

def test_validate_expected_output_manifest(self):
_validate_expected_output_manifest(['a.py'], ['a.py'])

with self.assertRaises(SystemExit):
_validate_expected_output_manifest(['a.py'], ['b.py'])


if __name__ == '__main__':
unittest.main()