IronHSP は HSP3 スクリプトの自動テストを書くための専用ランタイムとユーティリティを提供します。 エラー時にモーダルダイアログが出てプロセスが固まる、画面表示をテストで取り出せないといった 従来の HSP の課題を、以下のコンポーネントを組み合わせて解決します。
自動テストは次の 4 コンポーネントを組み合わせて行います:
| コンポーネント | 役割 | 配置 |
|---|---|---|
hsp3_net_test.exehsp3cl_net_test.exe(+ _64 版) |
テスト用 HSP3 ランタイム。エラー発生時にダイアログを出さず、 stderr に JSON 1 行イベントとして状況を流して非ゼロ exit する。 |
hsp3net/Release_test/ ほか |
hsptestrt.dllhsptestrt_64.dll |
テストスクリプトから呼び出す assert / expect / screenshot などのプリミティブ。 結果を stderr へ NDJSON 形式で書き込む。 |
package/win32/ |
iron_test_ex.hsp |
hsptestrt.dll を書きやすくラップした DSL マクロ集。 | package/win32/common/ |
iron_test_runner.exe |
複数の .hsp をまとめて実行し、結果を集計して色付きサマリ & JSON を出力する集約ランナー。 | package/win32/ |
┌─────────────┐ ┌─────────────────────┐
│ test_*.hsp │ │ iron_test_runner.exe │
│ (テストスクリプト) │──────────→│ │
└─────────────┘ │ (hspcmp64 → .ax → │
│ hsp3cl_net_test_64) │
│ × N 本 │
│ │
│ stderr NDJSON 集計 │
└──────────┬────────────┘
│
┌────────────┴───────────┐
│ │
▼ ▼
ターミナル色付きサマリ --json=file で機械可読出力
hsp3net 系の 4 つのランタイム (GUI / CL × Win32 / x64) を HSP_TEST_MODE
プリプロセッサ付きでビルドした版です。ソースコードは本体と完全に共有しており、
#ifdef HSP_TEST_MODE ガードだけで以下の振る舞いが変わります:
dialog 命令を抑止し、OK 応答を自動で返す (テストがブロックされない)Alert() / Alertf() などの内部アラートも stderr へstop 命令による Press any key 待ちをスキップ| ファイル | 対応ランタイム | ビットサイズ |
|---|---|---|
hsp3_net_test.exe | GUI (hsp3.exe 系) | Win32 |
hsp3_net_test_64.exe | GUI (hsp3_64.exe 系) | x64 |
hsp3cl_net_test.exe | CL (hsp3cl.exe 系) | Win32 |
hsp3cl_net_test_64.exe | CL (hsp3cl_64.exe 系) | x64 |
hsp3cl_net_test_64.exe を基本とします。
rem 通常のコンパイル (デバッグ情報付きが必須) hspcmp64 -i -u -d -o test.ax test.hsp rem テストランタイムで実行 hsp3cl_net_test_64 test.ax 2> events.log echo exit=%errorlevel%
エラーが起きた場合、events.log に以下のようなイベントが残ります:
{"time":"2026-04-18T17:31:50","type":"start","exe":"...hsp3cl_net_test_64.exe","cmdline":"test.ax"}
{"time":"2026-04-18T17:31:50","type":"error","code":19,"line":5,"file":"test.hsp","message":"0で除算しました"}
{"time":"2026-04-18T17:31:50","type":"end","exit":1}
line フィールドは hspcmp64 -d (デバッグ情報付き) でコンパイルしたときのみ出力されます。
-d を付けないとエラーの行番号が拾えません。テスト用途では必ず付けてください。
テストスクリプトから呼び出す primitive のセットです。
各関数は stderr に直接 WriteFile(STD_ERROR_HANDLE, ...) で JSON を書き込むので、
テストランタイム以外 (通常の hsp3.exe など) でも動作します。
ただし stderr が繋がっていない場合は黙って no-op になります。
#include "hsp3cl_net_64.as" ; 64bit 用プリプロセッサ識別子 _HSP64 を定義 #include "hsptestrt.as" ; hsptestrt.dll を #uselib
| コマンド | 説明 |
|---|---|
testrt_begin "name" | テストケース開始。別ケース動作中なら暗黙に testrt_end する。 |
testrt_end | 現在のケースを終了し、pass/fail 件数を test_end イベントに出力。 |
testrt_summary() (#cfunc) | 全体集計を test_summary イベントに出し、失敗件数を int で返す。end testrt_summary() とすれば失敗件数=プロセス終了コード。 |
testrt_pass_count() | 累計 pass 件数を返す (int)。 |
testrt_fail_count() | 累計 fail 件数を返す (int)。 |
| コマンド | 説明 |
|---|---|
testrt_assert cond, "msg" | cond が 0 なら fail。 |
testrt_expect_true cond, "msg" | cond が真なら pass。 |
testrt_expect_false cond, "msg" | cond が偽なら pass。 |
testrt_expect_eq_i actual, expected, "msg" | int 等価。detail に actual=X expected=Y が出る。 |
testrt_expect_ne_i actual, expected, "msg" | int 不等。 |
testrt_expect_eq_d actual, expected, eps, "msg" | double 等価 (誤差 eps 以内)。 |
testrt_expect_eq_s actual, expected, "msg" | 文字列 strcmp 等価。 |
testrt_expect_ne_s actual, expected, "msg" | 文字列 strcmp 不等。 |
#func var で変数アドレスを渡すので、int64 変数や配列を直接比較できます。
| コマンド | 説明 |
|---|---|
testrt_expect_eq_i64 var a, var b, "msg" | int64 変数の等価 (10進表記で detail) |
testrt_expect_ne_i64 var a, var b, "msg" | int64 変数の不等 |
testrt_expect_near_i actual, expected, tol, "msg" | |actual-expected| ≤ tol で pass (int) |
testrt_expect_eq_intptr var a, var b, "msg" | ハンドル比較。detail は 16進 |
testrt_expect_array_eq_i var a, var b, length, "msg" | int 配列の全要素比較。最初に不一致した index を detail に |
testrt_expect_array_eq_d var a, var b, length, eps, "msg" | double 配列 (要素毎 eps 内) |
stb_image.h で PNG を読み、ピクセル単位で baseline と current を比較します。
testrt_image_cmp / testrt_image_diff は値を返す #cfunc、
testrt_expect_image_eq は pass/fail を記録する #func です。
| コマンド | 説明 |
|---|---|
testrt_image_cmp(baseline, current, tol) | 最大ピクセル差 (0..255) を返す。負値はエラー (-1=baseline 読み失敗 / -2=current 読み失敗 / -3=サイズ不一致) |
testrt_image_diff(baseline, current, diff_out, tol) | 上と同じ + tolerance 超過ピクセルを赤、それ以外をグレースケール化した diff PNG を書き出す |
testrt_expect_image_eq baseline, current, tol, "msg" | 超過ピクセル 0 で pass。detail に diff_pixels / max_diff / mean_diff / tol / size |
tol 以下なら一致とみなす。
tol=0 で厳密一致、tol=5 程度で圧縮やアンチエイリアスのゆらぎを吸収できる。
| コマンド | 説明 |
|---|---|
testrt_trace "tag" | 任意のタグ名で trace イベントを出す (ロジック通過確認用)。 |
testrt_emit "type", "message" | 任意のイベント種別で 1 行 JSON を出す。 |
| コマンド | 説明 |
|---|---|
testrt_screenshot("path.png", hwnd) | 指定ウィンドウのクライアント領域を PNG で保存。hwnd=0 で GetForegroundWindow。戻り値 0 で成功、負値でエラー。 |
testrt_click(x, y, button) | スクリーン座標にマウスクリックを送信。 button: TESTRT_BUTTON_LEFT (0) / _RIGHT (1) / _MIDDLE (2) |
testrt_key(vk_code) | 仮想キーコードを SendInput で送信。 |
hsptestrt の API を毎回フルに書くと冗長なので、薄いマクロ層を用意しています。 マクロのみで関数呼び出しを挟まないため、失敗時の行番号が呼び出し元の ソース位置を指します。
#include "hsp3cl_net_64.as"
#include "iron_test_ex.hsp"
test_case "basic arithmetic"
expect_eq 1+2, 3
expect_eq 4*5, 20
assert_true 1 < 2
test_end
test_case "strings"
s = "hello"
expect_eq strlen(s), 5
expect_streq s, "hello"
expect_strne s, "world"
test_end
test_case "float tolerance"
expect_feq 0.1 + 0.2, 0.3, 1.0e-9
test_end
trace "reached final section"
end test_finish ; 失敗件数=プロセス終了コード
| DSL | 展開先 |
|---|---|
test_case "name" | testrt_begin "name" |
test_end | testrt_end |
test_finish | testrt_summary() |
expect_eq a, b | testrt_expect_eq_i a, b, "expect_eq" |
expect_ne a, b | testrt_expect_ne_i a, b, "expect_ne" |
expect_feq a, b, eps | testrt_expect_eq_d a, b, eps, "expect_feq" |
expect_streq a, b | testrt_expect_eq_s a, b, "expect_streq" |
expect_strne a, b | testrt_expect_ne_s a, b, "expect_strne" |
assert_true c / assert_false c | testrt_expect_true/false |
assert_with c, "msg" | testrt_assert c, "msg" |
expect_eq_m a, b, "msg" | メッセージ明示版 (int) |
expect_streq_m a, b, "msg" | メッセージ明示版 (str) |
trace "tag" | testrt_trace "tag" |
emit "type", "message" | testrt_emit "type", "message" |
screenshot("path") / screenshot_hwnd("path", h) | testrt_screenshot(...) |
expect_eq_i64 a, b / expect_ne_i64 a, b | int64 変数比較 (Phase 4b) |
expect_near a, b, tol | int 許容差比較 (Phase 4b) |
expect_eq_ptr a, b | intptr/ハンドル比較 (Phase 4b) |
expect_array_eq a, b, len | int 配列比較 (Phase 4b) |
expect_array_feq a, b, len, eps | double 配列比較 (Phase 4b) |
expect_image_eq baseline, current, tol | PNG 画像比較 (Phase 4d) |
test_image_cmp(...) / test_image_diff(...) | 画像比較 rvalue / diff PNG 書き出し (Phase 4d) |
複数の .hsp をまとめて「コンパイル → テスト実行 → 結果集計」するネイティブ実行ファイルです。
HSP に依存せず Windows の CreateProcess + パイプで子プロセスの stderr を拾うため、
子側がクラッシュしても runner は止まりません。
iron_test_runner.exe [options] <file-or-glob> [...]
| オプション | デフォルト | 意味 |
|---|---|---|
--runtime=<exe> | hsp3cl_net_test_64.exe | テスト実行に使う HSP ランタイム |
--compiler=<exe> | hspcmp64.exe | HSP コンパイラ。常に -i -u -d を付けて呼ぶ |
--syspath=<dir> | — | hspcmp に渡す --syspath |
--compath=<dir> | — | hspcmp に渡す --compath (.as / .hsp の置き場) |
--json=<file> | — | マシン可読な JSON 集計ファイルを書き出す |
--junit=<file> | — | JUnit XML を書き出す (GitHub Actions / Jenkins など CI 連携用) |
--jobs=<N> | 1 | N ファイルを並列実行 (CreateProcess を N 本並行に走らせる)。Phase 4c |
--coverage=<file> | — | ソース行カバレッジをマージして TSV で書き出す (Phase 4e) |
--no-compile | OFF | 入力を .ax とみなしてコンパイルをスキップ |
--quiet | OFF | 個別の fail 詳細を抑制し、サマリだけ表示 |
--verbose | OFF | 子プロセスの stderr NDJSON を全行エコー |
--no-color | OFF | ANSI カラーコードを使わない |
rem カレントの test_*.hsp を全部走らせる
iron_test_runner.exe --compath=. test_*.hsp
rem ランタイム / コンパイラを明示、JSON 出力
iron_test_runner.exe --compiler=.\hspcmp64.exe ^
--runtime=.\hsp3cl_net_test_64.exe ^
--compath=. ^
--json=results.json ^
tests\*.hsp
[PASS] suite_pass.hsp cases:2 pass:5 fail:0 (94ms)
[FAIL] suite_fail.hsp cases:2 pass:1 fail:2 (109ms)
- [one passes one fails] expect_eq (actual=2 expected=99)
- [all fail] expect_streq (actual="hi" expected="bye")
[ERR ] suite_error.hsp cases:1 pass:0 fail:0 (109ms)
error code=19 line=6 file=iron_test_ex.hsp "0で除算しました"
----------------------------------------
files: 1 ok, 1 fail, 1 error (total 3)
tests: 5 cases, 6 pass, 2 fail (312ms)
exit = failed_expects + error_files です。0 のときだけ all-green。
CI では if errorlevel 1 exit 1 でそのまま合否判定できます。
{
"files": 3,
"ok": 1,
"fail": 1,
"error": 1,
"total_cases": 5,
"pass": 6,
"fail_expects": 2,
"duration_ms": 296,
"results": [
{"path":"suite_pass.hsp","cases":2,"pass":5,"fail":0,"error":false,"ms":93},
{"path":"suite_fail.hsp","cases":2,"pass":1,"fail":2,"error":false,"ms":109},
{"path":"suite_error.hsp","cases":1,"pass":0,"fail":0,"error":true,"ms":94}
]
}
GitHub Actions / Jenkins / Azure Pipelines など多くの CI が標準対応している
JUnit XML 形式で結果を吐き出します。<testsuites> / <testsuite> (ファイル単位) /
<testcase> (test_case 単位) の 3 階層構造で、
失敗は <failure>、HSP ランタイムエラーは <error> として記録されます。
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="iron_test_runner" tests="5" failures="2" errors="1" time="0.298">
<testsuite name="suite_pass" tests="2" failures="0" errors="0" time="0.125" file="suite_pass.hsp">
<testcase classname="suite_pass" name="arithmetic"></testcase>
<testcase classname="suite_pass" name="strings"></testcase>
</testsuite>
<testsuite name="suite_fail" tests="2" failures="2" errors="0" time="0.079" file="suite_fail.hsp">
<testcase classname="suite_fail" name="one passes one fails">
<failure type="expect_eq" message="expect_eq">actual=2 expected=99</failure>
</testcase>
<testcase classname="suite_fail" name="all fail">
<failure type="expect_streq" message="expect_streq">actual="hi" expected="bye"</failure>
</testcase>
</testsuite>
<testsuite name="suite_error" tests="1" failures="0" errors="1" time="0.094" file="suite_error.hsp">
<testcase classname="suite_error" name="about to crash"></testcase>
<testcase classname="suite_error" name="(runtime)">
<error type="runtime_error" message="0で除算しました">code=19 line=6 file=...</error>
</testcase>
</testsuite>
</testsuites>
| 属性 | 意味 |
|---|---|
testsuites.tests / failures / errors | 全ファイルを集計した全体カウント |
testsuites.time | 全ファイルの合計実行時間 (秒) |
testsuite.file | 元の .hsp ファイルパス |
testcase.classname | suite 名 (= ファイル名から拡張子を除いたもの) |
testcase.name | test_case "..." で指定した名前 |
failure.type / message | expect_eq などアサーション種別 |
failure 要素本文 | actual=... expected=... の詳細 |
error.type / message | HSP ランタイムエラー内容 |
error 要素本文 | エラーコード / 行番号 / ファイル名 |
スイート間に依存がないので、複数の .hsp を同時にコンパイル+実行できます。 runner は atomic counter によるワークスティーリング方式で N スレッドに配分します。
iron_test_runner.exe --jobs=8 --compath=. test_*.hsp
結果の出力順は入力順と同じになる (各スレッドが自分の結果 slot を書き込む形)。
テストランタイム側にバイトコード dispatch ループの per-step フックがあり、
環境変数 HSPTEST_COV_FILE が設定されているときだけ (file, line) タプルを
in-memory set に追加します。runner は各ファイルを走らせるたびに
HSPTEST_COV_FILE=<tmp>.cov.tsv を渡し、終了後に全ファイルをマージします。
iron_test_runner.exe --coverage=coverage.tsv --compath=. test_*.hsp
出力 TSV の形式:
file line hits suite_pass.hsp 5 1 suite_pass.hsp 6 1 suite_pass.hsp 13 3 iron_test_ex.hsp 48 5 ...
画面にもサマリが 1 行:
coverage: 4 files, 44 unique lines, 45 hit events -> coverage.tsv
-d (デバッグ情報付き) コンパイルが前提。-d を付けます。g_cov_state 1 回チェック + 早期 return だけなので性能影響はほぼゼロです。
import csv
hits = {} # (file, line) -> count
with open('coverage.tsv') as fp:
for row in csv.DictReader(fp, delimiter='\t'):
hits[(row['file'], int(row['line']))] = int(row['hits'])
# ファイルごとに hit 行数を集計
by_file = {}
for (f, _), c in hits.items():
by_file.setdefault(f, 0)
by_file[f] += 1
for f, n in sorted(by_file.items()):
print(f'{f}: {n} lines')
テストランタイムおよび hsptestrt.dll は、すべてのイベントを 1 行 1 JSON
(NDJSON / JSON Lines) 形式で stderr に書き込みます。
改行は LF 1 つで、各行は必ず {...} の形です。
| フィールド | 型 | 説明 |
|---|---|---|
time | string | ISO8601 形式のローカル時刻 (秒精度) |
type | string | イベント種別 (下表) |
| type | ソース | 主要フィールド | 意味 |
|---|---|---|---|
start | ランタイム | exe, cmdline | プロセス起動直後に 1 回だけ出力 |
end | ランタイム | exit | プロセス終了直前に 1 回だけ出力 |
error | ランタイム | code, line, file, message | HSP ランタイムエラー (0 除算、型不一致など)。非ゼロ exit の原因 |
alert | ランタイム | message | 内部の Alert()/Alertf() 呼び出し。通常は致命的ではない |
dialog | ランタイム | flag, caption, text | dialog 命令が抑止された (stat には IDOK=1 or IDYES=6 を返す) |
test_begin | hsptestrt | case | testrt_begin / test_case で 1 回 |
test_end | hsptestrt | case, pass, fail | testrt_end / test_end で 1 回 |
expect_fail | hsptestrt | case, message, detail | expect_* 系で失敗したときだけ |
test_summary | hsptestrt | total, pass, fail | testrt_summary() / test_finish で 1 回 |
screenshot | hsptestrt | path, width, height, ok | testrt_screenshot 完了時 |
image_cmp | hsptestrt | baseline, current, w_a, h_a, w_b, h_b, tolerance, max_diff, diff_pixels, mean_diff, pass | testrt_image_cmp / _diff / _expect_image_eq (Phase 4d) |
trace | hsptestrt | tag, line | testrt_trace / trace マクロ |
coverage | ランタイム | file, hits | HSPTEST_COV_FILE ありでランタイム終了時 (Phase 4e) |
event (任意) | hsptestrt | message | testrt_emit で指定した任意 type |
{"time":"2026-04-18T17:50:33","type":"start","exe":"...hsp3cl_net_test_64.exe","cmdline":"_test_dsl.ax"}
{"time":"2026-04-18T17:50:33","type":"test_begin","case":"basic arithmetic"}
{"time":"2026-04-18T17:50:33","type":"test_end","case":"basic arithmetic","pass":3,"fail":0}
{"time":"2026-04-18T17:50:33","type":"test_begin","case":"a case that fails on purpose"}
{"time":"2026-04-18T17:50:33","type":"expect_fail","case":"a case that fails on purpose","message":"expect_eq","detail":"actual=1 expected=2"}
{"time":"2026-04-18T17:50:33","type":"expect_fail","case":"a case that fails on purpose","message":"expect_streq","detail":"actual=\"abc\" expected=\"xyz\""}
{"time":"2026-04-18T17:50:33","type":"test_end","case":"a case that fails on purpose","pass":0,"fail":2}
{"time":"2026-04-18T17:50:33","type":"test_summary","total":9,"pass":7,"fail":2}
{"time":"2026-04-18T17:50:33","type":"end","exit":2}
- name: Run IronHSP tests
shell: cmd
run: |
cd test
..\package\win32\iron_test_runner.exe ^
--compiler=..\package\win32\hspcmp64.exe ^
--runtime=..\hsp3net\Release_test\hsp3cl_net_test_64.exe ^
--compath=..\package\win32\common ^
--junit=test-results.xml ^
--json=test-results.json ^
test_*.hsp
- name: Publish JUnit report
if: always()
uses: mikepenz/action-junit-report@v4
with:
report_paths: test/test-results.xml
- name: Upload test artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: |
test/test-results.xml
test/test-results.json
mikepenz/action-junit-report や
dorny/test-reporter などの JUnit Reporter アクションを使うと、
PR 画面にテスト結果が直接表示されます。
rem バッチファイル run_tests.bat
@echo off
iron_test_runner.exe --compath=. test_*.hsp
if errorlevel 1 (
echo TESTS FAILED
exit /b %errorlevel%
)
echo OK
テストランタイム (hsp3*_net_test) は現状 hsp3net ツリーだけをビルド対象にしています。
IronHSP の推奨構成が hsp3net (とくに x64) であり、.NET 連携・NSTRUCT・cfuncst などを含む全機能を
使える唯一のランタイムだからです。vanilla hsp3 側に同じ #ifdef HSP_TEST_MODE
ガードを足すことで同じ仕組みに拡張可能ですが、現時点ではやっていません。
ソースコードは 完全に同じファイルを共有しています。vcxproj だけを別途用意し、
プリプロセッサに HSP_TEST_MODE を追加した 4 本を作っています。
#ifdef HSP_TEST_MODE の分岐で、既存のダイアログ出力経路だけ stderr JSON 出力に置換されます。
テスト専用の「フォーク」は存在しないので、本体の修正が自動的にテストランタイムにも追従します。
line フィールドが出ないことがあるHSP の行番号情報は .ax にデバッグ情報が入っている時のみ取得できます。
hspcmp64 -d (または -d 相当) で必ずコンパイルしてください。
iron_test_runner.exe は自動的に -d を付けて呼ぶので通常は気にする必要はありません。
GUI アプリはコンソールを持たないため、そのままだと stderr が捨てられます。 テストランタイムは以下の優先順で出力先を解決します:
2>file でリダイレクトしていれば、そのハンドルをそのまま使うAttachConsole(ATTACH_PARENT_PROCESS) + CONOUT$ で出すHSPTEST_STDERR_FILE が設定されていればそのパスに追記iron_test_runner.exe から呼ぶ場合はパイプで拾うので常に (1) のルートが有効です。
testrt_screenshot は GetDC(hwnd) + BitBlt で取得します。
Direct3D や DirectComposition でレンダリングしているウィンドウは BitBlt で取れないため真っ黒になる場合があります。
その場合は OS の PrintWindow(PW_RENDERFULLCONTENT) を使う版を追加する必要があります (未実装)。
Windows の exit code は 32bit なのでオーバーフローはしませんが、
シェルによっては下位 8bit しか見ないケースがあります (bash など)。
255 件以上の失敗がある場合は --json 出力の fail_expects を参照してください。
CL テストランタイムでは stop 命令は Press any key 表示せず即終了します。
end 数値 のときは数値が exit code になります (テストが end test_finish
パターンを使う理由)。await はそのまま動作するので、タイミング依存のテストも書けます。
最終更新: 2026-04-18 — IronHSP Test Runtime Phase 1 + Phase 2 + Phase 3
関連ドキュメント:
IronHSP_specification.html /
iron_modules_intro.html