🤪

GitHubActionsでクリップボードに対するテストを書く

2021/12/05に公開

こんにちは。こんばんは。おはようございます。LAPASでエンジニアだったりDBのお守りだったりをしている denzow です。この記事はLAPRAS Advent Calendar 2021 5日目の記事です。

最近はredasql という redash に対してCLI経由でSQLを実行するツールを作っていました。その際のunittestをGitHub Actionsで実行しようとしたときに嵌った点についてあまり情報がなかったのでまとめておきます。

サンプルの説明

今回は、クリップボード周りの確認だけしたいので最小構成のアプリを用意しました。

command.py
import argparse
from pyperclip import copy


def init():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '-t',
        '--text',
        help='text for clipboard',
        required=True,
    )
    args = parser.parse_args()
    return args.text


def main(text_for_clipboard: str):
    copy(text_for_clipboard)
    print(f'copied: {text_for_clipboard}')


if __name__ == '__main__':
    text_for_clipboard = init()
    main(text_for_clipboard)

こんな感じで-t コピーしたい文字列 として実行するとクリップボードに文字列が入ります。

(sample39) denzow@denpad$ poetry run python command.py  -t hello
copied: hello

クリップボード周りはpyperclip を使っています。

そしてこちらに対する雑なテストコードです。

tests/test_command.py
from unittest import TestCase
from pyperclip import paste

import command


class MainTest(TestCase):

    def test_main(self):
        """
        クリップボードに正常にコピーされるかのテスト
        :return: 
        """
        expected_text = 'copy_target'
        command.main(expected_text)
        self.assertEqual(expected_text, paste())

ローカルでは正常にテストも実行できています。

(sample39) denzow@denpad$ poetry run python -m unittest -vvv
test_main (tests.test_command.MainTest)
クリップボードに正常にコピーされるかのテスト ... copied: copy_target
ok

----------------------------------------------------------------------
Ran 1 test in 0.006s

OK

これを題材に見ていきます。

GitHubActionsでのunittest実行

とりあえず何も考えずに適当にpoetryのcacheとか含めながらunittestを登録するとこんな感じになると思います。

.github/workflows/unittest.yml
name: unittest

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  unittest:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Install Python 3
        uses: actions/setup-python@v1
        with:
          python-version: 3.9
      - name: Install poetry
        uses: snok/install-poetry@v1.0.0
        with:
          virtualenvs-create: true
          virtualenvs-in-project: true
      - name: Load cached venv
        id: cached-poetry-dependencies
        uses: actions/cache@v2
        with:
          path: .venv
          key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
      - name: Install dependencies
        run: poetry install
        if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
      - name: unit test
        run: |
          poetry run python -m unittest discover -s tests/

さくっと実行するとさくっとに死にます。

======================================================================
ERROR: test_main (test_command.MainTest)
クリップボードに正常にコピーされるかのテスト
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/runner/work/github-actions-clipboard-sample/github-actions-clipboard-sample/tests/test_command.py", line 15, in test_main
    command.main(expected_text)
  File "/home/runner/work/github-actions-clipboard-sample/github-actions-clipboard-sample/command.py", line 18, in main
    copy(text_for_clipboard)
  File "/home/runner/work/github-actions-clipboard-sample/github-actions-clipboard-sample/.venv/lib/python3.9/site-packages/pyperclip/__init__.py", line 659, in lazy_load_stub_copy
    return copy(text)
  File "/home/runner/work/github-actions-clipboard-sample/github-actions-clipboard-sample/.venv/lib/python3.9/site-packages/pyperclip/__init__.py", line 336, in __call__
    raise PyperclipException(EXCEPT_MSG)
pyperclip.PyperclipException: 
    Pyperclip could not find a copy/paste mechanism for your system.
    For more information, please visit https://pyperclip.readthedocs.io/en/latest/index.html#not-implemented-error 

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

Pyperclipが期待しているOS側のパッケージが足りてないとのことです。提示されているリンクを見ればわかりますがxselxclipが必要のようです。ソースではこの辺りです。

https://github.com/asweigart/pyperclip/blob/781603ea491eefce3b58f4f203bf748dbf9ff003/src/pyperclip/init.py#L576-L581

クリップボードに触れるコマンドラインツールを順に試して有るやつを使うって感じの実装ですね。とりあえず最初に試しているxselを先にインストールするようにします。

--- a/.github/workflows/unittest.yml
+++ b/.github/workflows/unittest.yml
@@ -17,6 +17,9 @@ jobs:
         uses: actions/setup-python@v1
         with:
           python-version: 3.9
+      - name: Install os packages
+        run: |
+          sudo apt-get install -y xsel
       - name: Install poetry
         uses: snok/install-poetry@v1.0.0
         with:

しかしこれでは結局同じエラーで失敗します。xselxclipを使うためにはDISPLAYが必要ですがGitHubActionsで実行される環境には出力される画面が存在しないため結局利用できていません。
そこで仮想ディスプレイを作ってくれるxvfbを利用します。xvfb自体は仮想のXWindowを構築したりスクリーンショットをとったりと色々なことができますが、今回必要な部分の話だけであればxvfb-run 経由で仮想ディスプレイを利用させたいコマンドを実行させるだけです。

--- a/.github/workflows/unittest.yml
+++ b/.github/workflows/unittest.yml
@@ -19,7 +19,7 @@ jobs:
           python-version: 3.9
       - name: Install os packages
         run: |
-          sudo apt-get install -y xsel
+          sudo apt-get install -y xsel xvfb
       - name: Install poetry
         uses: snok/install-poetry@v1.0.0
         with:
@@ -36,4 +36,4 @@ jobs:
         if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
       - name: unit test
         run: |
-          poetry run python -m unittest discover -s tests/
+          xvfb-run poetry run python -m unittest discover -s tests/

これで無事にGitHubActions経由でもクリップボードのテストが出来るようになりました。

まとめ

長々と書きましたが、xselxvfb使えってだけの話でした。ただ、踏んだときにいい感じに情報を見つけられなかったので誰かの一助になれば幸いです。
合わせて今回のサンプルリポジトリを以下に公開しておきます。

https://github.com/denzow/github-actions-clipboard-sample

Discussion