added .gitignore for all to enjoy
[testingSuite] / testSuite.ec
1 import "ecere"
2 import "EDA"
3 import "md5"
4 import "Project"
5 import "PathBox"
6
7 Test dummy; // FIXME ugly hack
8
9 GlobalSettings globalSettings { };
10
11 struct MD5Digest
12 {
13    byte digest [16];
14
15    void OnDisplay(Surface surface,
16                   int x, int y, int width,
17                   void * fieldData,
18                   Alignment alignment,
19                   DataDisplayFlags flags)
20    {
21       ColorKey keys[] = {{Color {digest[0] , digest[1] , digest[2] }, 0.0f},
22                          {Color {digest[3] , digest[4] , digest[5] }, 0.2f},
23                          {Color {digest[6] , digest[7] , digest[8] }, 0.4f},
24                          {Color {digest[9] , digest[10], digest[11]}, 0.6f},
25                          {Color {digest[12], digest[13], digest[14]}, 0.8f},
26                          {Color {digest[15], digest[0] , digest[1] }, 1.0f}};
27                         
28       surface.Gradient(keys, 6, 0, horizontal, x+1, y+1, x+34, y+14);
29       surface.Rectangle(x+1,y+1,x+34,y+14);
30    }
31
32    void OnSerialize(IOChannel channel)
33    {
34       channel.WriteData(digest,16);
35    }
36    void OnUnserialize(IOChannel channel)
37    {
38       channel.ReadData(digest,16);
39    }
40    property TestRun 
41    {
42       set {
43          TempFile hashTemp { };
44
45          Array<OutputFile> files = AccessMacros::allOutputFiles(value);
46          for(of : files)
47          {
48             File f = of.outputFile;
49             if(f)
50             {
51                while(!f.Eof())
52                {
53                   byte buffer[1024];
54                   uint count = f.Read(buffer,1,sizeof(buffer));
55                   hashTemp.Write(buffer, 1, count);
56                }
57                delete f;
58             }
59          }
60          {
61             uint size;
62             MD5_CTX ctx;
63             MD5Init(&ctx);
64             size = hashTemp.GetSize();
65             MD5Update(&ctx, hashTemp.buffer, size);
66             MD5Final(digest, &ctx);
67          }
68          delete hashTemp;
69          files.Free();
70          delete files;
71       }
72       // TOFIX: When this is commented out, no warning at conversion but conversion not acting as such!!
73       get { }
74    }
75 };
76
77 CompilerConfig defaultCompiler;
78
79 void CreateMakefile(String epjPath, String makePath)
80 {
81    Project project;
82    bool valid = true;
83    char* configName = null;//"release";
84
85    defaultCompiler = MakeDefaultCompiler("Default", true);
86    defaultCompiler.eccCommand = "ecc -nolinenumbers";
87
88    if(FileExists(epjPath).isFile)
89    {
90       char extension[MAX_EXTENSION] = "";
91       GetExtension(epjPath, extension);
92       strlwr(extension);
93       if(!strcmp(extension, ProjectExtension))
94       {
95          project = LoadProject(epjPath);
96          if(project)
97          {
98             ProjectConfig defaultConfig = null;
99             if(configName)
100             {
101                valid = false;
102                for(config : project.configurations)
103                {
104                   if(!strcmpi(configName, config.name))
105                   {
106                      project.config = config;
107                      valid = true;
108                      break;
109                   }
110                }
111                if(!valid)
112                   printf("Error: Project configuration (%s) was not found.\n", configName);
113             }
114             else
115             {
116                ProjectConfig releaseConfig;
117                for(config : project.configurations)
118                {
119                   if(!strcmpi(config.name, "Release"))
120                   {
121                      project.config = config;
122                      releaseConfig = config;
123                      break;
124                   }
125                }
126                if(!releaseConfig && project.configurations.count)
127                {
128                   project.config = project.configurations.firstIterator.data;
129                   /*
130                   releaseConfig = project.configs.first;
131                   releaseConfig.objDir.dir = "release";
132                   releaseConfig.targetDir.dir = "release";
133                   releaseConfig.optimize = forSpeed;
134                   releaseConfig.debug = false;
135                   releaseConfig.name = "Release";
136                   */
137                }
138
139                if(!releaseConfig)
140                {
141                   printf("Error: There are no Project configurations.\n", configName);
142                   valid = false;
143                   // we don't need to create a config to compile a config-less project, do we? 
144                   /*
145                   char targetName[MAX_FILENAME];
146                   GetLastDirectory(epjPath, targetName);
147                   StripExtension(targetName);
148                   defaultConfig = ProjectConfig
149                   {
150                      name = "Release";
151                      targetType = executable;
152                      targetName = targetName;
153                      defaultNameSpace = "";
154                      objDir.dir = "release";
155                      targetDir.dir = "release";
156                      optimize = forSpeed;
157                      debug = false;
158                      allWarnings = true;
159                      makingModified = true;
160                      compilingModified = true;
161                      linkingModified = true;
162                   };
163                   project.config = defaultConfig;
164                   */
165                }
166             }
167             if(valid)
168                project.GenerateMakefile(makePath, false, "");
169
170             delete defaultConfig;
171             delete project;
172          }
173       }
174    }
175 }
176
177
178 dbtable "Tests" Test
179 {
180    Test       id         "id";
181    String     name       "name";
182    String     filePath   "filePath";
183    TestRun    reference  "reference";
184    bool       active     "active";
185 }
186
187 enum OutputFileType
188 {
189    sym, imp, c, ec
190 };
191
192 dbtable "OutputFiles" OutputFile
193 {
194    OutputFile     id         "id";
195    File           outputFile "outputFile";
196    String         filePath   "filePath";
197    Test           test       "test";
198    TestRun        run        "run";
199    OutputFileType type       "type";
200    DateTime       created    "created";
201    dbindex run;
202 }
203
204 dbtable "Runs" TestRun
205 {
206    TestRun   id              "id";
207    File      makeOutput      "makeOutput";
208    bool      makeReturnValue "makeReturnValue";
209    Test      test            "test";
210    TimeStamp time            "time";
211    bool      save            "save";
212    MD5Digest digest          "digest";
213    dbindex test, time testAndTime;
214 }
215
216 Database db;
217
218 class AccessMacros
219 {
220    TestRun ::lastTestRun(Test t)
221    {
222       TestRun tr = 0;
223       Row tRow { tbl = dbindex("Runs", testAndTime) };
224       if(tRow.Find(dbfield("Runs",test),middle,nil,t)) {
225          tr = tRow.sysID;
226       }
227       delete tRow;
228       return tr;
229    }
230    Array<TestRun> ::allTestRuns(Test t)
231    {
232       Array<TestRun> runs { };
233       Row tRow { tbl = dbindex("Runs",testAndTime) };
234       for(tRow.Find(dbfield("Runs",test),middle,nil,t); !tRow.nil; tRow.Next())
235          runs.Add(tRow.sysID);
236       delete tRow;
237       return runs;
238    }
239    Array<OutputFile> ::allOutputFiles(TestRun t)
240    {
241       Array<OutputFile> files { };
242       RowOutputFiles tRow { tbl = dbindex("OutputFiles",run) };
243       for(tRow.Find(dbfield("OutputFiles",run),middle,nil,t); !tRow.nil; tRow.Next())
244       {
245          if(t != tRow.run)
246             break;
247          files.Add(tRow.sysID);
248       }
249       delete tRow;
250       return files;
251    }
252 }
253
254 class TestSuiteApp : GuiApplication
255 {
256    DataSource ds { driver = /*"SQLite"*/"EDB" };
257    TestSuiteApp()
258    {
259       db = database_open(ds, "TestSuite");
260    }
261    ~TestSuiteApp()
262    {
263       Array<OutputFile> files;
264       Array<TestRun> runs;
265       RowTests tests { };
266       RowRuns rRuns { };
267       RowOutputFiles rFiles { };
268       while(tests.Next())
269       {
270          Test t = (Test)tests.id;
271          runs = AccessMacros::allTestRuns(t);
272          for(run : runs)
273          {
274             if(!run.save)
275             {
276                if(t.reference == run)
277                {
278                   t.reference = null;
279                }
280                files = AccessMacros::allOutputFiles(run);
281                for(file : files)
282                {
283                   rFiles.sysID=file;
284                   rFiles.Delete();
285                }
286                rRuns.sysID=run;
287                rRuns.Delete();
288             }
289          }
290       }
291       delete tests;
292       delete rRuns;
293       delete rFiles;
294       delete db;
295       delete ds;
296    }
297 }
298
299 class TestSuiteGlobalSettings : GlobalAppSettings
300 {
301    settingsName = "testSuite";
302    char * diffTool;
303    char * testDB_file;
304    ~TestSuiteGlobalSettings()
305    {
306       delete diffTool;
307       delete testDB_file;
308    }
309    void OnAskReloadSettings()
310    {
311       Load();
312    }
313    void Load()
314    {
315       if(GlobalAppSettings::Load())
316       {
317          delete diffTool;
318          delete testDB_file;
319          GetGlobalValue("Tools","diff", singleString, &diffTool);
320          GetGlobalValue("Tests","list", singleString, $testDB_file);
321          CloseAndMonitor();
322       }
323    }
324    void Save()
325    {
326       if(GlobalAppSettings::Save())
327       {
328          PutGlobalValue("Tools","diff", singleString, diffTool);
329          PutGlobalValue("Tests","list", singleString, testDB_file);
330          CloseAndMonitor();
331       }
332    }
333 }
334
335 class DiffThread : Thread
336 {
337    char origDir[MAX_LOCATION];
338    char newDir[MAX_LOCATION];
339    
340    uint Main()
341    {
342       DualPipe diff;
343       FileListing flOrig { origDir, 0 };
344       FileListing flNew { newDir, 0 };
345       diff = DualPipeOpenf({ true }, "%s \"%s\" \"%s\"", theGlobalSettings.diffTool, origDir, newDir);
346       if(diff)
347       {
348          diff.Wait();
349       }
350       while(flOrig.Find())
351       {
352          DeleteFile(flOrig.path);
353       }
354       rmdir(origDir);
355       while(flNew.Find())
356       {
357          DeleteFile(flNew.path);
358       }
359       rmdir(newDir);
360       delete diff;
361       return 0;
362    }
363    void DiffDir(Array<OutputFile> outputOrig, Array<OutputFile> outputNew)
364    {
365       //char origDir[MAX_LOCATION];
366       //char newDir[MAX_LOCATION];
367       char tmpname[MAX_LOCATION];
368
369       CreateTemporaryDir(origDir, "TestSuiteOrigDir");
370       CreateTemporaryDir(newDir,"TestSuiteNewDir");
371
372       for(d : [ outputOrig, outputNew ])
373       {
374          for(of : d)
375          {
376             char tmpname[MAX_LOCATION];
377             char dummybuffer[MAX_LOCATION];
378             File outputFile = of.outputFile;
379             if(outputFile)
380             {
381                File tmp;
382                GetLastDirectory(of.filePath, dummybuffer);
383                strcpy(tmpname, (d == outputOrig) ? origDir : newDir);
384                PathCat(tmpname, dummybuffer);
385                tmp = FileOpen(tmpname, write);
386                if(tmp)
387                {
388                   CopyFile(outputFile, tmp);
389                   delete tmp;
390                }
391             }
392             delete outputFile;
393          }
394       }
395
396       Create();
397    }
398 }
399
400 class BuildTool
401 {
402    int GenerateMakefile(Test t)
403    {
404       DualPipe epj2makePipe;
405       char testFolder[MAX_LOCATION];
406       char makefileOut[MAX_LOCATION];
407       char epjName[MAX_LOCATION];
408       char * filePath = t.filePath;
409       
410       StripLastDirectory(filePath, testFolder);
411       PathCat(testFolder, "release");
412       MakeDir(testFolder);
413       
414       StripLastDirectory(filePath, testFolder);
415       GetLastDirectory(filePath, epjName);
416       strcpy(makefileOut, testFolder);
417       PathCat(makefileOut, "makefile.test");
418       
419       // epj2makePipe = DualPipeOpenf({ output = true },"epj2make -l /usr/ecere/lib -o %s %s > /dev/null", makefileOut, t.filePath);
420       //epj2makePipe = DualPipeOpenf({ output = true },"epj2make -cpp cpp -l /usr/ecere/lib -o %s %s", makefileOut, filePath);
421       //epj2makePipe.Wait();
422       CreateMakefile(filePath, makefileOut);
423
424       delete filePath;
425       //return epj2makePipe.GetExitCode();
426       return 0;
427    }
428    
429    char* Copy2Temp(File f, String tmpPath)
430    {
431       File tmp;
432       char tmpFilename[MAX_LOCATION];
433       tmp = CreateTemporaryFile(tmpFilename, tmpPath);
434       CopyFile(f, tmp);
435       tmp.Flush();
436       return tmpFilename;
437    }
438    
439    int BuildTest(TestRun run)
440    {
441       DualPipe makePipe;
442       int makeSuccess;
443       char makefileOut[MAX_LOCATION];
444       char * filePath = run.test.filePath;
445       char oldDir[MAX_LOCATION];
446       StripLastDirectory(filePath, makefileOut);
447
448       GetWorkingDir(oldDir, sizeof(oldDir));
449       ChangeWorkingDir(makefileOut);
450       PathCat(makefileOut, "makefile.test");
451       
452       //Execute("make -f %s clean", makefileOut);
453
454       makePipe = DualPipeOpenf({ output = true },"make -f %s clean", makefileOut);
455       {
456          TempFile f { };
457          char makeBuffer[1024];
458          int size=1024;
459          uint count;
460          while(!makePipe.Eof())
461          {
462             count = makePipe.Read(makeBuffer,1,sizeof(makeBuffer));
463             f.Write(makeBuffer, 1, count);
464          }
465          run.makeOutput = f;
466       }
467       delete makePipe;
468
469       makePipe = DualPipeOpenf({ output = true },"make -f %s", makefileOut);
470       {
471          TempFile f { };
472          char makeBuffer[1024];
473          int size=1024;
474          uint count;
475          while(!makePipe.Eof())
476          {
477             count = makePipe.Read(makeBuffer,1,sizeof(makeBuffer));
478             f.Write(makeBuffer, 1, count);
479          }
480          run.makeOutput = f;
481       }
482       //makePipe.Wait();
483       ChangeWorkingDir(oldDir);
484       makeSuccess = makePipe.GetExitCode();
485       run.makeReturnValue = makeSuccess;
486       return makeSuccess;
487    }
488    void AddOutputFiles(TestRun run)
489    {
490       char testFolder[MAX_LOCATION];
491       FileListing fl;
492       char * filePath = run.test.filePath;
493       StripLastDirectory(filePath, testFolder);
494       PathCat(testFolder, "release");
495       
496       fl = { testFolder, extensions = "c, ec, sym, imp" }; // ["c", "ec", "sym", "imp"] perhaps?
497       while(fl.Find())
498       {
499          File f;
500          RowOutputFiles of { };
501          char extension[MAX_LOCATION];
502          DateTime rightNow { };
503          f = FileOpen(fl.path, read);
504          
505          of.Add();
506          of.id = of.sysID;
507          of.outputFile = f;
508          of.filePath = fl.path;
509          of.test = run.test;
510          of.run = run;
511          GetExtension(fl.path, extension);
512          if(!strcmp(extension,"c"))
513          {
514             of.type = c;
515          }
516          else if(!strcmp(extension,"ec"))
517          {
518             of.type = ec;
519          }
520          else if(!strcmp(extension,"sym"))
521          {
522             of.type = sym;
523          }
524          else if(!strcmp(extension,"imp"))
525          {
526             of.type = imp;
527          }
528          rightNow.GetLocalTime();
529          of.created = (TimeStamp)rightNow;
530          delete f;
531       }
532       delete filePath;
533    }
534 }
535
536 void CopyFile(File input, File output)
537 {
538    byte buffer[65536];
539    input.Seek(0, start);
540    for(;!input.Eof();)
541    {
542       uint count = input.Read(buffer, 1, sizeof(buffer));
543       if(count)
544       {
545          int yo = output.Write(buffer, 1, count);
546       }
547    }
548 }
549
550 class RelativeTimeStamp : TimeStamp
551 {
552    char * OnGetString(char * tempString, void * fieldData, bool * needClass)
553    {
554       int64 count;
555       int days;
556       String units;
557       DateTime dt { };
558       TimeStamp rightNow;
559       dt.GetLocalTime();
560       rightNow = (TimeStamp)dt;
561       count = (int64)(rightNow - (TimeStamp)this);
562       if(count >= 60)
563       {
564          count /= 60;
565          if(count >= 60)
566          {
567             count /= 60;
568             if(count >= 24)
569             {
570                count /= 24;
571                if(count >= 7)
572                {
573                   days = (int)count;
574                   count /= 7;
575                   if(days >= 30)
576                   {
577                      count = days / 30;
578                      if(days >= 365)
579                      {
580                         count = days / 365;
581                         units = "year";
582                      }
583                      else
584                      {
585                         units = "month";
586                      }
587                   }
588                   else
589                   {
590                      units = "week";
591                   }
592                }
593                else
594                {
595                   units = "day";
596                }
597             }
598             else
599             {
600                units = "hour";
601             }
602          }
603          else
604          {
605             units = "minute";
606          }
607       }
608       else
609       {
610          count = 0;
611       }
612       if(count == 0)
613       {
614          PrintBuf(tempString, 1024, "<1 minute ago");
615       }
616       else
617       {
618          PrintBuf(tempString, 1024, count, " ", units, (count > 1)? "s" : "", " ago");
619       }
620       return tempString;
621    }
622 }
623
624 enum BuildStatus
625 {
626    success, failure;
627    void OnDisplay(Surface surface,
628                   int x, int y, int width,
629                   TrumpDialog trumpDialog,
630                   Alignment alignment,
631                   DataDisplayFlags flags)
632    {
633       Bitmap b;
634       if(this==success)
635       {
636          b = theTestSuiteWindow.makeSuccess.bitmap;
637       }
638       else
639       {
640          b = theTestSuiteWindow.makeFailure.bitmap;
641       }
642       surface.Blit(b, x, y, 0, 0, b.width, b.height);
643    }
644 };
645
646 class TestSuiteWindow : Window
647 {
648    text = "Ecere Compiler Tests";
649    background = activeBorder;
650    borderStyle = sizable;
651    hasMaximize = true;
652    hasMinimize = true;
653    hasClose = true;
654    size = { 520, 600 };
655    anchor = { horz = -3, vert = 80 };
656
657    BitmapResource makeSuccess { ":check.png" , window = this };
658    BitmapResource makeFailure { ":x.png", window = this };
659    
660    TestSuiteWindow()
661    {
662       testListBox.AddField(fieldName);
663       testListBox.AddField(fieldActive);
664       
665       runsListBox.AddField(fieldBuildStatus);
666       runsListBox.AddField(fieldAuraColor);
667       runsListBox.AddField(fieldRun);
668       runsListBox.AddField(fieldSaved);
669       // LoadTests(); FIXME this causes a segfault (but not OnCreate)
670       Print("starting up...\n");
671    }
672
673    PaneSplitter windowPanes
674    {
675       this, leftPane = testListBox, rightPane = runSection, split = 200;
676    };
677    Window runSection
678    {
679       this, anchor = { top = 0, bottom = 30, left = 0, right = 0 };
680    };
681    PaneSplitter runPanes
682    {
683       runSection, leftPane = dataBox, rightPane = runsListBox, split = 100, orientation = horizontal;
684    };
685
686    Timer timer
687    {
688       this,
689       delay = 30,
690       started = true;
691
692       bool DelayExpired()
693       {
694          runsListBox.Update(null);
695          return true;
696       }
697    };
698
699    void NewTest(RowTests tRow, char* testName, char* testFilePath)
700    {
701       tRow.Add();
702       tRow.id = tRow.sysID;
703       tRow.active=true;
704       tRow.name=testName;
705       tRow.filePath = testFilePath;
706    }
707
708    DataRow AddTest(RowTests tRow)
709    {
710       DataRow row;
711       String s;
712       row = testListBox.AddRow();
713       row.tag = tRow.id;
714       s = tRow.name; row.SetData(fieldName, s); delete s;
715       row.SetData(fieldActive, tRow.active);
716       return row;
717    }
718
719    void DeleteTest(ListBox testListBox, DataRow row)
720    {
721       // do we also delete all runs and files associated with this test?
722       RowTests tRow { };
723       tRow.sysID=row.tag;
724       tRow.Delete();
725       delete tRow; // won't be needed in the (distant?) future :-)
726       testListBox.DeleteRow(row);
727    }
728
729    void LoadTests()
730    {
731       RowTests tRow { };
732       while(tRow.Next())
733       {
734          theTestSuiteWindow.AddTest(tRow);
735       }
736       delete tRow;
737    }
738    
739    DataField fieldName   { width = 140, header = "Name"  , dataType = class(char*), editable = true };
740    DataField fieldActive { width = 40,  header = "Active", dataType = class(bool)                   };
741
742    ListBox testListBox
743    {
744       this,
745       anchor = { top = 2, bottom = 30, left = 2, right = 2 },
746       hasHeader = true;
747
748       bool NotifyChanged(ListBox testListBox, DataRow row)
749       {
750          RowTests tRow { };
751          tRow.sysID=row.tag;
752          tRow.name = row.GetData(fieldName);
753          tRow.active = row.GetData(fieldActive);
754          delete tRow;
755          return true;
756       }
757
758       bool NotifyKeyDown(ListBox testListBox, DataRow row, Key key, unichar ch)
759       {
760          if(key == del)
761          {
762             DeleteTest(testListBox, row);
763             // bRemove.NotifyClicked(this, bRemove,0,0,0);
764          }
765          else if(key == space)
766          {
767             //bool newActiveStatus = !(*(bool*)row.GetData(fieldActive)); // FIXME is needed?
768             row.SetData(fieldActive, !row.GetData(fieldActive));
769             testListBox.NotifyChanged(this, testListBox, row);
770             return false;
771          }
772          return true;
773       }
774
775       bool NotifySelect(ListBox listBox, DataRow row, Modifiers mods)
776       {
777          dataBox.Clear();
778          if(row)
779          {
780             { // print to data box
781                Test t;
782                char* tname;
783                char* tpath;
784                bool tactive;
785                t = (Test)row.tag;
786                tname = t.name;
787                tpath = t.filePath;
788                tactive = t.active;
789                dataBox.Printf("Name: %s\n", tname);
790                dataBox.Printf("Location: %s\n", tpath);
791                dataBox.Printf("Active: %d\n", tactive);
792                delete tname; delete tpath;
793             }
794             { // show test runs
795                Test t;
796                Array<TestRun> runs;
797                AccessMacros amacs{ };
798                t = (Test)row.tag;
799                runs = amacs.allTestRuns(t);
800                runsListBox.Clear();
801                for(tr : runs)
802                {
803                   DataRow row;
804                   BuildStatus buildStatus;
805                   TimeStamp time = tr.time;
806                   MD5Digest aura = tr.digest;
807                   buildStatus = tr.makeReturnValue ? failure : success;
808                   row = runsListBox.AddRow();
809                   row.tag = tr;
810                   row.SetData(fieldBuildStatus, buildStatus);
811                   row.SetData(fieldAuraColor, aura);
812                   row.SetData(fieldRun, time);
813                   row.SetData(fieldSaved, tr.save);
814                }
815             }
816          }
817          return true;
818       }
819    };
820    
821    DataField fieldBuildStatus { width = 40,  dataType = class(BuildStatus),       header = "Make" };
822    DataField fieldAuraColor   { width = 40,  dataType = class(MD5Digest),         header = "Aura" };
823    DataField fieldRun         { width = 120, dataType = class(RelativeTimeStamp), header = "Run" };
824    DataField fieldSaved       { width = 80,  dataType = class(bool),              header = "Save", editable = true };
825
826    ListBox runsListBox
827    {
828       parent = runSection,
829       master=this,
830       anchor = { top = 0, bottom = 0, left = 0, right = 0 },
831       hasHeader = true;
832
833       bool NotifyChanged(ListBox listBox, DataRow row)
834       {
835          TestRun tr = (TestRun)row.tag;
836          tr.save = row.GetData(fieldSaved);
837          return true;
838       }
839    };
840    EditBox dataBox
841    {
842       parent = runSection,
843       master=this,
844       opacity = 0,
845       inactive = true,
846       anchor = { top = 0, bottom = 0, left = 0, right = 0 },
847       hasHorzScroll = true,
848       readOnly = true,
849       multiLine = true,
850       noCaret = true
851    };
852    Button addTest
853    {
854       parent=this,
855       text = "Add",
856       hotKey=altA,
857       anchor = { bottom = 2, left = 2 };
858
859       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
860       {
861          FileFilter projectFilters[] =
862             {
863                { "eC project and source files (.ec, .epj)", "ec, epj" }
864             };
865          FileDialog f { filters = projectFilters, sizeFilters = sizeof(projectFilters), type = multiOpen };
866          incref f;
867          if(f.Modal()== ok)
868          {
869             int i;
870             RowTests tRow { };
871             for(i=0; i<f.numSelections; ++i)
872             {
873                char testName[MAX_LOCATION];
874                DataRow row;
875                GetLastDirectory(f.multiFilePaths[i], testName);
876                NewTest(tRow, testName, f.multiFilePaths[i]);
877                row = AddTest(tRow);
878                if(i+1 == f.numSelections)
879                {
880                   testListBox.SelectRow(row);
881                }
882             } 
883          }
884          delete f;
885          //Refocus();
886          return true;
887       }
888    };
889    Button runTests
890    {
891       parent = this,
892       text = "Run",
893       hotKey = altR,
894       anchor = { bottom = 2, right = 2 };
895
896       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
897       {
898          DataRow row;
899          for(row=testListBox.firstRow; row; row=row.next)
900          {
901             Test t = (Test)row.tag;
902             if(!t.active)
903             {
904                continue;
905             }
906             else
907             {
908                BuildTool bt { };
909                DiffThread dt { };
910                DateTime rightNow { };
911                RowRuns runs { };
912                TestRun run;
913                runs.Add();
914                runs.id = runs.sysID;
915                rightNow.GetLocalTime();
916                runs.time = (TimeStamp)rightNow;
917                runs.test = t;
918                run = runs.id;
919                bt.GenerateMakefile(t);
920                bt.BuildTest(run);
921                bt.AddOutputFiles(run);
922                runs.digest = run;
923                if(!t.reference)
924                {
925                   t.reference = run;
926                }
927                else
928                {
929                   DiffThread dt { };
930                   Array<OutputFile> outputsLatest = AccessMacros::allOutputFiles(run);
931                   Array<OutputFile> outputsReference = AccessMacros::allOutputFiles(t.reference);
932                   dt.DiffDir(outputsReference, outputsLatest);
933                }
934             }
935          testListBox.NotifySelect(this, testListBox, testListBox.currentRow, mods);
936          }
937          return true;
938       }
939    };
940    Button acceptTest
941    {
942       parent=this,
943       text = "Accept Run (x)",
944       hotKey=altX,
945       anchor={ bottom = 2, right = 52 };
946
947       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
948       {
949          DataRow row;
950          MessageBox confirmation
951          {
952             type = yesNo,
953             text = "Accept Test",
954             contents = "Accept the selected test?"
955          };
956          if(confirmation.Modal() == yes)
957          {
958             TestRun tr;
959             row = runsListBox.currentRow;
960             if(row)
961             {
962                tr = (TestRun)row.tag;
963                tr.save = true;
964                tr.test.reference = tr;
965             }
966          }
967          return true;
968       }
969    }
970    bool OnCreate()
971    {
972       globalSettings.Load();
973
974       LoadTests();
975       delete theGlobalSettings.diffTool;
976       theGlobalSettings.diffTool = CopyString("meld");
977       // theGlobalSettings.diffTool = CopyString("C:/Program Files/Araxis/Araxis Merge v6.5/compare.exe /wait");
978       return true;
979    }
980 }
981
982 TestSuiteWindow theTestSuiteWindow { };
983 TestSuiteGlobalSettings theGlobalSettings { };