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