IronHSP テストランタイム & 自動テストガイド

IronHSP は HSP3 スクリプトの自動テストを書くための専用ランタイムとユーティリティを提供します。 エラー時にモーダルダイアログが出てプロセスが固まる、画面表示をテストで取り出せないといった 従来の HSP の課題を、以下のコンポーネントを組み合わせて解決します。

目次

1. 全体像

自動テストは次の 4 コンポーネントを組み合わせて行います:

コンポーネント役割配置
hsp3_net_test.exe
hsp3cl_net_test.exe
(+ _64 版)
テスト用 HSP3 ランタイム。エラー発生時にダイアログを出さず、
stderr に JSON 1 行イベントとして状況を流して非ゼロ exit する。
hsp3net/Release_test/ ほか
hsptestrt.dll
hsptestrt_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 で機械可読出力

2. テストランタイム (hsp3*_net_test.exe)

hsp3net 系の 4 つのランタイム (GUI / CL × Win32 / x64) を HSP_TEST_MODE プリプロセッサ付きでビルドした版です。ソースコードは本体と完全に共有しており、 #ifdef HSP_TEST_MODE ガードだけで以下の振る舞いが変わります:

出力される 4 つの実行ファイル

ファイル対応ランタイムビットサイズ
hsp3_net_test.exeGUI (hsp3.exe 系)Win32
hsp3_net_test_64.exeGUI (hsp3_64.exe 系)x64
hsp3cl_net_test.exeCL (hsp3cl.exe 系)Win32
hsp3cl_net_test_64.exeCL (hsp3cl_64.exe 系)x64
x64 推奨: IronHSP 本体も 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 を付けないとエラーの行番号が拾えません。テスト用途では必ず付けてください。

3. hsptestrt.dll — テスト支援プラグイン

テストスクリプトから呼び出す 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 不等。

型拡張アサーション (Phase 4b)

#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 内)

画像比較 (Phase 4d)

stb_image.h で PNG を読み、ピクセル単位で baseline と current を比較します。 testrt_image_cmp / testrt_image_diff は値を返す #cfunctestrt_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 の意味: 各ピクセルの RGB チャネル差分の最大値が tol 以下なら一致とみなす。 tol=0 で厳密一致、tol=5 程度で圧縮やアンチエイリアスのゆらぎを吸収できる。

ログ / トレース

コマンド説明
testrt_trace "tag"任意のタグ名で trace イベントを出す (ロジック通過確認用)。
testrt_emit "type", "message"任意のイベント種別で 1 行 JSON を出す。

GUI 自動操作 (hsp3_net_test GUI 向け)

コマンド説明
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 で送信。

4. iron_test_ex.hsp DSL

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_endtestrt_end
test_finishtestrt_summary()
expect_eq a, btestrt_expect_eq_i a, b, "expect_eq"
expect_ne a, btestrt_expect_ne_i a, b, "expect_ne"
expect_feq a, b, epstestrt_expect_eq_d a, b, eps, "expect_feq"
expect_streq a, btestrt_expect_eq_s a, b, "expect_streq"
expect_strne a, btestrt_expect_ne_s a, b, "expect_strne"
assert_true c / assert_false ctestrt_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, bint64 変数比較 (Phase 4b)
expect_near a, b, tolint 許容差比較 (Phase 4b)
expect_eq_ptr a, bintptr/ハンドル比較 (Phase 4b)
expect_array_eq a, b, lenint 配列比較 (Phase 4b)
expect_array_feq a, b, len, epsdouble 配列比較 (Phase 4b)
expect_image_eq baseline, current, tolPNG 画像比較 (Phase 4d)
test_image_cmp(...) / test_image_diff(...)画像比較 rvalue / diff PNG 書き出し (Phase 4d)

5. iron_test_runner.exe — 集約ランナー

複数の .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.exeHSP コンパイラ。常に -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>1N ファイルを並列実行 (CreateProcess を N 本並行に走らせる)。Phase 4c
--coverage=<file>ソース行カバレッジをマージして TSV で書き出す (Phase 4e)
--no-compileOFF入力を .ax とみなしてコンパイルをスキップ
--quietOFF個別の fail 詳細を抑制し、サマリだけ表示
--verboseOFF子プロセスの stderr NDJSON を全行エコー
--no-colorOFFANSI カラーコードを使わない

使用例

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 でそのまま合否判定できます。

--json 出力形式

{
  "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}
  ]
}

--junit 出力形式 (CI 連携用)

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.classnamesuite 名 (= ファイル名から拡張子を除いたもの)
testcase.nametest_case "..." で指定した名前
failure.type / messageexpect_eq などアサーション種別
failure 要素本文actual=... expected=... の詳細
error.type / messageHSP ランタイムエラー内容
error 要素本文エラーコード / 行番号 / ファイル名

6. 並列実行 & カバレッジ (Phase 4c / 4e)

--jobs=N で並列実行

スイート間に依存がないので、複数の .hsp を同時にコンパイル+実行できます。 runner は atomic counter によるワークスティーリング方式で N スレッドに配分します。

iron_test_runner.exe --jobs=8 --compath=. test_*.hsp

