Ejemplo n.º 1
0
class Engine(hitchpylibrarytoolkit.Engine):
    info_definition = InfoDefinition()

    def assert_text(self, will_output, actual_output):
        assert will_output == actual_output, \
            "GOT:\n{}\n\nEXPECTED:\n{}\n".format(will_output, actual_output)

    def set_up(self):
        self._build.ensure_built()

        for filename, contents in self.given.get('files', {}).items():
            filepath = self._build.working.parent.joinpath(filename)
            if not filepath.dirname().exists():
                filepath.dirname().makedirs()
            filepath.write_text(contents)
Ejemplo n.º 2
0
class Engine(hitchpylibrarytoolkit.Engine):
    info_definition = InfoDefinition(environments=InfoProperty(
        Seq(Enum(["gui", "mac", "docker", "headless", "wsl"]))), )

    def set_up(self):
        self._build.ensure_built()

        for filename, contents in self.given.get('files', {}).items():
            filepath = self._build.working.parent.joinpath(filename)
            if not filepath.dirname().exists():
                filepath.dirname().makedirs()
            filepath.write_text(contents)

    def screenshot_exists(self, filename):
        assert self._build.working.joinpath(filename).exists()
Ejemplo n.º 3
0
class Engine(BaseEngine):
    """Python engine for running tests."""

    given_definition = GivenDefinition(
        scripts=GivenProperty(MapPattern(Str(), Str())),
        python_version=GivenProperty(Str()),
        pexpect_version=GivenProperty(Str()),
        icommandlib_version=GivenProperty(Str()),
        setup=GivenProperty(Str()),
        files=GivenProperty(MapPattern(Str(), Str())),
        code=GivenProperty(Str()),
    )

    info_definition = InfoDefinition(
        importance=InfoProperty(schema=Int()),
        docs=InfoProperty(schema=Str()),
        fails_on_python_2=InfoProperty(schema=Bool()),
    )

    def __init__(self, keypath, rewrite=False):
        self.path = keypath
        self._rewrite = rewrite
        self._cprofile = False

    def set_up(self):
        """Set up your applications and the test environment."""
        self.path.state = self.path.gen.joinpath("state")
        if self.path.state.exists():
            self.path.state.rmtree(ignore_errors=True)
        self.path.state.mkdir()

        for script in self.given.get("scripts", []):
            script_path = self.path.state.joinpath(script)

            if not script_path.dirname().exists():
                script_path.dirname().makedirs()

            script_path.write_text(self.given["scripts"][script])
            script_path.chmod("u+x")

        for filename, contents in self.given.get("files", {}).items():
            self.path.state.joinpath(filename).write_text(contents)

        self.python = hitchpylibrarytoolkit.project_build(
            "commandlib", self.path, self.given["python version"]
        ).bin.python

        self.example_py_code = (
            ExamplePythonCode(self.python, self.path.state)
            .with_code(self.given.get("code", ""))
            .with_setup_code(self.given.get("setup", ""))
        )

    def _story_friendly_output(self, text):
        return text.replace(self.path.state, "/path/to")

    @no_stacktrace_for(AssertionError)
    @no_stacktrace_for(HitchRunPyException)
    @validate(
        code=Str(),
        will_output=Str(),
        raises=Map(
            {
                Optional("type"): Map({"in python 2": Str(), "in python 3": Str()})
                | Str(),
                Optional("message"): Map({"in python 2": Str(), "in python 3": Str()})
                | Str(),
            }
        ),
    )
    def run(self, code, will_output=None, raises=None):
        to_run = self.example_py_code.with_code(code)

        if self._cprofile:
            to_run = to_run.with_cprofile(
                self.path.profile.joinpath("{0}.dat".format(self.story.slug))
            )

        result = (
            to_run.expect_exceptions().run() if raises is not None else to_run.run()
        )

        if will_output is not None:
            actual_output = "\n".join(
                [line.rstrip() for line in result.output.split("\n")]
            )
            try:
                Templex(will_output).assert_match(actual_output)
            except AssertionError:
                if self._rewrite:
                    self.current_step.update(**{"will output": actual_output})
                else:
                    raise

        if raises is not None:
            differential = False  # Difference between python 2 and python 3 output?
            exception_type = raises.get("type")
            message = raises.get("message")

            if exception_type is not None:
                if not isinstance(exception_type, str):
                    differential = True
                    exception_type = (
                        exception_type["in python 2"]
                        if self.given["python version"].startswith("2")
                        else exception_type["in python 3"]
                    )

            if message is not None:
                if not isinstance(message, str):
                    differential = True
                    message = (
                        message["in python 2"]
                        if self.given["python version"].startswith("2")
                        else message["in python 3"]
                    )

            try:
                result = self.example_py_code.expect_exceptions().run()
                result.exception_was_raised(exception_type)
                exception_message = self._story_friendly_output(
                    result.exception.message
                )
                Templex(exception_message).assert_match(message)
            except AssertionError:
                if self._rewrite and not differential:
                    new_raises = raises.copy()
                    new_raises["message"] = self._story_friendly_output(
                        result.exception.message
                    )
                    self.current_step.update(raises=new_raises)
                else:
                    raise

    def file_contents_will_be(self, filename, contents):
        file_contents = "\n".join(
            [
                line.rstrip()
                for line in self.path.state.joinpath(filename)
                .bytes()
                .decode("utf8")
                .strip()
                .split("\n")
            ]
        )
        try:
            # Templex(file_contents).assert_match(contents.strip())
            assert file_contents == contents.strip(), "{0} not {1}".format(
                file_contents, contents.strip()
            )
        except AssertionError:
            if self._rewrite:
                self.current_step.update(contents=file_contents)
            else:
                raise

    def pause(self, message="Pause"):
        import IPython

        IPython.embed()

    def on_success(self):
        if self._cprofile:
            self.python(
                self.path.key.joinpath("printstats.py"),
                self.path.profile.joinpath("{0}.dat".format(self.story.slug)),
            ).run()
