Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

Akatsuki Hackers Labは株式会社アカツキが運営しています。

あらゆる人間関係の衝突は、謙虚・尊敬・信頼・カレーの欠如によるものだ

エンジニアリングオフィス(以下EO)の島村です。

先日、アカツキゲームスに所属しているエンジニアを対象にディナー(交流会)を開催したのでレポートをさせていただきます。

開催のきっかけ

もともと、エンジニア組織としては隔週火曜日17~18時に「エンジニア定例MTG」という完全オンラインの全体ミーティングを開催しています。

そこではエンジニア全体への連絡( イベントの告知など)はもちろんですが、外部カンファレンス参加のレポートや、有志により技術共有発表があったりと、エンジニア主導で様々な情報が交換されています。

そこにEOのメンバーより、最近は出社して作業している人も増えてきており、せっかくの集まる機会だからエンジニア定例のあとの時間に横の繋がりを作るエンジニアディナーを開催しようという提案があり、実施することになりました。*1

*1:元々、エンジニアディナーという企画自体は以前より不定期で開催していたのですが、開催するきっかけがなく、半年に1回程度の開催となっていました。

続きを読む

which-keyはいいぞ

こんにちは!エンジニアの宮川です。株式会社アカツキゲームスでクライアントエンジニアをやっています。

このエントリーは Akatsuki Games Advent Calendar 2023 の14日目の記事です。
昨日の記事は「最先端のライフゲーム(Particle Lenia)を作ったので眺める」でした。

qiita.com

コンピュータープログラムから生み出される有機的な挙動にはロマンを感じますね!この手のシミュレーションは永遠に見ていられます。

14日目のこの記事では、私が愛用しているwhich-keyという仕組みを紹介します!

 

時に皆さん、ショートカットキーは使っていますか?既存のショートカットキーをマスターしたり、新しいショートカットキーを登録したりして日々QoLを爆上げしているかと思います。

ですが、ショートカットキーの登録をするにあたってこんな悩みに直面したことはありませんか?

  • 新しくショートカットキーを登録したいのに、既存のショートカットキーやmacosのショートカットキーとバッティングしてしまう
  • ショートカットキーを登録し過ぎて新たに割り当てるキーが枯渇している
  • 一生懸命ショートカットキーを設定しても、後でどのキーに割り当てたかどうか忘れてしまう
  • 同時押しが複雑過ぎて指が混線する

which-keyはそんなあなたの悩みにそっと寄り添ってくれる、新しいショートカットキーの仕組みかもしれません。

which-keyとは

which-keyとは、特定のキー(Spaceキーなど)を基点とした任意長のキーシーケンスにコマンドを割り当てることができる仕組みです。Vimmer向けに言えば、リアルタイムガイド付きLeader-コマンドとも言えます。

イメージとしては、ウィンドウ上部のメニューバーの階層構造をキーボード操作のみで「たどる」ような感じです。

which-keyは、プラグインを入れることでEmacs, Vim/NeoVim, VSCode, Intellij IDEAといったJetbrains製IDEなどで使うことができます。

デモ

言葉だけで説明するのがなかなか難しい概念ですので、早速以下に簡単なデモを見せます。

(埋め込みで画質が荒い場合はYouTubeに移動して見て下さい)

このデモではSpaceキーがwhich-keyのトリガーとなっていて、Spaceを押すと画面下部にメニューが出現します。

メニューに書かれたキーを押す事で、登録されたコマンドを実行するか、もしくは別のメニューに移動することができます。

このデモではwキーに「Window...」メニューが登録されている他、fキーには「Find...」メニュー、gキーには「Goto...」メニュー等が登録されています。

「Window...」メニューには色々なコマンドが登録されていて、例えばvでウィンドウ縦分割、sでウィンドウ横分割、cで分割ウィンドウ削除ができます。また、トップメニューのqにはエディタの終了が登録されていて、これはデモの最後で使用されています。

このように、which-keyではコマンドをメニューの階層構造にして登録することができるのです。

これを「キーシーケンスとコマンドの対応関係」として見てみると次のようになります:

  • <Space>wv → ウィンドウ縦分割
  • <Space>wv → ウィンドウ横分割
  • <Space>wc → 分割ウィンドウ削除
  • <Space>q → エディタ終了

which-keyの特徴

which-keyがどういうものなのかデモをお見せした所で、次はwhich-keyの良いところを特徴ごとに説明していきます:

