Skip to content

Commit

Permalink
Add 'check' command and update actions
Browse files Browse the repository at this point in the history
  • Loading branch information
rzuckerm committed Oct 5, 2024
1 parent 95ec0f0 commit 9a78d86
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 23 deletions.
18 changes: 9 additions & 9 deletions .github/workflows/makefile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: "3.8"

- uses: snok/install-poetry@v1.3.3
- uses: snok/install-poetry@v1
with:
version: 1.8.3
virtualenvs-create: false
Expand All @@ -35,13 +35,13 @@ jobs:
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- uses: snok/install-poetry@v1.3.3
- uses: snok/install-poetry@v1
with:
version: 1.8.3
virtualenvs-create: false
Expand All @@ -54,13 +54,13 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: "3.8"

- uses: snok/install-poetry@v1.3.3
- uses: snok/install-poetry@v1
with:
version: 1.8.3
virtualenvs-create: false
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: "3.8"

- uses: snok/install-poetry@v1.3.3
- uses: snok/install-poetry@v1
with:
version: 1.8.3
virtualenvs-create: false
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ If you'd like to contribute to Glotter2, read our [contributing guidelines](./CO

### Glotter2 releases

* 0.9.0:
* Add `check` command
* 0.8.2:
* Update to docker 7.1.0
* 0.8.1:
Expand Down
7 changes: 7 additions & 0 deletions doc/general-usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,10 @@ build server:
glotter batch 3 --batch <m> --parallel
where: ``<m>`` is ``1`` for the first build server, ``2`` for the second, and ``3`` for the third.

Check
-----

The ``check`` command makes sure that the sample program files are named properly. If they are
not, a list of improperly named files are output, and this command exits with an non-zero return code.
Otherwise, this command exits with a zero return code.
14 changes: 13 additions & 1 deletion glotter/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from glotter.download import download
from glotter.report import report
from glotter.batch import batch
from glotter.check import check


