0
|
1 \chapter{テスト環境の構築} \label{chapter:test}
|
4
|
2 本研究では、逐次型プログラムとして Super Dandy の OpenGL バージョン、Cerium
|
|
3 の Rendering Engine のみを使用したバージョンを用意した。これらと Task Dandy
|
7
|
4 を用いてその挙動をテストし、全てのバージョンにおいて同じ挙動をしている事を
|
|
5 確認できるテスト環境を構築する。ここではテスト環境のために追加した機能に
|
|
6 ついて説明する。
|
0
|
7
|
9
|
8 \section{Capture モードと Trace モードの実装}
|
4
|
9 ゲームにおいてプレイヤーからの入力は制御不能なランダム要素であり、
|
|
10 バグを再現することを困難にする(\ref{sec:player}節)。
|
|
11 そこでプレイヤーからの入力を1フレーム毎に記録し、バイナリデータとして
|
|
12 書き出す Capture モードと書き出されたバイナリデータを読み込み、プレイヤーの
|
|
13 入力を再現する Trace モードを実装した。どちらのモードも実行ファイルに
|
|
14 オプションとファイル名を付けることによって起動することが出来る。
|
|
15
|
|
16 \begin{verbatim}
|
|
17 % ./demo -capture capture.dat
|
|
18 Start Capture mode.
|
|
19
|
|
20 % ./demo -trace capture.dat
|
|
21 Start Trace mode.
|
|
22 \end{verbatim}
|
|
23
|
|
24 2 つのモードでは TraceBuff という単方向リスト型のバッファを使用している。
|
|
25 Capture モードではバッファサイズが足りなくなると新たな TraceBuff を allocate
|
|
26 して next ポインタにアドレスを格納する。バイナリデータに書き出す時はリストの
|
|
27 先頭から順番に書き出していく。
|
|
28
|
|
29 Trace モードではバイナリデータのサイズを計算し、予め必要なサイズの TraceBuff
|
|
30 を allocate しておく。
|
|
31 (図\ref{fig:pad_buff})
|
|
32
|
|
33 \begin{figure}[h]
|
|
34 \begin{center}
|
7
|
35 \includegraphics[scale=0.6]{images/pad_buff.pdf}
|
4
|
36 \end{center}
|
|
37 \caption{TraceBuff}
|
|
38 \label{fig:pad_buff}
|
|
39 \end{figure}
|
|
40
|
|
41 この 2 つのモードは用意された 3 つの Super Dandy 全てに組み込まれている。
|
|
42 よって旧バージョンで入力データを記録し、記録したデータを新バージョンで
|
|
43 読み込むことも可能である。これにより、両バージョンで異なる動きが見られた
|
|
44 場合、そこにバグが潜んでいると仮定することができる。
|
|
45
|
9
|
46 \section{SPE における乱数生成の欠点}\label{sec:create_random}
|
4
|
47 乱数のランダム性はバグの再現性を落とすが、乱数生成器を無効にするか、定数で
|
|
48 シードすることによって再現性を下げることなく、テストすることが出来る
|
|
49 (\ref{sec:random}節)。
|
|
50
|
5
|
51 通常のシーケンシャルなプログラムでは 1 つの乱数列から順番に各 Move 関数が
|
|
52 乱数を取得し、使用する。しかし並列プログラムでは Move 関数が Task として
|
|
53 SPE に送られる。各 SPE 内では以下のような処理が行われる。
|
|
54
|
|
55 \begin{enumerate}
|
|
56 \item 定数でシードし、乱数列を生成する。
|
|
57 \item Task1 で乱数列の先頭から 1 番目の乱数を取得する
|
|
58 \item Task2 で乱数列の先頭から 2 番目の乱数を取得する
|
|
59 \item Task3 で乱数列の先頭から 3 番目の乱数を取得する
|
|
60 \end{enumerate}
|
|
61
|
|
62 乱数列は SPE 毎に独自に生成されるため、各 Task(=Move) が受け取る乱数は
|
|
63 逐次実行した時とは異なる値となってしまう。また、SPE 内でも Task 同士に
|
|
64 依存関係を持たせない限り、Task の実行順序が保証されないのでこれも受け取る
|
|
65 乱数が不定となる原因となる。(図\ref{fig:spe_random})
|
|
66
|
7
|
67 \begin{figure}[h]
|
|
68 \begin{center}
|
|
69 \includegraphics[scale=0.4]{images/spe_random.pdf}
|
|
70 \end{center}
|
|
71 \caption{SPE 内での乱数の生成}
|
|
72 \label{fig:spe_random}
|
|
73 \end{figure}
|
14
|
74 OB
|
7
|
75 \newpage
|
|
76
|
5
|
77 そこで予め PPE 内で乱数列を生成し、inData として Task に渡しておく。
|
|
78 Task Dandy では Task の生成、定義がされるタイミングは Super Dandy における
|
|
79 Move 関数や Collision 関数が実行されるタイミングと同じである為、渡される乱数
|
|
80 は Super Dandy と同じ乱数となる。(図\ref{fig:ppe_random})
|
|
81
|
|
82 \begin{figure}[h]
|
|
83 \begin{center}
|
|
84 \includegraphics[scale=0.5]{images/ppe_random.pdf}
|
|
85 \end{center}
|
|
86 \caption{PPE 内での乱数の生成と乱数の受け渡し}
|
|
87 \label{fig:ppe_random}
|
|
88 \end{figure}
|
|
89
|
9
|
90 \section{テストログに記述する情報とそのタイミング}
|
7
|
91 シーケンシャルなプログラムを Task に分割して並列実行する際に、新たに発生する
|
|
92 バグとして、本研究では以下の項目に焦点を当てた。
|
|
93
|
|
94 \begin{itemize}
|
|
95 \item Task 間のデータの同期
|
|
96 \item Task の実行順序
|
|
97 \item Task の定義
|
|
98 \end{itemize}
|
|
99
|
|
100 このうち、Task の定義については、定義される内容が非常に小さい為、Task の
|
9
|
101 inData や outData を調べるといった従来のテスト手法でも十分にテストが可能で
|
7
|
102 ある。その他の 2 つについては、いずれも衝突判定の際に生じるバグである。
|
|
103 (図\ref{fig:test_log})
|
|
104
|
5
|
105 \newpage
|
|
106
|
|
107 \begin{figure}[h]
|
|
108 \begin{center}
|
|
109 \includegraphics[scale=0.8]{images/test_log.pdf}
|
|
110 \end{center}
|
|
111 \caption{Task Dandy で起こりうるバグ}
|
|
112 \label{fig:test_log}
|
|
113 \end{figure}
|
4
|
114
|
|
115 そこで、オブジェクトが被弾した時、そしてオブジェクトが生成された時にテスト
|
|
116 ログを取ることで効率的にバグを発見することができると考えた。以下に実際に
|
|
117 収集したテストログの例を示す。
|
|
118
|
|
119 \begin{verbatim}
|
5
|
120 F64: CREATE [NAME]enemy_greenclab_0 [COORD]x= 120.000000 y= -128.000000
|
|
121 vx= 0.000000 vy= 4.000000
|
|
122 F85: DELETE [NAME]enemy_greenclab_0 [COORD]x= 120.000000 y= -44.000000
|
|
123 vx= 0.000000 vy= 4.000000
|
7
|
124 [BULLET]tlv1 = 2, tlv2 = 0 llv1 = 0
|
4
|
125 \end{verbatim}
|
|
126
|
|
127 それぞれのパラメータの詳細は次のとおりである。
|
|
128
|
7
|
129 \newpage
|
|
130
|
4
|
131 \begin{itemize}
|
|
132 \item F64,F85\\
|
|
133 生成、もしくは被弾した時の経過フレーム。
|
|
134 \item CREATE,DELETE\\
|
|
135 CREATE ならオブジェクトが生成された、DELETE ならオブジェクトが被弾した事を
|
|
136 表す。
|
5
|
137 \item NAME\\
|
|
138 オブジェクトの種類と ID。ID はオブジェクトの種類毎に 0 から順番に付けられ
|
|
139 るのでどのオブジェクトの情報なのかを特定できる。
|
|
140 \item COORD\\
|
|
141 オブジェクトのxy座標とxy方向の速度。
|
7
|
142 \item BULLET
|
5
|
143 その瞬間に画面内に存在した弾の数。差異があれば同期が取れていないことを
|
|
144 示す。
|
4
|
145 \end{itemize}
|
|
146
|
|
147 これにより、フレーム単位でどのオブジェクトが生成、または被弾したのか知ること
|
|
148 ができる。trace モードでプレイヤーの入力を固定し、各バージョンでテストログを
|
|
149 取り、その差異を調べることでバグが発生している時間や場所を特定することが
|
|
150 できる。
|
|
151
|
|
152 \section{描画処理を行わないビデオモード}\label{sec:video_none}
|
|
153 Cerium では Rendering Engine において 3 つの Task を用いて画面の描画処理を
|
|
154 行っている(\ref{sec:rendering}節)。Cerium では複数の環境で動作するように
|
|
155 オブジェクトを画面のフレームバッファに直接書き出すモードや、SDL のバッファに
|
5
|
156 書き出すモードなど、複数のビデオモードが存在する。
|
|
157 (図\ref{fig:video_mode})
|
4
|
158
|
5
|
159 しかし、プレイヤーの入力をバイナリデータから読み出す場合、処理の詳細が
|
|
160 知りたい場合を除いて画面の描画処理は不要となる。
|
4
|
161 そこでテスト用に画面の描画処理を行わないモードを Cerium に実装した。
|
|
162 これは、Cerium 内で画面のバッファを確保する処理をしている箇所と、描画用の
|
|
163 Task を生成する処理をしている箇所をスルーすることで描画処理と無駄なメモリ
|
9
|
164 確保を行わずにテストを行うことができるビデオモードである。
|
|
165 (図\ref{fig:video_none})
|
4
|
166
|
5
|
167 \begin{figure}[h]
|
|
168 \begin{center}
|
|
169 \includegraphics[scale=0.6]{images/video.pdf}
|
|
170 \end{center}
|
|
171 \caption{通常のビデオモード}
|
|
172 \label{fig:video_mode}
|
|
173 \end{figure}
|
|
174
|
4
|
175 \begin{figure}[h]
|
|
176 \begin{center}
|
|
177 \includegraphics[scale=0.8]{images/video_none.pdf}
|
|
178 \end{center}
|
|
179 \caption{描画処理を行わないビデオモード}
|
7
|
180 \label{fig:video_none}
|
4
|
181 \end{figure}
|