Ejemplo n.º 4
0
class Engine(BaseEngine):
    """Python engine for running tests."""

    given_definition = GivenDefinition(
        python_version=GivenProperty(Str()),
        selenium_version=GivenProperty(Str()),
        website=GivenProperty(MapPattern(Str(), Str())),
        selectors_yml=GivenProperty(Str()),
        javascript=GivenProperty(Str()),
        setup=GivenProperty(Str()),
        code=GivenProperty(Str()),
    )

    info_definition = InfoDefinition(
        status=InfoProperty(schema=Enum(["experimental", "stable"])),
        docs=InfoProperty(schema=Str()),
    )

    def __init__(self, keypath, settings):
        self.path = keypath
        self.settings = settings

    def set_up(self):
        """Set up your applications and the test environment."""
        self.path.state = self.path.gen.joinpath("state")
        if self.path.state.exists():
            self.path.state.rmtree(ignore_errors=True)
        self.path.state.mkdir()

        self.path.profile = self.path.gen.joinpath("profile")
        dirtemplate.DirTemplate(
            "webapp", self.path.key / "htmltemplate", self.path.state
        ).with_vars(javascript=self.given.get("javascript", "")).with_files(
            base_html={
                filename: {
                    "content": content
                }
                for filename, content in self.given.get("website", {}).items()
            }).ensure_built()

        self.path.state.joinpath("selectors.yml").write_text(
            self.given["selectors.yml"])

        self.server = (python("-m", "http.server").in_dir(self.path.state /
                                                          "webapp").pexpect())
        self.server.expect("Serving HTTP on 0.0.0.0")

        if not self.path.profile.exists():
            self.path.profile.mkdir()

        self.python = project_build(self.path, self.given["python version"],
                                    self.given["selenium version"]).bin.python

        self.example_py_code = (ExamplePythonCode(
            self.python,
            self.path.state).with_setup_code(self.given.get(
                "setup", "")).with_terminal_size(160, 100).with_long_strings())

    @validate(
        code=Str(),
        will_output=Map({
            "in python 2": Str(),
            "in python 3": Str()
        }) | Str(),
        raises=Map({
            Optional("type"):
            Map({
                "in python 2": Str(),
                "in python 3": Str()
            })
            | Str(),
            Optional("message"):
            Map({
                "in python 2": Str(),
                "in python 3": Str()
            })
            | Str(),
        }),
        in_interpreter=Bool(),
    )
    def run(self, code, will_output=None, raises=None, in_interpreter=False):
        if in_interpreter:
            code = "{0}\nprint(repr({1}))".format(
                "\n".join(code.strip().split("\n")[:-1]),
                code.strip().split("\n")[-1])

        to_run = self.example_py_code.with_code(code)

        if self.settings.get("cprofile"):
            to_run = to_run.with_cprofile(
                self.path.profile.joinpath("{0}.dat".format(self.story.slug)))

        result = (to_run.expect_exceptions().run()
                  if raises is not None else to_run.run())

        if will_output is not None:
            actual_output = "\n".join(
                [line.rstrip() for line in result.output.split("\n")])
            try:
                Templex(will_output).assert_match(actual_output)
            except AssertionError:
                if self.settings.get("rewrite"):
                    self.current_step.update(**{"will output": actual_output})
                else:
                    raise

        if raises is not None:
            differential = False  # Difference between python 2 and python 3 output?
            exception_type = raises.get("type")
            message = raises.get("message")

            if exception_type is not None:
                if not isinstance(exception_type, str):
                    differential = True
                    exception_type = (
                        exception_type["in python 2"]
                        if self.given["python version"].startswith("2") else
                        exception_type["in python 3"])

            if message is not None:
                if not isinstance(message, str):
                    differential = True
                    message = (message["in python 2"]
                               if self.given["python version"].startswith("2")
                               else message["in python 3"])

            try:
                result.exception_was_raised(exception_type, message)
            except ExpectedExceptionMessageWasDifferent:
                if self.settings.get("rewrite") and not differential:
                    new_raises = raises.copy()
                    new_raises["message"] = result.exception.message
                    self.current_step.update(raises=new_raises)
                else:
                    raise

    def do_nothing(self):
        pass

    def pause(self, message="Pause"):
        import IPython

        IPython.embed()

    def tear_down(self):
        self.server.kill(signal.SIGTERM)
        self.server.wait()
