5656import java .util .Collection ;
5757import java .util .Collections ;
5858import java .util .Comparator ;
59+ import java .util .HashMap ;
5960import java .util .List ;
61+ import java .util .Map ;
6062import java .util .concurrent .CountDownLatch ;
6163import java .util .concurrent .Executor ;
6264import java .util .concurrent .Executors ;
@@ -78,7 +80,9 @@ public class TestDriver extends BaseAppState{
7880
7981 private static final Logger logger = Logger .getLogger (TestDriver .class .getName ());
8082
81- public static final String IMAGES_ARE_DIFFERENT = "Images are different. (If you are running the test locally this is expected, images only reproducible on github CI infrastructure)" ;
83+ public static final String IMAGES_ARE_DIFFERENT = "Generated images is different from committed image. (If you are running the test locally this is expected, images only reproducible on github CI infrastructure)" ;
84+
85+ public static final String IMAGES_ARE_DIFFERENT_BETWEEN_SCENARIOS = "Images are different between scenarios." ;
8286
8387 public static final String IMAGES_ARE_DIFFERENT_SIZES = "Images are different sizes." ;
8488
@@ -145,85 +149,127 @@ public void update(float tpf){
145149 * - After all the frames have been taken it stops the application
146150 * - Compares the screenshot to the expected screenshot (if any). Fails the test if they are different
147151 */
148- public static void bootAppForTest (TestType testType , AppSettings appSettings , String baseImageFileName , List <Integer > framesToTakeScreenshotsOn , AppState ... initialStates ){
149- FastMath . rand . setSeed ( 0 ); //try to make things deterministic by setting the random seed
152+ public static void bootAppForTest (TestType testType , AppSettings appSettings , String baseImageFileName , List <Integer > framesToTakeScreenshotsOn , List < Scenario > scenarios ){
153+
150154 Collections .sort (framesToTakeScreenshotsOn );
151155
152- Path imageTempDir ;
156+ List <Path > tempFolders = new ArrayList <>();
157+ Map <Scenario , List <Path >> imageFilesPerScenario = new HashMap <>();
158+
159+ // usually there is a single scenario, but the framework can be set up to expect multiple scenarios that give identical results
160+ for (Scenario scenario : scenarios ) {
161+ FastMath .rand .setSeed (0 ); //try to make things deterministic by setting the random seed
162+ Path imageTempDir ;
163+ try {
164+ imageTempDir = Files .createTempDirectory ("jmeSnapshotTest" );
165+ } catch (IOException e ) {
166+ throw new RuntimeException (e );
167+ }
168+ tempFolders .add (imageTempDir );
153169
154- try {
155- imageTempDir = Files .createTempDirectory ("jmeSnapshotTest" );
156- } catch (IOException e ){
157- throw new RuntimeException (e );
158- }
170+ ScreenshotNoInputAppState screenshotAppState = new ScreenshotNoInputAppState (imageTempDir .toString () + "/" );
171+ String screenshotAppFileNamePrefix = "Screenshot-" ;
172+ screenshotAppState .setFileName (screenshotAppFileNamePrefix );
159173
160- ScreenshotNoInputAppState screenshotAppState = new ScreenshotNoInputAppState (imageTempDir .toString () + "/" );
161- String screenshotAppFileNamePrefix = "Screenshot-" ;
162- screenshotAppState .setFileName (screenshotAppFileNamePrefix );
174+ List <AppState > states = new ArrayList <>(Arrays .asList (scenario .states ));
175+ TestDriver testDriver = new TestDriver (screenshotAppState , framesToTakeScreenshotsOn );
176+ states .add (screenshotAppState );
177+ states .add (testDriver );
163178
164- List <AppState > states = new ArrayList <>(Arrays .asList (initialStates ));
165- TestDriver testDriver = new TestDriver (screenshotAppState , framesToTakeScreenshotsOn );
166- states .add (screenshotAppState );
167- states .add (testDriver );
179+ SimpleApplication app = new App (states .toArray (new AppState [0 ]));
180+ app .setSettings (appSettings );
181+ app .setShowSettings (false );
168182
169- SimpleApplication app = new App (states .toArray (new AppState [0 ]));
170- app .setSettings (appSettings );
171- app .setShowSettings (false );
183+ testDriver .waitLatch = new CountDownLatch (1 );
184+ executor .execute (() -> app .start (JmeContext .Type .Display ));
172185
173- testDriver .waitLatch = new CountDownLatch (1 );
174- executor .execute (() -> app .start (JmeContext .Type .Display ));
186+ int maxWaitTimeMilliseconds = 45000 ;
175187
176- int maxWaitTimeMilliseconds = 45000 ;
188+ try {
189+ boolean exitedProperly = testDriver .waitLatch .await (maxWaitTimeMilliseconds , TimeUnit .MILLISECONDS );
177190
178- try {
179- boolean exitedProperly = testDriver .waitLatch .await (maxWaitTimeMilliseconds , TimeUnit .MILLISECONDS );
191+ if (!exitedProperly ) {
192+ logger .warning ("Test driver did not exit in " + maxWaitTimeMilliseconds + "ms. Timed out" );
193+ app .stop (true );
194+ }
180195
181- if (!exitedProperly ){
182- logger .warning ("Test driver did not exit in " + maxWaitTimeMilliseconds + "ms. Timed out" );
183- app .stop (true );
196+ Thread .sleep (1000 ); //give time for openGL is fully released before starting a new test (get random JVM crashes without this)
197+ } catch (InterruptedException e ) {
198+ Thread .currentThread ().interrupt ();
199+ throw new RuntimeException (e );
184200 }
185201
186- Thread .sleep (1000 ); //give time for openGL is fully released before starting a new test (get random JVM crashes without this)
187- } catch (InterruptedException e ) {
188- Thread .currentThread ().interrupt ();
189- throw new RuntimeException (e );
190- }
202+ //search the imageTempDir
203+ List <Path > imageFiles = new ArrayList <>();
204+ try (Stream <Path > paths = Files .list (imageTempDir )) {
205+ paths .forEach (imageFiles ::add );
206+ } catch (IOException e ) {
207+ throw new RuntimeException (e );
208+ }
191209
192- //search the imageTempDir
193- List <Path > imageFiles = new ArrayList <>();
194- try (Stream <Path > paths = Files .list (imageTempDir )){
195- paths .forEach (imageFiles ::add );
196- } catch (IOException e ){
197- throw new RuntimeException (e );
198- }
210+ //this resorts with natural numeric ordering (so App10.png comes after App9.png)
211+ imageFiles .sort (new Comparator <Path >() {
212+ @ Override
213+ public int compare (Path p1 , Path p2 ) {
214+ return extractNumber (p1 ).compareTo (extractNumber (p2 ));
215+ }
199216
200- //this resorts with natural numeric ordering (so App10.png comes after App9.png)
201- imageFiles .sort (new Comparator <Path >(){
202- @ Override
203- public int compare (Path p1 , Path p2 ){
204- return extractNumber (p1 ).compareTo (extractNumber (p2 ));
217+ private Integer extractNumber (Path path ) {
218+ String name = path .getFileName ().toString ();
219+ int numStart = screenshotAppFileNamePrefix .length ();
220+ int numEnd = name .lastIndexOf (".png" );
221+ return Integer .parseInt (name .substring (numStart , numEnd ));
222+ }
223+ });
224+ if (imageFiles .isEmpty ()) {
225+ fail ("No screenshot found in the temporary directory. Did the application crash?" );
205226 }
206-
207- private Integer extractNumber (Path path ){
208- String name = path .getFileName ().toString ();
209- int numStart = screenshotAppFileNamePrefix .length ();
210- int numEnd = name .lastIndexOf (".png" );
211- return Integer .parseInt (name .substring (numStart , numEnd ));
227+ if (imageFiles .size () != framesToTakeScreenshotsOn .size ()) {
228+ fail ("Not all screenshots were taken, expected " + framesToTakeScreenshotsOn .size () + " but got " + imageFiles .size ());
212229 }
213- });
214230
215- if (imageFiles .isEmpty ()){
216- fail ("No screenshot found in the temporary directory. Did the application crash?" );
231+ imageFilesPerScenario .put (scenario , imageFiles );
217232 }
218- if (imageFiles .size () != framesToTakeScreenshotsOn .size ()){
219- fail ("Not all screenshots were taken, expected " + framesToTakeScreenshotsOn .size () + " but got " + imageFiles .size ());
220- }
221-
222233 String failureMessage = null ;
223234
224235 try {
236+ List <Path > primeScenarioScreenshots = imageFilesPerScenario .get (scenarios .get (0 ));
237+
238+ if (imageFilesPerScenario .size ()>1 ){
239+ String primeScenarioName = scenarios .get (0 ).scenarioName ;
240+
241+ // check each scenario gave the same results (before checking a single scenario against the reference images
242+ for (int i =1 ;i <imageFilesPerScenario .size ();i ++){
243+ String thisScenarioName = scenarios .get (i ).scenarioName ;
244+ List <Path > otherScenarioScreenshots = imageFilesPerScenario .get (scenarios .get (i ));
245+ for (int screenshotIndex =0 ;screenshotIndex <framesToTakeScreenshotsOn .size ();screenshotIndex ++) {
246+ Path primeImage = primeScenarioScreenshots .get (screenshotIndex );
247+ Path otherImage = otherScenarioScreenshots .get (screenshotIndex );
248+
249+ BufferedImage img1 = ImageIO .read (primeImage .toFile ());
250+ BufferedImage img2 = ImageIO .read (otherImage .toFile ());
251+
252+ int frame = framesToTakeScreenshotsOn .get (screenshotIndex );
253+
254+ String thisFrameBaseImageFileName = baseImageFileName + "_f" + frame ;
255+
256+ if (!imagesAreTheSame (img1 , img2 )) {
257+ attachImage ("Scenario " + primeScenarioName + " " + screenshotIndex , thisFrameBaseImageFileName + "_" + primeScenarioName + ".png" , img1 );
258+ attachImage ("Scenario " + thisScenarioName + " " + screenshotIndex , thisFrameBaseImageFileName + "_" + thisScenarioName + ".png" , img2 );
259+ attachImage ("Diff (between above scenarios)" , thisFrameBaseImageFileName + "_" + primeScenarioName + "_" + thisScenarioName + "_diff.png" , createComparisonImage (img1 , img2 ));
260+
261+ if (failureMessage ==null ){ //only want the first thing to go wrong as the junit test fail reason
262+ failureMessage = IMAGES_ARE_DIFFERENT_BETWEEN_SCENARIOS ;
263+ }
264+ ExtentReportExtension .getCurrentTest ().fail (IMAGES_ARE_DIFFERENT_BETWEEN_SCENARIOS );
265+ }
266+ }
267+ }
268+ }
269+
270+
225271 for (int screenshotIndex =0 ;screenshotIndex <framesToTakeScreenshotsOn .size ();screenshotIndex ++){
226- Path generatedImage = imageFiles .get (screenshotIndex );
272+ Path generatedImage = primeScenarioScreenshots .get (screenshotIndex );
227273 int frame = framesToTakeScreenshotsOn .get (screenshotIndex );
228274
229275 String thisFrameBaseImageFileName = baseImageFileName + "_f" + frame ;
@@ -280,7 +326,9 @@ private Integer extractNumber(Path path){
280326 } catch (IOException e ) {
281327 throw new RuntimeException ("Error reading images" , e );
282328 } finally {
283- clearTemporaryFolder (imageTempDir );
329+ for (Path imageTempDir : tempFolders ){
330+ clearTemporaryFolder (imageTempDir );
331+ }
284332 }
285333
286334 if (failureMessage !=null ){
0 commit comments