diff options
Diffstat (limited to '')
-rw-r--r-- | tests/__init__.py | 0 | ||||
-rw-r--r-- | tests/asciicast/__init__.py | 0 | ||||
-rw-r--r-- | tests/asciicast/v2_test.py | 28 | ||||
-rw-r--r-- | tests/config_test.py | 218 | ||||
-rw-r--r-- | tests/demo.cast | 40 | ||||
-rw-r--r-- | tests/demo.json | 114 | ||||
-rwxr-xr-x | tests/distros.sh | 38 | ||||
-rw-r--r-- | tests/distros/Dockerfile.alpine | 19 | ||||
-rw-r--r-- | tests/distros/Dockerfile.arch | 22 | ||||
-rw-r--r-- | tests/distros/Dockerfile.centos | 18 | ||||
-rw-r--r-- | tests/distros/Dockerfile.debian | 33 | ||||
-rw-r--r-- | tests/distros/Dockerfile.fedora | 20 | ||||
-rw-r--r-- | tests/distros/Dockerfile.ubuntu | 32 | ||||
-rwxr-xr-x | tests/integration.sh | 95 | ||||
-rw-r--r-- | tests/pty_test.py | 54 | ||||
-rw-r--r-- | tests/test_helper.py | 16 |
16 files changed, 747 insertions, 0 deletions
diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/__init__.py diff --git a/tests/asciicast/__init__.py b/tests/asciicast/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/asciicast/__init__.py diff --git a/tests/asciicast/v2_test.py b/tests/asciicast/v2_test.py new file mode 100644 index 0000000..113ddf7 --- /dev/null +++ b/tests/asciicast/v2_test.py @@ -0,0 +1,28 @@ +import json +import tempfile + +from asciinema.asciicast import v2 + +from ..test_helper import Test + + +class TestWriter(Test): + @staticmethod + def test_writing() -> None: + _file, path = tempfile.mkstemp() + + with v2.writer(path, width=80, height=24) as w: + w.write_stdout(1, "x") # ensure it supports both str and bytes + w.write_stdout(2, bytes.fromhex("78 c5 bc c3 b3 c5")) + w.write_stdout(3, bytes.fromhex("82 c4 87")) + w.write_stdout(4, bytes.fromhex("78 78")) + + with open(path, "rt", encoding="utf_8") as f: + lines = list(map(json.loads, f.read().strip().split("\n"))) + assert lines == [ + {"version": 2, "width": 80, "height": 24}, + [1, "o", "x"], + [2, "o", "xżó"], + [3, "o", "łć"], + [4, "o", "xx"], + ], f"got:\n\n{lines}" diff --git a/tests/config_test.py b/tests/config_test.py new file mode 100644 index 0000000..7b154ff --- /dev/null +++ b/tests/config_test.py @@ -0,0 +1,218 @@ +import re +import tempfile +from os import path +from typing import Dict, Optional + +import asciinema.config as cfg +from asciinema.config import Config + + +def create_config( + content: Optional[str] = None, env: Optional[Dict[str, str]] = None +) -> Config: + # avoid redefining `dir` builtin + dir_ = tempfile.mkdtemp() + + if content: + # avoid redefining `os.path` + path_ = f"{dir_}/config" + with open(path_, "wt", encoding="utf_8") as f: + f.write(content) + + return cfg.Config(dir_, env) + + +def read_install_id(install_id_path: str) -> str: + with open(install_id_path, "rt", encoding="utf_8") as f: + return f.read().strip() + + +def test_upgrade_no_config_file() -> None: + config = create_config() + config.upgrade() + install_id = read_install_id(config.install_id_path) + + assert re.match("^\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}", install_id) + assert install_id == config.install_id + assert not path.exists(config.config_file_path) + + # it must not change after another upgrade + + config.upgrade() + + assert read_install_id(config.install_id_path) == install_id + + +def test_upgrade_config_file_with_api_token() -> None: + config = create_config("[api]\ntoken = foo-bar-baz") + config.upgrade() + + assert read_install_id(config.install_id_path) == "foo-bar-baz" + assert config.install_id == "foo-bar-baz" + assert not path.exists(config.config_file_path) + + config.upgrade() + + assert read_install_id(config.install_id_path) == "foo-bar-baz" + + +def test_upgrade_config_file_with_api_token_and_more() -> None: + config = create_config( + "[api]\ntoken = foo-bar-baz\nurl = http://example.com" + ) + config.upgrade() + + assert read_install_id(config.install_id_path) == "foo-bar-baz" + assert config.install_id == "foo-bar-baz" + assert config.api_url == "http://example.com" + assert path.exists(config.config_file_path) + + config.upgrade() + + assert read_install_id(config.install_id_path) == "foo-bar-baz" + + +def test_upgrade_config_file_with_user_token() -> None: + config = create_config("[user]\ntoken = foo-bar-baz") + config.upgrade() + + assert read_install_id(config.install_id_path) == "foo-bar-baz" + assert config.install_id == "foo-bar-baz" + assert not path.exists(config.config_file_path) + + config.upgrade() + + assert read_install_id(config.install_id_path) == "foo-bar-baz" + + +def test_upgrade_config_file_with_user_token_and_more() -> None: + config = create_config( + "[user]\ntoken = foo-bar-baz\n[api]\nurl = http://example.com" + ) + config.upgrade() + + assert read_install_id(config.install_id_path) == "foo-bar-baz" + assert config.install_id == "foo-bar-baz" + assert config.api_url == "http://example.com" + assert path.exists(config.config_file_path) + + config.upgrade() + + assert read_install_id(config.install_id_path) == "foo-bar-baz" + + +def test_default_api_url() -> None: + config = create_config("") + assert config.api_url == "https://asciinema.org" + + +def test_default_record_stdin() -> None: + config = create_config("") + assert config.record_stdin is False + + +def test_default_record_command() -> None: + config = create_config("") + assert config.record_command is None + + +def test_default_record_env() -> None: + config = create_config("") + assert config.record_env == "SHELL,TERM" + + +def test_default_record_idle_time_limit() -> None: + config = create_config("") + assert config.record_idle_time_limit is None + + +def test_default_record_yes() -> None: + config = create_config("") + assert config.record_yes is False + + +def test_default_record_quiet() -> None: + config = create_config("") + assert config.record_quiet is False + + +def test_default_play_idle_time_limit() -> None: + config = create_config("") + assert config.play_idle_time_limit is None + + +def test_api_url() -> None: + config = create_config("[api]\nurl = http://the/url") + assert config.api_url == "http://the/url" + + +def test_api_url_when_override_set() -> None: + config = create_config( + "[api]\nurl = http://the/url", {"ASCIINEMA_API_URL": "http://the/url2"} + ) + assert config.api_url == "http://the/url2" + + +def test_record_command() -> None: + command = "bash -l" + config = create_config(f"[record]\ncommand = {command}") + assert config.record_command == command + + +def test_record_stdin() -> None: + config = create_config("[record]\nstdin = yes") + assert config.record_stdin is True + + +def test_record_env() -> None: + config = create_config("[record]\nenv = FOO,BAR") + assert config.record_env == "FOO,BAR" + + +def test_record_idle_time_limit() -> None: + config = create_config("[record]\nidle_time_limit = 2.35") + assert config.record_idle_time_limit == 2.35 + + config = create_config("[record]\nmaxwait = 2.35") + assert config.record_idle_time_limit == 2.35 + + +def test_record_yes() -> None: + yes = "yes" + config = create_config(f"[record]\nyes = {yes}") + assert config.record_yes is True + + +def test_record_quiet() -> None: + quiet = "yes" + config = create_config(f"[record]\nquiet = {quiet}") + assert config.record_quiet is True + + +def test_play_idle_time_limit() -> None: + config = create_config("[play]\nidle_time_limit = 2.35") + assert config.play_idle_time_limit == 2.35 + + config = create_config("[play]\nmaxwait = 2.35") + assert config.play_idle_time_limit == 2.35 + + +def test_notifications_enabled() -> None: + config = create_config("") + assert config.notifications_enabled is True + + config = create_config("[notifications]\nenabled = yes") + assert config.notifications_enabled is True + + config = create_config("[notifications]\nenabled = no") + assert config.notifications_enabled is False + + +def test_notifications_command() -> None: + config = create_config("") + assert config.notifications_command is None + + config = create_config( + '[notifications]\ncommand = tmux display-message "$TEXT"' + ) + assert config.notifications_command == 'tmux display-message "$TEXT"' diff --git a/tests/demo.cast b/tests/demo.cast new file mode 100644 index 0000000..fe55360 --- /dev/null +++ b/tests/demo.cast @@ -0,0 +1,40 @@ +{"env": {"TERM": "xterm-256color", "SHELL": "/usr/local/bin/fish"}, "width": 75, "height": 18, "timestamp": 1509091818, "version": 2, "idle_time_limit": 2.0} +[0.089436, "o", "\u001b]0;fish /Users/sickill/code/asciinema/asciinema\u0007\u001b[30m\u001b(B\u001b[m"] +[0.100989, "o", "\u001b[?2004h"] +[0.164215, "o", "\u001b]0;fish /Users/sickill/code/asciinema/asciinema\u0007\u001b[30m\u001b(B\u001b[m"] +[0.164513, "o", "\u001b[38;5;237m⏎\u001b(B\u001b[m \r⏎ \r\u001b[2K"] +[0.164709, "o", "\u001b[32m~/c/a/asciinema\u001b[30m\u001b(B\u001b[m (develop ↩☡=) \u001b[30m\u001b(B\u001b[m\u001b[K"] +[1.511526, "i", "v"] +[1.511937, "o", "v"] +[1.512148, "o", "\b\u001b[38;2;0;95;215mv\u001b[30m\u001b(B\u001b[m"] +[1.514564, "o", "\u001b[38;2;85;85;85mim tests/vim.cast \u001b[18D\u001b[30m\u001b(B\u001b[m"] +[1.615727, "i", "i"] +[1.616261, "o", "\u001b[38;2;0;95;215mi\u001b[38;2;85;85;85mm tests/vim.cast \u001b[17D\u001b[30m\u001b(B\u001b[m"] +[1.694908, "i", "m"] +[1.695262, "o", "\u001b[38;2;0;95;215mm\u001b[38;2;85;85;85m tests/vim.cast \u001b[16D\u001b[30m\u001b(B\u001b[m"] +[2.751713, "i", "\r"] +[2.752186, "o", "\u001b[K\r\n\u001b[30m"] +[2.752381, "o", "\u001b(B\u001b[m\u001b[?2004l"] +[2.752718, "o", "\u001b]0;vim /Users/sickill/code/asciinema/asciinema\u0007\u001b[30m\u001b(B\u001b[m\r"] +[2.86619, "o", "\u001b[?1000h\u001b[?2004h\u001b[?1049h\u001b[?1h\u001b=\u001b[?2004h"] +[2.867669, "o", "\u001b[1;18r\u001b[?12h\u001b[?12l\u001b[27m\u001b[29m\u001b[m\u001b[38;5;231m\u001b[48;5;235m\u001b[H\u001b[2J\u001b[2;1H▽\u001b[6n\u001b[2;1H \u001b[1;1H\u001b[>c"] +[2.868169, "i", "\u001b[2;2R\u001b[>0;95;0c"] +[2.869918, "o", "\u001b[?1000l\u001b[?1002h\u001b[?12$p"] +[2.870136, "o", "\u001b[?25l\u001b[1;1H\u001b[93m1 \u001b[m\u001b[38;5;231m\u001b[48;5;235m\r\n\u001b[38;5;59m\u001b[48;5;236m~ \u001b[3;1H~ \u001b[4;1H~ \u001b[5;1H~ \u001b[6;1H~ \u001b[7;1H~ \u001b[8;1H~ \u001b[9;1H~ \u001b[10;1H~ \u001b[11;1H~ \u001b[12;1H~ \u001b[13;1H~ "] +[2.870245, "o", " \u001b[14;1H~ \u001b[15;1H~ \u001b[16;1H~ \u001b[m\u001b[38;5;231m\u001b[48;5;235m\u001b[17;1H\u001b[1m\u001b[38;5;231m\u001b[48;5;236m[No Name] (unix/utf-8/) (line 0/1, col 000)\u001b[m\u001b[38;5;231m\u001b[48;5;235m\u001b[3;30HVIM - Vi IMproved\u001b[5;30Hversion 8.0.1171\u001b[6;26Hby Bram Moolenaar et al.\u001b[7;17HVim is open source and freely distributable\u001b[9;24HBecome a registered Vim user!\u001b[10;15Htype :help register\u001b[38;5;59m\u001b[48;5;236m<Enter>\u001b[m\u001b[38;5;231m\u001b[48;5;235m for information \u001b[12;15Htype :q\u001b[38;5;59m\u001b[48;5;236m<Enter>\u001b[m\u001b[38;5;231m\u001b[48;5;235m to exit \u001b[13;15Htype :help\u001b[38;5;59m\u001b[48;5;236m<Enter>\u001b[m\u001b[38;5;231m\u001b[48;5;235m or \u001b[38;5;59m\u001b[48;5;236m<F1>\u001b[m\u001b[38;5;231m\u001b[48;5;235m for on-line help\u001b[14;15Htype :help version8\u001b[38;5;59m\u001b[48;5;236m<Enter>\u001b[m\u001b[38;5;231m\u001b[48;5;235m for version"] +[2.870302, "o", " info\u001b[1;5H\u001b[?25h"] +[5.63147, "i", ":"] +[5.631755, "o", "\u001b[?25l\u001b[18;65H:\u001b[1;5H"] +[5.631934, "o", "\u001b[18;65H\u001b[K\u001b[18;1H:\u001b[?2004l\u001b[?2004h\u001b[?25h"] +[6.16692, "i", "q"] +[6.167137, "o", "q\u001b[?25l\u001b[?25h"] +[7.463349, "i", "\r"] +[7.463561, "o", "\r"] +[7.498922, "o", "\u001b[?25l\u001b[?1002l\u001b[?2004l"] +[7.604236, "o", "\u001b[18;1H\u001b[K\u001b[18;1H\u001b[?2004l\u001b[?1l\u001b>\u001b[?25h\u001b[?1049l"] +[7.612576, "o", "\u001b[?2004h"] +[7.655999, "o", "\u001b]0;fish /Users/sickill/code/asciinema/asciinema\u0007\u001b[30m\u001b(B\u001b[m"] +[7.656239, "o", "\u001b[38;5;237m⏎\u001b(B\u001b[m \r⏎ \r\u001b[2K\u001b[32m~/c/a/asciinema\u001b[30m\u001b(B\u001b[m (develop ↩☡=) \u001b[30m\u001b(B\u001b[m\u001b[K"] +[11.891762, "i", "\u0004"] +[11.893297, "o", "\r\n\u001b[30m\u001b(B\u001b[m\u001b[30m\u001b(B\u001b[m"] +[11.89348, "o", "\u001b[?2004l"] diff --git a/tests/demo.json b/tests/demo.json new file mode 100644 index 0000000..68092ae --- /dev/null +++ b/tests/demo.json @@ -0,0 +1,114 @@ +{ + "version": 1, + "width": 80, + "height": 40, + "duration": 6.46111, + "command": "/bin/bash", + "title": null, + "env": { + "TERM": "xterm-256color", + "SHELL": "/bin/bash" + }, + "stdout": [ + [ + 0.013659, + "\u001b[?1034hbash-3.2$ " + ], + [ + 1.923187, + "v" + ], + [ + 0.064049, + "i" + ], + [ + 0.032034, + "m" + ], + [ + 0.19157, + "\r\n" + ], + [ + 0.032342, + "\u001b[?1049h\u001b[?1h\u001b=\u001b[2;1H▽\u001b[6n\u001b[2;1H \u001b[1;1H" + ], + [ + 0.001436, + "\u001b[1;40r\u001b[?12;25h\u001b[?12l\u001b[?25h\u001b[27m\u001b[m\u001b[H\u001b[2J\u001b[>c" + ], + [ + 0.000311, + "\u001b[?25l\u001b[1;1H\u001b[33m 1 \u001b[m\r\n\u001b[1m\u001b[34m~ \u001b[3;1H~ \u001b[4;1H~ \u001b[5;1H~ \u001b[6;1H~ \u001b[7;1H~ \u001b[8;1H~ \u001b[9;1H~ \u001b[10;1H~ \u001b[11;1H~ \u001b[12;1H~ \u001b[13;1H~ " + ], + [ + 3.9e-05, + " \u001b[14;1H~ \u001b[15;1H~ \u001b[16;1H~ \u001b[17;1H~ \u001b[18;1H~ \u001b[19;1H~ \u001b[20;1H~ \u001b[21;1H~ \u001b[22;1H~ \u001b[23;1H~ \u001b[24;1H~ \u001b[25;1H~ " + ], + [ + 9.2e-05, + " \u001b[26;1H~ \u001b[27;1H~ \u001b[28;1H~ \u001b[29;1H~ \u001b[30;1H~ \u001b[31;1H~ \u001b[32;1H~ \u001b[33;1H~ \u001b[34;1H~ \u001b[35;1H~ \u001b[36;1H~ \u001b[37;" + ], + [ + 2.4e-05, + "1H~ \u001b[38;1H~ \u001b[m\u001b[39;1H\u001b[1m\u001b[7m[No Name] \u001b[m\u001b[14;32HVIM - Vi IMproved\u001b[16;33Hversion 7.4.8056\u001b[17;29Hby Bram Moolenaar et al.\u001b[18;19HVim is open source and freely distributable\u001b[20;26HBecome a registered Vim user!\u001b[21;18Htype :help register\u001b[32m<Enter>\u001b[m for information \u001b[23;18Htype :q\u001b[32m<Enter>\u001b[m to exit \u001b[24;18Htype :help\u001b[32m<Enter>\u001b[m or \u001b[32m<F1>\u001b[m for on-line help\u001b[25;18Htype :help version7\u001b[32m<Enter>\u001b[m for version info\u001b[1;5H\u001b[?12l\u001b[?25h" + ], + [ + 1.070242, + "\u001b[?25l\u001b[40;1H:" + ], + [ + 2.3e-05, + "\u001b[?12l\u001b[?25h" + ], + [ + 0.503964, + "q" + ], + [ + 0.151903, + "u" + ], + [ + 0.04002, + "i" + ], + [ + 0.088084, + "t" + ], + [ + 0.287636, + "\r" + ], + [ + 0.002178, + "\u001b[?25l\u001b[40;1H\u001b[K\u001b[40;1H\u001b[?1l\u001b>\u001b[?12l\u001b[?25h\u001b[?1049l" + ], + [ + 0.000999, + "bash-3.2$ " + ], + [ + 1.58912, + "e" + ], + [ + 0.184114, + "x" + ], + [ + 0.087915, + "i" + ], + [ + 0.103987, + "t" + ], + [ + 0.087613, + "\r\n" + ] + ] +} diff --git a/tests/distros.sh b/tests/distros.sh new file mode 100755 index 0000000..c34d272 --- /dev/null +++ b/tests/distros.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +set -euo pipefail + +readonly DISTROS=( + 'arch' + 'alpine' + 'centos' + 'debian' + 'fedora' + 'ubuntu' +) + +readonly DOCKER='docker' + +# do not redefine builtin `test` +test_() { + local -r tag="${1}" + + local -ra docker_opts=( + "--tag=asciinema/asciinema:${tag}" + "--file=tests/distros/Dockerfile.${tag}" + ) + + printf "\e[1;32mTesting on %s...\e[0m\n\n" "${tag}" + + # shellcheck disable=SC2068 + "${DOCKER}" build ${docker_opts[@]} . + + "${DOCKER}" run --rm -it "asciinema/asciinema:${tag}" tests/integration.sh +} + + +for distro in "${DISTROS[@]}"; do + test_ "${distro}" +done + +printf "\n\e[1;32mAll tests passed.\e[0m\n" diff --git a/tests/distros/Dockerfile.alpine b/tests/distros/Dockerfile.alpine new file mode 100644 index 0000000..9716325 --- /dev/null +++ b/tests/distros/Dockerfile.alpine @@ -0,0 +1,19 @@ +# syntax=docker/dockerfile:1.3 + +FROM docker.io/library/alpine:3.15 + +# https://github.com/actions/runner/issues/241 +RUN apk --no-cache add bash ca-certificates make python3 util-linux + +WORKDIR /usr/src/app + +COPY asciinema/ asciinema/ +COPY tests/ tests/ + +ENV LANG="en_US.utf8" + +USER nobody + +ENTRYPOINT ["/bin/bash"] + +# vim:ft=dockerfile diff --git a/tests/distros/Dockerfile.arch b/tests/distros/Dockerfile.arch new file mode 100644 index 0000000..3224495 --- /dev/null +++ b/tests/distros/Dockerfile.arch @@ -0,0 +1,22 @@ +# syntax=docker/dockerfile:1.3 + +FROM docker.io/library/archlinux:latest + +RUN pacman-key --init \ + && pacman --sync --refresh --sysupgrade --noconfirm make python3 \ + && printf "LANG=en_US.UTF-8\n" > /etc/locale.conf \ + && locale-gen \ + && pacman --sync --clean --clean --noconfirm + +WORKDIR /usr/src/app + +COPY asciinema/ asciinema/ +COPY tests/ tests/ + +ENV LANG="en_US.utf8" + +USER nobody + +ENTRYPOINT ["/bin/bash"] + +# vim:ft=dockerfile diff --git a/tests/distros/Dockerfile.centos b/tests/distros/Dockerfile.centos new file mode 100644 index 0000000..bc4fd7e --- /dev/null +++ b/tests/distros/Dockerfile.centos @@ -0,0 +1,18 @@ +# syntax=docker/dockerfile:1.3 + +FROM docker.io/library/centos:7 + +RUN yum install -y epel-release && yum install -y make python36 && yum clean all + +WORKDIR /usr/src/app + +COPY asciinema/ asciinema/ +COPY tests/ tests/ + +ENV LANG="en_US.utf8" + +USER nobody + +ENTRYPOINT ["/bin/bash"] + +# vim:ft=dockerfile diff --git a/tests/distros/Dockerfile.debian b/tests/distros/Dockerfile.debian new file mode 100644 index 0000000..6c14287 --- /dev/null +++ b/tests/distros/Dockerfile.debian @@ -0,0 +1,33 @@ +# syntax=docker/dockerfile:1.3 + +FROM docker.io/library/debian:bullseye + +ENV DEBIAN_FRONTENT="noninteractive" + +RUN apt-get update \ + && apt-get install -y \ + ca-certificates \ + locales \ + make \ + procps \ + python3 \ + && localedef \ + -i en_US \ + -c \ + -f UTF-8 \ + -A /usr/share/locale/locale.alias \ + en_US.UTF-8 \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /usr/src/app + +COPY asciinema/ asciinema/ +COPY tests/ tests/ + +ENV LANG="en_US.utf8" + +USER nobody + +ENV SHELL="/bin/bash" + +# vim:ft=dockerfile diff --git a/tests/distros/Dockerfile.fedora b/tests/distros/Dockerfile.fedora new file mode 100644 index 0000000..e5abb51 --- /dev/null +++ b/tests/distros/Dockerfile.fedora @@ -0,0 +1,20 @@ +# syntax=docker/dockerfile:1.3 + +# https://medium.com/nttlabs/ubuntu-21-10-and-fedora-35-do-not-work-on-docker-20-10-9-1cd439d9921 +# https://www.mail-archive.com/ubuntu-bugs@lists.ubuntu.com/msg5971024.html +FROM registry.fedoraproject.org/fedora:34 + +RUN dnf install -y make python3 procps && dnf clean all + +WORKDIR /usr/src/app + +COPY asciinema/ asciinema/ +COPY tests/ tests/ + +ENV LANG="en_US.utf8" +ENV SHELL="/bin/bash" + +USER nobody + +ENTRYPOINT ["/bin/bash"] +# vim:ft=dockerfile diff --git a/tests/distros/Dockerfile.ubuntu b/tests/distros/Dockerfile.ubuntu new file mode 100644 index 0000000..38223c2 --- /dev/null +++ b/tests/distros/Dockerfile.ubuntu @@ -0,0 +1,32 @@ +# syntax=docker/dockerfile:1.3 + +FROM docker.io/library/ubuntu:20.04 + +ENV DEBIAN_FRONTENT="noninteractive" + +RUN apt-get update \ + && apt-get install -y \ + ca-certificates \ + locales \ + make \ + python3 \ + && localedef \ + -i en_US \ + -c \ + -f UTF-8 \ + -A /usr/share/locale/locale.alias \ + en_US.UTF-8 \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /usr/src/app + +COPY asciinema/ asciinema/ +COPY tests/ tests/ + +ENV LANG="en_US.utf8" + +USER nobody + +ENTRYPOINT ["/bin/bash"] + +# vim:ft=dockerfile diff --git a/tests/integration.sh b/tests/integration.sh new file mode 100755 index 0000000..9f4f5d1 --- /dev/null +++ b/tests/integration.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash + +set -eExuo pipefail + +if ! command -v "pkill" >/dev/null 2>&1; then + printf "error: pkill not installed\n" + exit 1 +fi + +python3 -V + +ASCIINEMA_CONFIG_HOME="$( + mktemp -d 2>/dev/null || mktemp -d -t asciinema-config-home +)" + +export ASCIINEMA_CONFIG_HOME + +TMP_DATA_DIR="$(mktemp -d 2>/dev/null || mktemp -d -t asciinema-data-dir)" + +trap 'rm -rf ${ASCIINEMA_CONFIG_HOME} ${TMP_DATA_DIR}' EXIT + +asciinema() { + python3 -m asciinema "${@}" +} + +## test help message + +asciinema -h + +## test version command + +asciinema --version + +## test auth command + +asciinema auth + +## test play command + +# asciicast v1 +asciinema play -s 5 tests/demo.json +asciinema play -s 5 -i 0.2 tests/demo.json +# shellcheck disable=SC2002 +cat tests/demo.json | asciinema play -s 5 - + +# asciicast v2 +asciinema play -s 5 tests/demo.cast +asciinema play -s 5 -i 0.2 tests/demo.cast +# shellcheck disable=SC2002 +cat tests/demo.cast | asciinema play -s 5 - + +## test cat command + +# asciicast v1 +asciinema cat tests/demo.json +# shellcheck disable=SC2002 +cat tests/demo.json | asciinema cat - + +# asciicast v2 +asciinema cat tests/demo.cast +# shellcheck disable=SC2002 +cat tests/demo.cast | asciinema cat - + +## test rec command + +# normal program +asciinema rec -c 'bash -c "echo t3st; sleep 2; echo ok"' "${TMP_DATA_DIR}/1a.cast" +grep '"o",' "${TMP_DATA_DIR}/1a.cast" + +# very quickly exiting program +asciinema rec -c whoami "${TMP_DATA_DIR}/1b.cast" +grep '"o",' "${TMP_DATA_DIR}/1b.cast" + +# signal handling +bash -c "sleep 1; pkill -28 -n -f 'm asciinema'" & +asciinema rec -c 'bash -c "echo t3st; sleep 2; echo ok"' "${TMP_DATA_DIR}/2.cast" + +bash -c "sleep 1; pkill -n -f 'bash -c echo t3st'" & +asciinema rec -c 'bash -c "echo t3st; sleep 2; echo ok"' "${TMP_DATA_DIR}/3.cast" + +bash -c "sleep 1; pkill -9 -n -f 'bash -c echo t3st'" & +asciinema rec -c 'bash -c "echo t3st; sleep 2; echo ok"' "${TMP_DATA_DIR}/4.cast" + +# with stdin recording +echo "ls" | asciinema rec --stdin -c 'bash -c "sleep 1"' "${TMP_DATA_DIR}/5.cast" +cat "${TMP_DATA_DIR}/5.cast" +grep '"i", "ls\\n"' "${TMP_DATA_DIR}/5.cast" +grep '"o",' "${TMP_DATA_DIR}/5.cast" + +# raw output recording +asciinema rec --raw -c 'bash -c "echo t3st; sleep 1; echo ok"' "${TMP_DATA_DIR}/6.raw" + +# appending to existing recording +asciinema rec -c 'echo allright!; sleep 0.1' "${TMP_DATA_DIR}/7.cast" +asciinema rec --append -c uptime "${TMP_DATA_DIR}/7.cast" diff --git a/tests/pty_test.py b/tests/pty_test.py new file mode 100644 index 0000000..0f309c7 --- /dev/null +++ b/tests/pty_test.py @@ -0,0 +1,54 @@ +import os +import pty +from typing import Any, List, Union + +import asciinema.pty_ + +from .test_helper import Test + + +class Writer: + def __init__(self) -> None: + self.data: List[Union[float, str]] = [] + + def write_stdout(self, _ts: float, data: Any) -> None: + self.data.append(data) + + def write_stdin(self, ts: float, data: Any) -> None: + raise NotImplementedError + + +class TestRecord(Test): + def setUp(self) -> None: + self.real_os_write = os.write + os.write = self.os_write # type: ignore + + def tearDown(self) -> None: + os.write = self.real_os_write + + def os_write(self, fd: int, data: Any) -> None: + if fd != pty.STDOUT_FILENO: + self.real_os_write(fd, data) + + @staticmethod + def test_record_command_writes_to_stdout() -> None: + writer = Writer() + + command = [ + "python3", + "-c", + ( + "import sys" + "; import time" + "; sys.stdout.write('foo')" + "; sys.stdout.flush()" + "; time.sleep(0.01)" + "; sys.stdout.write('bar')" + ), + ] + + asciinema.pty_.record( + command, {}, writer, lambda: (80, 24), lambda s: None, {} + ) + + assert writer.data == [b"foo", b"bar"] diff --git a/tests/test_helper.py b/tests/test_helper.py new file mode 100644 index 0000000..03b7e97 --- /dev/null +++ b/tests/test_helper.py @@ -0,0 +1,16 @@ +import sys +from codecs import StreamReader +from io import StringIO +from typing import Optional, TextIO, Union + +stdout: Optional[Union[TextIO, StreamReader]] = None + + +class Test: + def setUp(self) -> None: + global stdout # pylint: disable=global-statement + self.real_stdout = sys.stdout + sys.stdout = stdout = StringIO() + + def tearDown(self) -> None: + sys.stdout = self.real_stdout |