Ejemplo n.º 5
0
class Engine(BaseEngine):
    """Python engine for running tests."""

    given_definition = GivenDefinition(
        yaml_snippet=GivenProperty(
            Str(), document="yaml_snippet:\n```yaml\n{{ yaml_snippet }}\n```"),
        yaml_snippet_1=GivenProperty(
            Str(),
            document="yaml_snippet_1:\n```yaml\n{{ yaml_snippet_1 }}\n```"),
        yaml_snippet_2=GivenProperty(
            Str(),
            document="yaml_snippet_2:\n```yaml\n{{ yaml_snippet_2 }}\n```"),
        modified_yaml_snippet=GivenProperty(
            Str(),
            document=
            "modified_yaml_snippet:\n```yaml\n{{ modified_yaml_snippet }}\n```"
        ),
        python_version=GivenProperty(Str()),
        ruamel_version=GivenProperty(Str()),
        setup=GivenProperty(Str(), document="```python\n{{ setup }}\n```"),
    )

    info_definition = InfoDefinition(
        status=InfoProperty(schema=Enum(["experimental", "stable"])),
        docs=InfoProperty(schema=Str()),
        fails_on_python_2=InfoProperty(schema=Bool()),
        description=InfoProperty(schema=Str()),
        experimental=InfoProperty(schema=Bool()),
    )

    def __init__(self,
                 keypath,
                 python_path=None,
                 rewrite=False,
                 cprofile=False):
        self.path = keypath
        self._python_path = python_path
        self._rewrite = rewrite
        self._cprofile = cprofile

    def set_up(self):
        """Set up your applications and the test environment."""
        self.path.profile = self.path.gen.joinpath("profile")

        if not self.path.profile.exists():
            self.path.profile.mkdir()

        if not self._python_path:
            self.python = hitchpylibrarytoolkit.project_build(
                "strictyaml",
                self.path,
                self.given["python version"],
                {
                    "ruamel.yaml": self.given["ruamel version"]
                },
            ).bin.python
        else:
            self.python = Path(self._python_path)
            assert self.python.exists()

        self.example_py_code = (ExamplePythonCode(
            self.python, self.path.gen).with_code(self.given.get(
                "code", "")).with_setup_code(self.given.get(
                    "setup", "")).with_terminal_size(160, 100).with_strings(
                        yaml_snippet_1=self.given.get("yaml_snippet_1"),
                        yaml_snippet=self.given.get("yaml_snippet"),
                        yaml_snippet_2=self.given.get("yaml_snippet_2"),
                        modified_yaml_snippet=self.given.get(
                            "modified_yaml_snippet"),
                    ))

    @no_stacktrace_for(AssertionError)
    @no_stacktrace_for(HitchRunPyException)
    @validate(
        code=Str(),
        will_output=Map({
            "in python 2": Str(),
            "in python 3": Str()
        }) | Str(),
        raises=Map({
            Optional("type"): CODE_TYPE,
            Optional("message"): CODE_TYPE
        }),
        in_interpreter=Bool(),
    )
    def run(
        self,
        code,
        will_output=None,
        yaml_output=True,
        raises=None,
        in_interpreter=False,
    ):
        if in_interpreter:
            code = "{0}\nprint(repr({1}))".format(
                "\n".join(code.strip().split("\n")[:-1]),
                code.strip().split("\n")[-1])
        to_run = self.example_py_code.with_code(code)

        if self._cprofile:
            to_run = to_run.with_cprofile(
                self.path.profile.joinpath("{0}.dat".format(self.story.slug)))

        if raises is None:
            result = (to_run.expect_exceptions().run()
                      if raises is not None else to_run.run())

            if will_output is not None:
                actual_output = "\n".join(
                    [line.rstrip() for line in result.output.split("\n")])
                try:
                    Templex(will_output).assert_match(actual_output)
                except AssertionError:
                    if self._rewrite:
                        self.current_step.update(
                            **{"will output": actual_output})
                    else:
                        raise

        elif raises is not None:
            differential = False  # Difference between python 2 and python 3 output?
            exception_type = raises.get("type")
            message = raises.get("message")

            if exception_type is not None:
                if not isinstance(exception_type, str):
                    differential = True
                    exception_type = (
                        exception_type["in python 2"]
                        if self.given["python version"].startswith("2") else
                        exception_type["in python 3"])

            if message is not None:
                if not isinstance(message, str):
                    differential = True
                    message = (message["in python 2"]
                               if self.given["python version"].startswith("2")
                               else message["in python 3"])

            try:
                result = to_run.expect_exceptions().run()
                result.exception_was_raised(exception_type, message)
            except ExpectedExceptionMessageWasDifferent as error:
                if self._rewrite and not differential:
                    new_raises = raises.copy()
                    new_raises["message"] = result.exception.message
                    self.current_step.update(raises=new_raises)
                else:
                    raise

    def pause(self, message="Pause"):
        import IPython

        IPython.embed()

    def on_success(self):
        if self._rewrite:
            self.new_story.save()
        if self._cprofile:
            self.python(
                self.path.key.joinpath("printstats.py"),
                self.path.profile.joinpath("{0}.dat".format(self.story.slug)),
            ).run()