def main():
Expand All @@ -19,13 +20,14 @@ def main():
download Download all the docker images required to run the tests
report Output a report of discovered sources for configured projects and languages
batch Download docker images, run tests, and optionally remove images for each batch
check Check for invalid sample program filenames
""",
)
parser.add_argument(
"command",
type=str,
help="Subcommand to run",
choices=["run", "test", "download", "report", "batch"],
choices=["run", "test", "download", "report", "batch", "check"],
)
args = parser.parse_args(sys.argv[1:2])
commands = {
Expand All @@ -34,6 +36,7 @@ def main():
"test": parse_test,
"report": parse_report,
"batch": parse_batch,
"check": parse_check,
}
commands[args.command]()

Expand Down Expand Up @@ -141,5 +144,14 @@ def parse_batch():
batch(args)


def parse_check():
parser = argparse.ArgumentParser(
prog="glotter",
description="Check for invalid sample program filenames.",
)
args = parser.parse_args(sys.argv[2:])
check(args)


if __name__ == "__main__":
main()
22 changes: 22 additions & 0 deletions glotter/check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import sys

from glotter.source import get_sources, BAD_SOURCES
from glotter.settings import Settings


def check(_args):
# Get all sources
all_sources = get_sources(Settings().source_root, check_bad_sources=True)

# If no bad sources, exit with zero status
bad_sources = all_sources[BAD_SOURCES]
if not bad_sources:
print("All filenames correspond to valid projects")
sys.exit(0)

# Show bad sources and exit with non-zero status
print("The following filenames do not correspond to a valid project:")
for source in sorted(bad_sources):
print(f"- {source}")

sys.exit(1)
24 changes: 22 additions & 2 deletions glotter/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from glotter.containerfactory import ContainerFactory
from glotter.utils import error_and_exit

BAD_SOURCES = "__bad_sources__"


class Source:
"""Metadata about a source file"""
Expand Down Expand Up @@ -110,14 +112,22 @@ def cleanup(self):


@lru_cache
def get_sources(path):
def get_sources(path, check_bad_sources=False):
"""
Walk through a directory and create Source objects
:param path: path to the directory through which to walk
:return: a dict where the key is the ProjectType and the value is a list of all the Source objects of that project
:param check_bad_source: if True, check for bad source filenames. Default is False
:return: a dict where the key is the ProjectType and the value is a list of all the
Source objects of that project. If check_bad_source is True,
the BAD_SOURCES key contains a list of invalid paths relative to the current
working directory
"""
sources = {k: [] for k in Settings().projects}
orig_path = path
if check_bad_sources:
sources[BAD_SOURCES] = []

for root, _, files in os.walk(path):
path = os.path.abspath(root)
if "testinfo.yml" in files:
Expand All @@ -137,6 +147,16 @@ def get_sources(path):
project_name, os.path.basename(path), path, test_info_string
)
sources[project_type].append(source)

if check_bad_sources:
invalid_filenames = set(files) - (
set(folder_project_names.values()) | {"testinfo.yml", "README.md"}
)
sources[BAD_SOURCES] += [
os.path.join(os.path.relpath(path, orig_path), filename)
for filename in invalid_filenames
]

return sources


Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[tool.poetry]
name = "glotter2"
packages = [{include="glotter"}]
version = "0.8.2"
version = "0.9.0"
description = "An execution library for scripts written in any language. This is a fork of https://github.com/auroq/glotter"
authors = ["auroq", "rzuckerm"]
readme = "README.md"
Expand Down
54 changes: 47 additions & 7 deletions test/integration/test_source.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os

import pytest

from glotter import source


Expand All @@ -20,6 +22,15 @@ def create_files_from_list(files):
file.write(contents)


def get_files(tmp_dir, test_info_string_no_build, test_info_string_with_build):
return {
os.path.join(tmp_dir, "python", "testinfo.yml"): test_info_string_no_build,
os.path.join(tmp_dir, "python", "hello_world.py"): get_hello_world("python"),
os.path.join(tmp_dir, "go", "testinfo.yml"): test_info_string_with_build,
os.path.join(tmp_dir, "go", "hello-world.go"): get_hello_world("go"),
}


def test_get_sources_when_no_testinfo(
tmp_dir, test_info_string_no_build, test_info_string_with_build, mock_projects
):
Expand All @@ -37,20 +48,49 @@ def test_get_sources(
test_info_string_no_build,
test_info_string_with_build,
glotter_yml_projects,
monkeypatch,
mock_projects,
):
files = {
os.path.join(tmp_dir, "python", "testinfo.yml"): test_info_string_no_build,
os.path.join(tmp_dir, "python", "hello_world.py"): get_hello_world("python"),
os.path.join(tmp_dir, "go", "testinfo.yml"): test_info_string_with_build,
os.path.join(tmp_dir, "go", "hello-world.go"): get_hello_world("go"),
}
files = get_files(tmp_dir, test_info_string_no_build, test_info_string_with_build)
create_files_from_list(files)
sources = source.get_sources(tmp_dir)
assert len(sources["helloworld"]) == 2
assert source.BAD_SOURCES not in sources
assert not any(
source_list
for project_type, source_list in sources.items()
if project_type != "helloworld" and len(source_list) > 0
)


@pytest.mark.parametrize(
"bad_sources",
[
pytest.param({}, id="no-bad-sources"),
pytest.param(
{
os.path.join("python", "foo.py"): "",
os.path.join("python", "bar.py"): "",
os.path.join("go", "helloworld.go"): "",
},
id="bad-sources",
),
],
)
def test_get_sources_with_check_bad_sources(
bad_sources,
tmp_dir,
test_info_string_no_build,
test_info_string_with_build,
glotter_yml_projects,
mock_projects,
):
files = get_files(tmp_dir, test_info_string_no_build, test_info_string_with_build)
for filename, contents in bad_sources.items():
files[os.path.join(tmp_dir, filename)] = contents

create_files_from_list(files)
sources = source.get_sources(tmp_dir, check_bad_sources=True)

expected_bad_sources = sorted(bad_sources)
actual_bad_sources = sorted(sources[source.BAD_SOURCES])
assert actual_bad_sources == expected_bad_sources
61 changes: 61 additions & 0 deletions test/unit/test_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import sys
from unittest.mock import patch

import pytest

from glotter.__main__ import main
from glotter.source import BAD_SOURCES


def test_check_no_errors(mock_get_sources, mock_settings, mock_sources, capsys):
mock_get_sources.return_value = {**mock_sources, BAD_SOURCES: []}

with pytest.raises(SystemExit) as e:
call_check()

assert e.value.code == 0

output = capsys.readouterr().out
assert "All filenames correspond to valid projects" in output


def test_check_errors(mock_get_sources, mock_settings, mock_sources, capsys):
mock_get_sources.return_value = {
**mock_sources,
BAD_SOURCES: ["stuff/nonsense", "garbage/blah", "junk/whatever"],
}

with pytest.raises(SystemExit) as e:
call_check()

assert e.value.code != 0

output = capsys.readouterr().out
expected_output = """\
- garbage/blah
- junk/whatever
- stuff/nonsense
"""
assert expected_output in output


def call_check(args=None):
args = args or []
with patch.object(sys, "argv", ["glotter", "check"] + args):
main()


class MockArgs:
pass


@pytest.fixture()
def mock_get_sources():
with patch("glotter.check.get_sources") as mock:
yield mock


@pytest.fixture()
def mock_settings():
with patch("glotter.test_doc_generator.Settings") as mock:
yield mock

0 comments on commit 9a78d86

Please sign in to comment.