which-keyの仕組み自体がチートシートの役割を果たしている

同時押しによって発動する一般的なショートカットキーとは違い、which-keyではリアルタイムでメニューウィンドウが表示されます。

これはショートカットキーのチートシートの役割も果たしていて、登録したキーシーケンスを覚えていなくてもその場で登録されているコマンドを確認することができます。

階層構造でコマンドの割当ができるので、好きなだけ登録できる

ワンストロークでキーボードの組み合わせを定義するショートカットキーとは異なり、コマンドを階層構造で登録することができるので、何個でもコマンドを登録しておくことができます。

「which-keyを起動するキーボードショートカット」さえ確保できていれば、既存のキーボードショートカットと衝突することもありません。

キーシーケンスに意味をもたせやすくて覚えやすい

さらに、メニューが階層構造で登録できることの恩恵として、キーシーケンスが比較的覚えやすいという事も挙げられます。

キーボードショートカットの空きが枯渇している場合とかだと「Ctrl + Bは使われてるから隣のGを使うか...」「Ctrl + BもCtrl + Shift + Bも使われてるからAlt + Bにするか...」といったケースがあると思います(私はあります)。そして、そうやって適当に無理やり作ったショートカットキーは割りとすぐに忘れてしまいます。

例えばデモで挙げたようなコマンドは <Space>wvにウィンドウ縦分割、<Space>wc分割ウィンドウ削除が登録されていますが、それぞれ

  • <Space>wv → Window Vertical split
  • <Space>wc → Window Close

といった意味とセットで覚えられます。(こういうのはVimmerには馴染み深いと思います。)

まっさらな状態から作り込める

ショートカットキーには慣例的に定義されているもの(Ctrl-C, Ctrl-Vなど)もあれば、ツールに最初から定義されているものもあり、それらを避けながら自分好みのショートカットキーを構築するのは面倒だったりします。

which-keyにはそういったしがらみは無く、基本的に自分でゼロから作り込むことができます。たいていのwhich-keyプラグインにはデフォルトのシーケンスも用意されているのでそれを使って見るのも良いですが、ゼロから作り込むのも楽しいものです。

which-keyが使える環境

which-keyは元はEmacsのプラグインとして開発されたものなのですが、これにインスパイアされた様々なエディタ向けのwhich-keyプラグインが開発されています。

冒頭で軽く触れましたが、私が知る限りだとEmacsの他にVim/NeoVim, VSCode, Intellij IDEA向けのwhich-keyプラグインが公開されています。せっかくなのでいくつかリンクを貼っておきます:

Emacs向けプラグイン - https://github.com/justbur/emacs-which-key

NeoVim向けプラグイン - https://github.com/folke/which-key.nvim

VSCode向けプラグイン - https://vspacecode.github.io

Jetbrains製IDE向けプラグイン - https://github.com/TheBlob42/idea-which-key

また、NeoVimにはゴリゴリにプラグインを入れまくってパッケージ化されたものがあったりするのですが(LunarVim など)、こういったものにはwhich-keyが標準搭載されていたりします。

shell環境では使えないんですか!?

...と思ったそこのあなた、安心して下さい。

私が作っておきました。

github.com

結構前に作ったもので作りも雑なのですが、一応zsh環境で動くはずです。私は日頃愛用しています。例えば、

git log --graph --decorate --abbrev-commit -5 --all --pretty=short

といったコマンドをいつも <Ctrl-G>gl で実行しています。

実装小話

which-key特有のメニューを実現するための要件として

  • Returnキーを押さなくても即座に入力を受け取れるようにしたい(一般的なCLIアプリケーションとは異なる)
  • できればプロンプトを「荒らす」ようなことはしたくない(which-keyモードを抜けた時に何も残らないで欲しい)

の2つがあったので、Cursesという仕組みを使ってレンダリングを行いました。

CursesはPythonから使うことができ、which-key-shellでもこれを使っています。

docs.python.org

ただshellという環境上、Spaceキーをトリガーにするのは難しく、そこが難点です。

おわりに

Akatsuki Games Advent Calendar 2023 14日目のこの記事では、私が日頃愛用しているwhich-keyという仕組みについて解説を行ったり自作ツールを自慢したりしました。

この記事を通して、マイナーながら強力なwhich-keyというシステムが少しでも広まれば良いなと思います。目指せ脱マウス!!