結果の出力順は入力順と同じになる (各スレッドが自分の結果 slot を書き込む形)。

--coverage でソース行カバレッジ

テストランタイム側にバイトコード 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 (デバッグ情報付き) コンパイルが前提。
runner は自動で -d を付けます。
カバレッジフックの無効化状態 (通常ビルド or 環境変数未設定) では g_cov_state 1 回チェック + 早期 return だけなので性能影響はほぼゼロです。

カバレッジ TSV の後処理例 (Python)

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')

7. stderr JSON イベント仕様

テストランタイムおよび hsptestrt.dll は、すべてのイベントを 1 行 1 JSON (NDJSON / JSON Lines) 形式で stderr に書き込みます。 改行は LF 1 つで、各行は必ず {...} の形です。

共通フィールド

フィールド説明
timestringISO8601 形式のローカル時刻 (秒精度)
typestringイベント種別 (下表)

イベント種別

typeソース主要フィールド意味
startランタイムexe, cmdlineプロセス起動直後に 1 回だけ出力
endランタイムexitプロセス終了直前に 1 回だけ出力
errorランタイムcode, line, file, messageHSP ランタイムエラー (0 除算、型不一致など)。非ゼロ exit の原因
alertランタイムmessage内部の Alert()/Alertf() 呼び出し。通常は致命的ではない
dialogランタイムflag, caption, textdialog 命令が抑止された (stat には IDOK=1 or IDYES=6 を返す)
test_beginhsptestrtcasetestrt_begin / test_case で 1 回
test_endhsptestrtcase, pass, failtestrt_end / test_end で 1 回
expect_failhsptestrtcase, message, detailexpect_* 系で失敗したときだけ
test_summaryhsptestrttotal, pass, failtestrt_summary() / test_finish で 1 回
screenshothsptestrtpath, width, height, oktestrt_screenshot 完了時
image_cmphsptestrtbaseline, current, w_a, h_a, w_b, h_b, tolerance, max_diff, diff_pixels, mean_diff, passtestrt_image_cmp / _diff / _expect_image_eq (Phase 4d)
tracehsptestrttag, linetestrt_trace / trace マクロ
coverageランタイムfile, hitsHSPTEST_COV_FILE ありでランタイム終了時 (Phase 4e)
event (任意)hsptestrtmessagetestrt_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}

8. CI 連携例

GitHub Actions

- 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-reportdorny/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

9. 制約 & FAQ

Q. なぜ hsp3net 限定なの? vanilla hsp3 は対応しない?

テストランタイム (hsp3*_net_test) は現状 hsp3net ツリーだけをビルド対象にしています。 IronHSP の推奨構成が hsp3net (とくに x64) であり、.NET 連携・NSTRUCT・cfuncst などを含む全機能を 使える唯一のランタイムだからです。vanilla hsp3 側に同じ #ifdef HSP_TEST_MODE ガードを足すことで同じ仕組みに拡張可能ですが、現時点ではやっていません。

Q. コード重複が気になる。本体ランタイムと何が違うの?

ソースコードは 完全に同じファイルを共有しています。vcxproj だけを別途用意し、 プリプロセッサに HSP_TEST_MODE を追加した 4 本を作っています。 #ifdef HSP_TEST_MODE の分岐で、既存のダイアログ出力経路だけ stderr JSON 出力に置換されます。 テスト専用の「フォーク」は存在しないので、本体の修正が自動的にテストランタイムにも追従します。

Q. エラー位置に line フィールドが出ないことがある

HSP の行番号情報は .ax にデバッグ情報が入っている時のみ取得できます。 hspcmp64 -d (または -d 相当) で必ずコンパイルしてください。 iron_test_runner.exe は自動的に -d を付けて呼ぶので通常は気にする必要はありません。

Q. GUI ランタイムで stderr が見えない

GUI アプリはコンソールを持たないため、そのままだと stderr が捨てられます。 テストランタイムは以下の優先順で出力先を解決します:

  1. 呼び出し側が 2>file でリダイレクトしていれば、そのハンドルをそのまま使う
  2. 親コンソールがあれば AttachConsole(ATTACH_PARENT_PROCESS) + CONOUT$ で出す
  3. 環境変数 HSPTEST_STDERR_FILE が設定されていればそのパスに追記

iron_test_runner.exe から呼ぶ場合はパイプで拾うので常に (1) のルートが有効です。

Q. screenshot が真っ黒になる

testrt_screenshotGetDC(hwnd) + BitBlt で取得します。 Direct3D や DirectComposition でレンダリングしているウィンドウは BitBlt で取れないため真っ黒になる場合があります。 その場合は OS の PrintWindow(PW_RENDERFULLCONTENT) を使う版を追加する必要があります (未実装)。

Q. 失敗件数が大きすぎて exit code 255 を超える

Windows の exit code は 32bit なのでオーバーフローはしませんが、 シェルによっては下位 8bit しか見ないケースがあります (bash など)。 255 件以上の失敗がある場合は --json 出力の fail_expects を参照してください。

Q. stop / end / await がどう扱われるか

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