Ejemplo n.º 6
0
class Engine(BaseEngine):
    """Python engine for running tests."""
    given_definition = GivenDefinition(
        files=GivenProperty(MapPattern(Str(), Str())),
        python_version=GivenProperty(Str()),
        setup=GivenProperty(Str()),
    )

    info_definition = InfoDefinition(
        docs=InfoProperty(schema=Str()),
    )

    def __init__(self, paths, settings):
        self.path = paths
        self.settings = settings
        self._build = hitchpylibrarytoolkit.PyLibraryBuild(
            "hitchqs",
            self.path,
        )

    def set_up(self):
        """Set up the environment ready to run the stories."""
        self._build.ensure_built()
        self.path.state = self.path.gen.joinpath("state")

        if self.path.state.exists():
            self.path.state.rmtree()
        self.path.state.mkdir()

        for filename, content in self.given['files'].items():
            filepath = self.path.state.joinpath(filename)

            if not filepath.dirname().exists():
                filepath.dirname().makedirs()
            filepath.write_text(content)

        self.python = self._build.bin.python

    def _process_exception(self, string):
        return string.replace(self.path.state, "/path/to")

    @no_stacktrace_for(AssertionError)
    @no_stacktrace_for(HitchRunPyException)
    @validate(
        code=Str(),
        will_output=Str(),
        raises=Map({
            Optional("type"): Str(),
            Optional("message"): Str(),
        })
    )
    def run(self, code, will_output=None, raises=None):
        self.example_py_code = ExamplePythonCode(self.python, self.path.state)\
            .with_terminal_size(160, 100)\
            .with_setup_code(self.given['setup'])\
            .in_dir(self.path.state)
        to_run = self.example_py_code.with_code(code)

        if self.settings.get("cprofile"):
            to_run = to_run.with_cprofile(
                self.path.profile.joinpath("{0}.dat".format(self.story.slug))
            )

        result = to_run.expect_exceptions().run() if raises is not None else to_run.run()

        actual_output = result.output

        if will_output is not None:
            try:
                Templex(will_output).assert_match(actual_output)
            except AssertionError:
                if self.settings.get("overwrite artefacts"):
                    self.current_step.update(**{"will output": actual_output})
                else:
                    raise

        if raises is not None:
            exception_type = raises.get('type')
            message = raises.get('message')

            try:
                result.exception_was_raised(exception_type)
                exception_message = self._process_exception(result.exception.message)
                Templex(message).assert_match(exception_message)
            except AssertionError:
                if self.settings.get("overwrite artefacts"):
                    new_raises = raises.copy()
                    new_raises['message'] = exception_message
                    self.current_step.update(raises=new_raises)
                else:
                    raise

    @no_stacktrace_for(AssertionError)
    @validate(files=MapPattern(Str(), Str()))
    def build_output_is(self, files):
        for filename, content in files.items():
            filepath = self.path.state.joinpath(filename)

            assert filepath.exists(), "{0} does not exist".format(filename)

            if content != "":
                try:
                    Templex(content).assert_match(filepath.text())
                except AssertionError as error:
                    raise AssertionError("{0} is nonmatching:\n\n{1}".format(filename, error))

        actual_files = [
            path.replace(self.path.state + "/", "")
            for path in pathquery(self.path.state.joinpath("example")).is_not_dir()
        ]

        assert len(actual_files) == len(files.keys()), \
            "Should be:\n\n{0}\n\nAre actually:\n\n{1}\n".format(
                '\n'.join(files.keys()),
                '\n'.join(actual_files),
            )

    def on_success(self):
        self.new_story.save()