明日、15日目は竹下さん(通称: ばじくん)の「RubyでTOTPのクライアントを自前実装してみた」です!日頃からお世話になっているTOTPの仕組みがどうなっているのか、とても気になる内容です!

最後に、アカツキゲームスでは一緒に働くエンジニアを募集しています。
カジュアル面談もやっていますので、気軽にご応募ください。
応募はここから!👉 https://herp.careers/v1/aktskgames/requisition-groups/47f46396-e08a-4b2f-8f9b-b2fc79e63b83

DoxygenでC++クラスを分析し、GitHub ActionsでPRコメントする仕組みを作ってみました

こんにちは!

株式会社アカツキゲームスに所属クライアントエンジニアのスーです。 今年の The Game Awards もいっぱい情報が発表されましたね。モンハンの新作、FF7 Rebirthすごく楽しみしています。

この記事は Akatsuki Games Advent Calendar 2023 の12日目の記事です。

この記事では、私が所属するチームで Doxygen を利用して特定のクラスが変更された際に、GitHub Actionsで自動的にPRにコメントする仕組みを作ったの知見を共有できればと思います。

Advent Calendar の11日目は渡辺さんの DiscordのStage Channelに読み上げbotを導入する でした。 自分もDiscordのベビーユーザーなので、サクッとbotの作成する方法について大変勉強になりました。 自分で何かを作って、ツールをより便利にする姿勢はとてもいいですね!👍

では、本編を始めたいと思います!!

なぜこの仕組みを作るか

自分が今所属しているチームでは、日本版だけではなく、他の言語に翻訳して全世界のユーザーにプレイしています。 どの文字列が翻訳されるべきか、今まではエンジニアが機能実装する時に、「手動」で追加しています。 人のてでやるのはやはり限界があります。翻訳文字を網羅できなくて国際版制作の工数が増えたことがありました。

もし翻訳文字の追加作業が自動化できだらいいなーと思って、 コードの差分から翻訳文字を抽出して、PR出す時に自動的にその文字を下にコメントする仕組みを作ってみました! この仕組みをどうやって作るのはをここで紹介できればなと思います!

DoxygenでC++コードを分析する

対象コード

前提として、今回の対象コードは下記のようにレイアウトを示すC++ヘッダーです。 ui::Textui::FormatTextは翻訳対象クラスとして抽出したいです。 クラスの中に他のレイアウトクラスを参照する場合があります。参照されたクラスに翻訳文字が存在した場合、その参照先の文字も抽出したいです。 再帰的にヘッダーを分析する必要があります。

#include "LayoutBase.h"

class LayoutAAA : public LayoutBase
{
public:
    bool init() override;

    ui::Image* getImg();
    ui::Text* getText();
    ui::Marquee* getMarquee();
    LayoutBBB* getBBB();
};

class LayoutBBB : public LayoutBase
{
public:
    bool init() override;

    ui::Text* getText();
};

Doxyfile を作成

Doxygenは、C++、C、Java、Objective-C、Python、IDL (Corba、Microsoft 風)、Fortran、VHDL、PHP、C# 向けのドキュメンテーション・システムです。 コードを分析し、HTML、XMLなどの出力フォーマットにドキュメントを出力するプログラムです。 下記のようなよくみるドキュメントはDoxygenで作成しています。

Cocos2dxのドキュメントもDoxygenで生成されました

今回はDoxygenで特定ディレクトリーに入ってるヘッダーファイルをXMLフォーマットに出力します。Doxyfileというファイルで出力内容を設定します。 下記のコマンドで今のディレクトリーにDoxyfileを生成します。

doxygen -g

デフォルト設定を変更した項目だけ紹介します。詳しくはこちらをご覧ください。

GENERATE_XML = YES               # XMLフォーマットを出力
INPUT = path/to/code             # 対象コードのディレクトリー
OUTPUT_DIRECTORY = doxygen/xml   # 出力ディレクトリー

下記のコマンドでXMLを出力します。

doxygen ./Doxyfile

Git diffで取得した差分ファイルを分析する

Git diffで分析対象コードの差分を抽出します。変更されたファイル名を取得し、ファイルごとに分析します。 今回はRubyでスクリプトで書きました。

Doxygen XMLを読み込むするため、doxyparser という gem を使いました。

まずgit diffでコードの差分を取得します。差分があるファイルのXMLを読み込んで、再帰的に分析します。その中に翻訳対象があれば記録して最後にMarkdown形式で出力します。

