はじめに
みなさん、競技プログラミングは楽しんでますでしょうか。
この記事では、AtCoder のテストケースを Dropbox から自動的にダウンロードし、競技プログラミング用のライブラリを検証するための GitHub Actions のワークフローを構築する手順を紹介します。
取り扱うこと
- AtCoder の問題で verify を実行する方法
取り扱わないこと
- online-judge-tools を使ったライブラリの実装
- Aizu Online Judge や Yosupo Judge の問題で自動テストを実行する方法
1. Dropbox アカウントとアプリの作成
まず手順に従って Dropbox アカウントを作成します。
その後、Dropbox Developers の Create App
からアプリを作成します。
2. Dropbox API を使用した OAuth2 認証フロー
App key と App secret の取得
アプリを作成したらまずは Settings
タブから App key
と App secret
を保管します。
この値は他者に知られないように保管してください。
スコープの変更
次に Permissions
タブからスコープを変更します。
次の項目にチェックを入れ、変更を保存します。
OAuth2 認証フロー
そしてアクセスコードを取得するために次のリンクにアクセスしてください。$APP_KEY
は先ほど保管した App Key
に置き換えてください。認証を許可して次の画面に表示されるアクセスコードを保管します。
3. リフレッシュトークンの取得と使用
リフレッシュトークンの取得
リフレッシュトークンを取得するために、今まで保管した
-
$APP_KEY
(App key
) -
$APP_SECRET
(App secret
) -
$ACCESS_CODE
(アクセスコード)
を使用して次の curl
コマンドを実行します。
$ curl https://api.dropbox.com/oauth2/token \
-d code=$ACCESS_CODE \
-d grant_type=authorization_code \
-u $APP_KEY:$APP_SECRET
{
"access_token": ...,
"token_type": "bearer",
"expires_in": 14400,
"refresh_token": ...,
"scope": "account_info.read files.metadata.read sharing.read",
"uid": ...,
"account_id": ...
}
これでリフレッシュトークンが取得できます。
このリフレッシュトークンの役割は、アクセストークンの有効期限が切れた後に、新しいアクセストークンを取得するために使われるものです。Dropbox API のアクセストークンの有効期限は14400秒と短いため、継続的に API を使うためにはリフレッシュトークンを経由する必要があります。
リフレッシュトークンの使用
次のコマンドでリフレッシュトークンを使用してアクセストークンを取得することができます。適宜 $REFRESH_TOKEN
を先ほど取得したリフレッシュトークンに置き換えてください。
$ curl https://api.dropbox.com/oauth2/token \
-d grant_type=refresh_token \
-d refresh_token=$REFRESH_TOKEN \
-u $APP_KEY:$APP_SECRET
{
"access_token": ...,
"token_type": "bearer",
"expires_in": 14400
}
この処理をこれから GitHub Actions
のワークフローに組み込んでいきます。
4. GitHub Actions ワークフローの作成
GitHub Secrets の設定
まずは、GitHub の Secrets に先ほど取得した Dropbox のトークンを登録します。
GitHub のリポジトリ設定ページに移動し、
左メニューから「Secrets and variables」の「Actions」を選択します。
「New repository secret」ボタンをクリックし、
先ほど取得した Dropbox の App key
、App secret
、リフレッシュトークンを登録します(名前は DROPBOX_APP_KEY
など)
次のように登録されていれば設定完了です。
ワークフロー設定ファイルの設定
次にワークフローの設定ファイルを編集します。
name: verify
on: push
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up Python
uses: actions/setup-python@v1
- name: Install dependencies
run: pip3 install -U online-judge-verify-helper
+ - name: Refresh Dropbox Token
+ run: |
+ response=$(curl -X POST https://api.dropbox.com/oauth2/token \
+ -d grant_type=refresh_token \
+ -d refresh_token=${{ secrets.DROPBOX_REFRESH_TOKEN }} \
+ -u "${{ secrets.DROPBOX_APP_KEY }}:${{ secrets.DROPBOX_APP_SECRET }}" \
+ -H "Content-Type: application/x-www-form-urlencoded")
+ ACCESS_TOKEN=$(echo $response | jq -r '.access_token')
+ echo "::add-mask::$ACCESS_TOKEN"
+ echo "DROPBOX_TOKEN=$ACCESS_TOKEN" >> $GITHUB_ENV
- name: Run tests
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
YUKICODER_TOKEN: ${{ secrets.YUKICODER_TOKEN }}
+ DROPBOX_TOKEN: ${{ env.DROPBOX_TOKEN }}
GH_PAT: ${{ secrets.GH_PAT }}
run: oj-verify all
先ほど行ったアクセストークンを取得する操作を追加して、GitHub の環境変数にアクセストークンを追加しています。
各操作の説明をします。
run: |
response=$(curl -X POST https://api.dropbox.com/oauth2/token \
-d grant_type=refresh_token \
-d refresh_token=${{ secrets.DROPBOX_REFRESH_TOKEN }} \
-u "${{ secrets.DROPBOX_APP_KEY }}:${{ secrets.DROPBOX_APP_SECRET }}" \
-H "Content-Type: application/x-www-form-urlencoded")
ACCESS_TOKEN=$(echo $response | jq -r '.access_token')
curl
コマンドでアクセストークンを含んだ json 形式のデータを取得、それに対してjq
コマンドでアクセストークンだけ抜き出し、$ACCESS_TOKEN
という変数に代入します。
echo "::add-mask::$ACCESS_TOKEN"
ここで $ACCESS_TOKEN
に ::add-mask::
をすることにより、この文字列がログに流れることを防ぎます。
echo "DROPBOX_TOKEN=$ACCESS_TOKEN" >> $GITHUB_ENV
$GITHUB_ENV
に $ACCESS_TOKEN
を追加します。これでこの後のステップで env.DROPBOX_TOKEN
を使うことが可能になります。
5. 実際に試してみる
ここまで作ったワークフローを実際に使って自動テストを試してみます。
今回はトポロジカルソートのライブラリを検証するため、ABC223のD問題を扱います。
まずはライブラリです。
#pragma once
#include <vector>
#include <queue>
#include "graph_template.hpp"
/**
* @brief Topological Sort (トポロジカルソート)
*/
template<class T> std::vector<int> topological_sort(Graph<T> &graph) {
int n = (int)(graph.size());
std::vector<int> res, indeg(n, 0);
for (int i = 0; i < n; i++) {
for (auto c: graph[i]) {
indeg[c]++;
}
}
std::priority_queue<int, std::vector<int>, std::greater<int>> que;
for (int i = 0; i < n; i++) {
if (indeg[i] == 0) que.push(i);
}
while (!que.empty()) {
auto v = que.top(); que.pop();
res.push_back(v);
for (auto c: graph[v]) {
indeg[c]--;
assert(indeg[c] >= 0);
if (indeg[c] == 0) que.push(c);
}
}
return res;
}
次にテスト用コードです。一行目に #define PROBLEM "https://atcoder.jp/contests/abc223/tasks/abc223_d"
と記述し、テストに使う問題を明示しておきます。
#define PROBLEM "https://atcoder.jp/contests/abc223/tasks/abc223_d"
#include <bits/stdc++.h>
#include "graph/graph_template.hpp"
#include "graph/topological_sort.hpp"
using namespace std;
typedef long long int ll;
#define rep(i, N) for(int i = 0; i < (int)N; i++)
int main() {
ll N, M; cin >> N >> M;
Graph<ll> graph(N);
rep (i, M) {
ll a, b; cin >> a >> b; a--; b--;
graph[a].push_back(b);
}
auto V = topological_sort(graph);
if ((int)(V.size()) != N) {
cout << -1 << endl;
return 0;
}
rep (i, N) {
if (i == 0) cout << V[i] + 1;
else cout << ' ' << V[i] + 1;
}
cout << endl;
return 0;
}
このコードをリモートブランチにプッシュして、
問題なくテストできました!アクセストークンも --dropbox-token ***
となっており、伏せられていることがわかります。
おわりに
この記事では、Dropbox API と GitHub Actions を活用し、AtCoder のテストケースを自動的にダウンロードして競技プログラミング用ライブラリをテストするワークフローの構築手順を紹介しました。これを導入することで、より幅広い問題でテストをすることができ、競技プログラミングのライブラリ整備がよりスムーズに進むようになると思います。
私自身もまだ始めたばかりですが、この自動化の一歩を踏み出すことで、より快適な自作ライブラリライフを一緒に送りましょう!
参考にさせていただいた記事