Ejemplo n.º 7
0
class Engine(BaseEngine):
    """Python engine for running tests."""

    given_definition = GivenDefinition(
        setup=GivenProperty(Str()),
        boxname=GivenProperty(Str()),
        vmname=GivenProperty(Str()),
        issue=GivenProperty(Str()),
        files=GivenProperty(MapPattern(Str(), Str())),
        python_version=GivenProperty(Str()),
    )

    info_definition = InfoDefinition(
        status=InfoProperty(schema=Enum(["experimental", "stable"])),
        docs=InfoProperty(schema=Str()),
    )

    def __init__(self, paths, settings):
        self.path = paths
        self.settings = settings

    def set_up(self):
        """Set up your applications and the test environment."""
        self.path.cachestate = self.path.gen.joinpath("cachestate")
        self.path.state = self.path.gen.joinpath("state")
        self.path.working_dir = self.path.gen.joinpath("working")
        self.path.build_path = self.path.gen.joinpath("build_path")
        self.path.localsync = self.path.gen.joinpath("local_sync")

        if self.path.state.exists():
            self.path.state.rmtree(ignore_errors=True)
        self.path.state.mkdir()

        if self.path.localsync.exists():
            self.path.localsync.rmtree(ignore_errors=True)
        self.path.localsync.mkdir()

        if self.path.build_path.exists():
            self.path.build_path.rmtree(ignore_errors=True)
        self.path.build_path.mkdir()

        self.python = hitchpylibrarytoolkit.project_build(
            "hitchbuildvagrant", self.path,
            self.given.get("python_version", "3.7.0")).bin.python

        if not self.path.cachestate.exists():
            self.path.cachestate.mkdir()

        for filename, contents in self.given.get("files", {}).items():
            filepath = self.path.state.joinpath(filename)
            if not filepath.dirname().exists():
                filepath.dirname().makedirs()
            filepath.write_text(contents)

        if self.path.working_dir.exists():
            self.path.working_dir.rmtree(ignore_errors=True)
        self.path.working_dir.mkdir()

        self.example_py_code = (ExamplePythonCode(
            self.python, self.path.state).with_setup_code(
                self.given.get("setup", "").replace(
                    "/path/to/share",
                    self.path.cachestate)).with_terminal_size(
                        160, 100).with_long_strings(
                            share=str(self.path.cachestate),
                            build_path=str(self.path.build_path),
                            issue=str(self.given.get("issue")),
                            boxname=str(self.given.get("boxname")),
                            vmname=str(self.given.get("vmname")),
                            local_sync_path=str(self.path.localsync),
                        ))

    @no_stacktrace_for(HitchRunPyException)
    def run(self, code):
        self.example_py_code.with_code(code).run()

    def write_to_localsync(self, **files):
        for filename, contents in files.items():
            self.path.localsync.joinpath(filename).write_text(contents)

    def delete_localsync_file(self, filename):
        self.path.localsync.joinpath(filename).remove()

    def write_file(self, filename, contents):
        self.path.state.joinpath(filename).write_text(contents)

    def raises_exception(self, message=None, exception_type=None):
        try:
            result = self.example_python_code.expect_exceptions().run(
                self.path.state, self.python)
            result.exception_was_raised(exception_type, message.strip())
        except ExpectedExceptionMessageWasDifferent as error:
            if self.settings.get("rewrite"):
                self.current_step.update(message=error.actual_message)
            else:
                raise

    def file_contains(self, filename, contents):
        assert (self.path.working_dir.joinpath(filename).bytes().decode("utf8")
                == contents)

    @validate(duration=Float())
    def sleep(self, duration):
        import time

        time.sleep(duration)

    def pause(self, message="Pause"):
        import IPython

        IPython.embed()

    def on_failure(self, reason):
        pass

    def tear_down(self):
        for vagrantfile in pathquery(self.path.state).named("Vagrantfile"):
            Command("vagrant", "destroy",
                    "-f").in_dir(vagrantfile.abspath().dirname()).run()