▼クリックでコードを表示する

出力はこんな感じです:

## LayoutClassesの翻訳差分
### Added Strings
- LayoutAAA
  - [ ] ui::Text* getText
  - [ ] ui::Marquee* getMarquee
  - LayoutBBB
    - [ ] ui::Text* getText

### Removed Strings
- LayoutCCC
  - [ ] ui::Marquee* getMarquee

あとはその結果をPRにコメントすれば...!

GitHub Actionsで翻訳差分をPRにコメントする

対象ディレクトリーが変更があった場合、上記のスクリプトを実行し、その結果をPRにコメントするGitHub Actions workflowで実装しました。

ファイル変更は tj-actions/changed-files で取得します。 前回のプッシュからの差分だけ取得する場合、checkoutのfetch_depth を 0 に設定(超大事)して、since_last_remote_commit: true にすればOKです。

steps:
  - name Checkout Repo
     uses: actions/checkout@v4
     with:
       fetch-depth: 0

  - name: Get changed files
     id: changed-files
     uses: tj-actions/changed-files@v40
     with:
       since_last_remote_commit: true

GitHub CLIを利用してPRにコメントします。下記の処理を実行する前にGitHub CLIのインストールは必要です。 gh auth login しないとPRにコメントする権限がないので必須処理です。

  - name: Comment on pull request
     if: steps.changed-files.outputs.any_modified == 'true'
     env:
       OWNER: ${{ github.repository_owner }}
       REPO: ${{ github.event.repository.name }}
      run: |
        echo '${{ secrets.GITHUB_TOKEN }}' | gh auth login --with-token
        gh pr comment ${{ github.event.number }} --repo ${OWNER}/${REPO} -F ./output.txt

▼クリックでフルコードを表示する gist.github.com

結果

対象ディレクトリーに入ってるファイルを適切に変更して commit & push すると、下記のようなコメントが届きます! 🥳🥳🥳

github-actions君が翻訳差分をPRにコメントしてくれた!ありがとう!

まとめ

Doxygenを使用してC++コードを分析して、GitHub ActionsでPRにコメントする実装の知見とシェアしました。

はじめてRubyのカスタマイズクラスの Set を実装しました。Github Actionsの書き方やGitHub CLIの使い方もとても勉強になりました。Doxygenの他の活用法も考えたいですね〜

皆さんも、もしCIでの特定な差分内容を出力してコメントしたいなら、この記事を読んで、少しでも力になれたのなら嬉しいです。

明日の記事はEito Shimomuraさんの記事です。お楽しみに〜

最後まで読んでいただきありがとうございました!!!

AWS Device Farm の adb プロセスを Airtest に認識してもらうための方法

この記事は Akatsuki Games Advent Calendar 2023 10日目の記事です。

昨日は Yusuke Nakajima さんの UnityのProjectWindowに表示履歴機能を付けるエディタ拡張 でした。表示履歴機能があるだけで開発中のストレスが軽減されますね。導入もシンプルにしてくれているのがとても良いと思いました!

zenn.dev

はじめに

QAエンジニアの山﨑@tomo_tk11です。私のチームでは「ゲーム開発を行うプロジェクトメンバーと連携しながら、QA コストや QA リードタイムの最適化」を行っています。 現在はマスターデータのチェッキング作業の効率化や、Airtest と AWS Device Farm(以下、Device Farm) を用いてリグレッションテストなどの自動化に取り組んでいます。

今回は、使用している Airtest のアップデートを行った際に Device Farm で動作しなくなったため、その原因と解決策をまとめました。 Airtest と Device Farm の自動テストシステムで過去に出した資料を合わせて読むことで理解が深まると思います。最後に載せておくのでよければご覧ください。

TL;DR

  • デバイスを指定する uri に serial_noadb_path を入力しよう
# OK
auto_setup(f"Android:///{serial_no}?adb_path={adb_path}")

# NG
auto_setup(f"Android:///")

目次

Airtest アップデートのきっかけ

今回は Airtest v1.3.0 から v1.3.1 へのアップデートを行いました。 最近のAirtest は2ヶ月くらいの間隔でバージョンアップされています。 アップデート内容もかなり積極的でとても助かっています。

  • 最近のアップデートで嬉しかったもの
    • デバイスの画面録画機能で cv2, ffmpeg サポート
    • iOS の自動テストサポートの拡張(今までは一癖ありました)

そして、v1.3.1 では adb に関する気になるアップデートがありました。

adb will now give priority to using the current adb process, or the system variable is set to adb in ANDROID_HOME. If neither is found, adb in airtest will be used.

Release v1.3.1 · AirtestProject/Airtest · GitHub

Device Farm では adb プロセスが既に立ち上がっており、その adb と Airtest に内蔵されている adb が競合するという問題が以前ありました。なので、私たちのシステムではひと工夫しています。

Airtestの FAQ によると、競合した際はpackage内のadbを任意のものに差し替えるとのことです

なのでtestspecのpre_testでinstallしたAirtest package内のadbをDevice Farmのものに差し替えます

hackerslab.aktsk.jp

v1.3.1 のアップデートでそのひと工夫が不要になるのではないかと思い、さっそく自動テストを動かしてみました。

Device Farm 上でテスト実行すると、 ADB Error が発生する

AdbError が発生し、テストスクリプトの実行にすら行きつけませんでした。

airtest.core.error.AdbError: stdout[] stderr[/bin/bash: devices: No such file or directory

原因

このエラーは、Airtest の adb に関する仕様変更による影響です。Device Farm では常に adb プロセスが動作しているという話をしましたが、実際には /bin/bash/opt/dev/android-sdk-linux/platform-tools/adb.orig の2つの実行ファイルが adb を名乗って動いています。 Device Farm は adb の機能に一部制限を加えるために元々の adb バイナリファイルを adb.orig とし、ラッパースクリプト(bash ファイル)を adb として実行しています。今回のアップデートによって Airtest が Device Farm の adb ラッパーの仕組みを吸収できず、adb パスを認識しなくなったためエラーが発生しています。

ちょっとややこしいので深掘りします。

なぜエラーが起きるようになったのか?

Airtest の adb パス決定ロジックが追加されたから

adb will now give priority to using the current adb process, or the system variable is set to adb in ANDROID_HOME. If neither is found, adb in airtest will be used.It also supports directly specifying adb_path, for example:

from airtest.core.android.android import Android, ADB
adb = ADB(adb_path=r"D:\adb\adb.exe")
dev = Android(serialno="5TSSMVBYUSEQNRY5", adb_path=r"D:\test\adb41\adb.exe")

Release v1.3.1 · AirtestProject/Airtest · GitHub

v1.3.1 から以下の優先度で adb パスが自動的に決定されるようになりました。

  1. 現在動いている adb プロセス のパス
  2. 環境変数 ANDROID_HOME で指定されているディレクトリ内の adb のパス
  3. Airtest に内蔵されている adb のパス

また、手動で adb パスを与えることもできます。

どうやらこのロジックが Device Farm の adb ラッパーの仕組みとうまく噛み合わず、エラーが起きるようになってしまったようです。

Airtest の adb パス決定ロジックを詳しく調査する

まず、Device Farm の adb ラッパーの仕組みについて見てみます。Device Farm 上で which adb を実行するとこのように返ってきます。

[DeviceFarm] which adb
/opt/dev/android-sdk-linux/platform-tools/adb

ちなみに、Device Farm では $ANDROID_HOME は /opt/dev/android-sdk-linux で定義されています。

また、Device Farm では adb に関して、以下の2プロセスが動いています。

{'exe': '/bin/bash', 'name': 'adb'}
{'exe': '/opt/dev/android-sdk-linux/platform-tools/adb.orig', 'name': 'adb.orig'}

adb は Device Farm 側で用意された adb のラッパースクリプト、 adb.orig はオリジナルの adb バイナリファイルです。 adb/bin/bash が実行していますが、実体は /opt/dev/android-sdk-linux/platform-tools/ に存在しています。

次に、Airtest の adb パスを探しに行くコードを見てみます。

@staticmethod
def get_adb_path():
    if platform.system() == "Windows":
        ADB_NAME = "adb.exe"
    else:
        ADB_NAME = "adb"
 
    # Check if adb process is already running
    for process in psutil.process_iter(['name', 'exe']):
        if process.info['name'] == ADB_NAME:
            return process.info['exe']

Airtest/airtest/core/android/adb.py at v1.3.1 · AirtestProject/Airtest · GitHub

このロジックだと name が adb のプロセスを探しに行きますが、Device Farm 上の adb スクリプトは bash のラッパースクリプトのため、psutil で取得される実行コマンドのパスは /bin/bash となってしまいます。 しかし、そのパスには adb の実体が存在しないので、Device Farm 上の Airtest では adb をうまく認識できない、という結果になっていました。 ADB_NAME = "adb.orig" とすることで adb は実行できるようになると思いますが、得られる adb パスは オリジナルの adb.orig ファイルを示すのでラッパースクリプトを実行することができません。また、 Airtest のソースコードにパッチを当てることになり保守性が高くなってしまうため、それは選択しませんでした。

Airtest 上でエラーが起きている箇所はどこか?

エラーは、 airtest.core.api の init_device を呼び出したときに発生します。

def init_device(platform="Android", uuid=None, **kwargs):
    """
    Initialize device if not yet, and set as current device.
 
    :param platform: Android, IOS or Windows
    :param uuid: uuid for target device, e.g. serialno for Android, handle for Windows, uuid for iOS
    :param kwargs: Optional platform specific keyword args, e.g. `cap_method=JAVACAP` for Android
    :return: device instance
    :Example:
 
        >>> init_device(platform="Android",uuid="SJE5T17B17", cap_method="JAVACAP")
        >>> init_device(platform="Windows",uuid="123456")
    """
    cls = import_device_cls(platform)
    dev = cls(uuid, **kwargs)
    # Add device instance in G and set as current device.
    G.add_device(dev)
    return dev

Airtest/airtest/core/api.py at v1.3.1 · AirtestProject/Airtest · GitHub

init_device は 自動テストの初期設定を行う auto_setup や デバイスの接続を行う connect_device に device の uri を指定したときに呼び出されます。

uri は "Android:///""Android:///SJE5T17B17?cap_method=javacap&touch_method=adb" のような形で表現されており、デバイスの OS と OS 固有のパラメータを入力するために指定します。

init_device では、各 OS ごとのクラスからインスタンスが生成されます。 今回の場合、Android デバイスを使っているので、 Android クラスのインスタンスが生成されることになります。

次のコードは Android クラスの init の一部です。

class Android(Device):
    """Android Device Class"""
 
    def __init__(self, serialno=None, host=None,
                 cap_method=CAP_METHOD.MINICAP,
                 touch_method=TOUCH_METHOD.MINITOUCH,
                 ime_method=IME_METHOD.YOSEMITEIME,
                 ori_method=ORI_METHOD.MINICAP,
                 display_id=None,
                 input_event=None,
                 adb_path=None,
                 name=None):
        super(Android, self).__init__()
        self.serialno = serialno or self.get_default_device()
        self._uuid = name or self.serialno
        self._cap_method = cap_method.upper()
        self._touch_method = touch_method.upper()
        self.ime_method = ime_method.upper()
        self.ori_method = ori_method.upper()
        self.display_id = display_id
        self.input_event = input_event
        # init adb
        self.adb = ADB(self.serialno, adb_path=adb_path, server_addr=host, display_id=self.display_id, input_event=self.input_event)
        self.adb.wait_for_device()

Airtest/airtest/core/android/android.py at v1.3.1 · AirtestProject/Airtest · GitHub

そして、エラーが発生している箇所は self.serialno = serialno or self.get_default_device() の部分です。 今回、接続しているデバイスは1デバイスのみでしたので、初期設定に渡す uri は "Android:///" だけ設定していました。 そのため、serialno は None であり、デバイスを特定するために get_default_device が呼ばれていました。

def get_default_device(self, adb_path=None):
    """
    Get local default device when no serialno
 
    Returns:
        local device serialno
 
    """
    if not ADB(adb_path=adb_path).devices(state="device"):
        raise IndexError("ADB devices not found")
    return ADB(adb_path=adb_path).devices(state="device")[0][0]

Airtest/airtest/core/android/android.py at v1.3.1 · AirtestProject/Airtest · GitHub

ここでは、要は adb devices を実行し、マシンに接続されているデバイス一覧を取得し、その一番上のデバイスを接続先として指定しています。 ここで問題なのは、 get_default_device の呼び出し元で adb_path を指定しないまま実行していることです。

ADB クラスに adb_path = None を渡すとどうなるか?

このコードは ADB クラスの init の一部です。

class ADB(object):
    """adb client object class"""
 
    _instances = []
    status_device = "device"
    status_offline = "offline"
    SHELL_ENCODING = "utf-8"
 
    def __init__(self, serialno=None, adb_path=None, server_addr=None, display_id=None, input_event=None):
        self.serialno = serialno
        self.adb_path = adb_path or self.get_adb_path()
        self.display_id = display_id

Airtest/airtest/core/android/adb.py at v1.3.1 · AirtestProject/Airtest · GitHub

adb_pash = None だった場合は、 self.get_adb_path() が実行されます。 そして、この関数内で、「すでに動作している adb プロセスの実行パス」である /bin/bash が取得されてしまいます。

ここで、冒頭の「Airtest の adb パス決定ロジック」に戻ります。

@staticmethod
def get_adb_path():
    if platform.system() == "Windows":
        ADB_NAME = "adb.exe"
    else:
        ADB_NAME = "adb"
 
    # Check if adb process is already running
    for process in psutil.process_iter(['name', 'exe']):
        if process.info['name'] == ADB_NAME:
            return process.info['exe']

今回のアップデートで adb_path を外から入力し指定できるようになっていますが、デバイスの初期設定のタイミング(init_device)で serialno = None だった場合は、 adb_path を指定することができません。

解決策

デバイス初期化時の uri を正確に書く

auto_setupconnect_device に対して、 "Android:///" だけを渡していたことが今回の一番の問題でした。 serialno さえあれば、接続するデバイスは一意に特定できるのです。更に、uri に adb_path パラメータを含めれば、Android クラスに adb_path を渡すことができるので、 Android クラスを介して呼び出される adb は指定した adb となります。つまり、以下のような uri にすることで Device Farm の adb_path を指定して Android デバイスに接続することができます。

auto_setup(f"Android:///{serial_no}?adb_path={adb_path}")

肝心の serial_noadb_path は テスト環境を構築するための testspec.yaml(Device Farm の環境構築ファイル) で adb deviceswhich adb を使えば取得可能なので、それを環境変数かなにかで設定しておけばホストマシンからテストスクリプトである Python 側に渡すことができます。

v1.3.1 のアップデートで不要となった処理

Airtest に内蔵されている adb をホストマシンのものに差し替える対応

冒頭でも述べましたが、 v1.3.1 以前では、 Airtest に含まれている adb とシステム側の adb が競合した場合は差し替える対応を行う必要でした。 しかし、今回から任意の adb を用いる場合は adb_path を指定する方式に変更されたので、差し替え対応が不要となりました。

従って、差し替えを行っているコードを削除しても正常に動作することが確認できました。

まとめ

adb 周りのアップデートがあった Airtest v1.3.1 へのアップデートを試みましたが、 Device Farm の adb ラッパーの仕組みと競合した結果、ADB Error が発生しました。Airtest のアップデートにより追加された adb パスを良い感じに探す仕組みでは、 Device Farm の adb.orig を動かすプロセスを探しきれていませんでした。その問題は、Airtest のデバイス初期化時に Device Farm のデバイスの serial_no と、adb プロセスの adb_path を指定することで解決しました。そうすることで、Airtest は接続しているデバイスを始めから認識できるようになり、「デバイスを探す」動作と、そのための「adb パスを探す」動作を行う必要がなくなります。

今後も自動テストの取り組みは続けていくので、興味深い内容があれば記事にしたいと思います。この記事がゲームアプリケーションのテスト自動化に取り組まれる方の助けになれば嬉しいです。

明日は Koki Watanabe さんの記事です。お楽しみに!

参考資料

システムの全体像が分かる資料

speakerdeck.com

システムの詳細が分かる資料

hackerslab.aktsk.jp

hackerslab.aktsk.jp

hackerslab.aktsk.jp

Terraform で CloudRun + Identity-Aware Proxy をやっていく 2023

こんにちは。

株式会社アカツキゲームスで ATLAS というチームに所属してゲーム内通貨管理基盤を開発及び運用しています、なかひこくん (@takanakahiko) です。 最近やっと CB250R が納車されましたが、寒すぎて全然乗れていません。気候よ、早く暖かくなりなさい。

この記事は Akatsuki Games Advent Calendar 2023 の7日目の記事です。

この記事では、私が所属するチームで Terraform を利用し Identity-Aware Proxy を導入した際の知見を共有できればと思います。 今回紹介する知見は、インターン生と一緒に取り組んだタスクで得られた内容です。この場をお借り致しましてお礼申し上げます。

続きを読む