Ejemplo n.º 8
0
class Engine(BaseEngine):
    """Python engine for running tests."""

    given_definition = GivenDefinition(
        python_version=GivenProperty(Str()),
        files=GivenProperty(MapPattern(Str(), Str())),
    )

    info_definition = InfoDefinition(
        status=InfoProperty(schema=Enum(["experimental", "stable"])),
        docs=InfoProperty(schema=Str()),
        fails_on_python_2=InfoProperty(schema=Bool()),
        description=InfoProperty(schema=Str()),
        experimental=InfoProperty(schema=Bool()),
    )

    def __init__(self, keypath, rewrite=False, build=False):
        self.path = keypath
        self._rewrite = rewrite
        self._build = hitchpylibrarytoolkit.PyLibraryBuild(
            "hitchqs",
            self.path,
        )

    def set_up(self):
        """Set up your applications and the test environment."""  
        self._build.ensure_built()
        
        self.path.state = self.path.project.parent / "tempqs"
        if self.path.state.exists():
            self.path.state.rmtree(ignore_errors=True)
        self.path.state.mkdir()

        for filename, contents in self.given.get("files", {}).items():
            filepath = self.path.state.joinpath(filename)
            if not filepath.dirname().exists():
                filepath.dirname().makedirs()
            filepath.write_text(contents)

        self.path.profile = self.path.gen.joinpath("profile")

        if not self.path.profile.exists():
            self.path.profile.mkdir()

        self.qs = self._build.bin.quickstart

    def _run(self, command, args, will_output=None, exit_code=0, timeout=5):
        process = command(*shlex.split(args)).interact().screensize(160, 80).run()
        process.wait_for_finish()

        actual_output = process.stripshot()\
                               .replace(self.path.state, "/path/to")

        # Replace 35.1 SECONDS with n.n SECONDS
        actual_output = re.sub("[0-9]+\.[0-9]", "n.n", actual_output)

        if will_output is not None:
            try:
                Templex(will_output).assert_match(actual_output)
            except AssertionError:
                if self._rewrite:
                    self.current_step.update(**{"will output": actual_output})
                else:
                    raise

        assert process.exit_code == exit_code, "Exit code should be {} was {}, output:\n{}".format(
            exit_code,
            process.exit_code,
            actual_output,
        )

    @validate(timeout=Int(), exit_code=Int())
    @no_stacktrace_for(AssertionError)
    def quickstart(self, args, will_output=None, exit_code=0, timeout=5):
        self._run(self.qs.in_dir(self.path.state), args, will_output, exit_code, timeout=timeout)

    @no_stacktrace_for(AssertionError)
    def hk(self, args, will_output=None, exit_code=0, timeout=5, in_dir=""):
        if self._build:
            self._run(Command("hk").in_dir(self.path.state / in_dir), args, will_output, exit_code, timeout=timeout)

    def initial_hk(self, args="", in_dir=""):
        if self._build:
            Command("hk", *shlex.split(args)).in_dir(self.path.state / in_dir).run()

    @validate(filenames=Seq(Str()))
    def files_appear(self, filenames):
        appeared = set()
        should_appear = set(filenames)
        for existing_file in pathquery(self.path.state):
            if "__pycache__" not in existing_file and not existing_file.isdir():
                appeared.add(str(existing_file.relpath(self.path.state)))

        diff = should_appear.symmetric_difference(appeared)

        assert diff == set(), \
            "Difference in files that appeared:\n{}".format('\n'.join(diff))

    def pause(self, message="Pause"):
        import IPython

        IPython.embed()


    def tear_down(self):
        if self._build:
            if self.path.state.exists():
                Command("hk", "--clean").ignore_errors().in_dir(self.path.state).run()