Initial Git commit (A bit rusty... please fix, test runs fail right now)
authorJerome St-Louis <jerome@ecere.com>
Tue, 17 May 2011 05:00:49 +0000 (01:00 -0400)
committerJerome St-Louis <jerome@ecere.com>
Tue, 17 May 2011 05:00:49 +0000 (01:00 -0400)
res/check.png [new file with mode: 0644]
res/x.png [new file with mode: 0644]
testSuite.ec [new file with mode: 0644]
testSuite.epj [new file with mode: 0644]

diff --git a/res/check.png b/res/check.png
new file mode 100644 (file)
index 0000000..efde215
Binary files /dev/null and b/res/check.png differ
diff --git a/res/x.png b/res/x.png
new file mode 100644 (file)
index 0000000..9438c2b
Binary files /dev/null and b/res/x.png differ
diff --git a/testSuite.ec b/testSuite.ec
new file mode 100644 (file)
index 0000000..4f08ac1
--- /dev/null
@@ -0,0 +1,983 @@
+import "ecere"
+import "EDA"
+import "md5"
+import "Project"
+import "PathBox"
+
+Test dummy; // FIXME ugly hack
+
+GlobalSettings globalSettings { };
+
+struct MD5Digest
+{
+   byte digest [16];
+
+   void OnDisplay(Surface surface,
+                  int x, int y, int width,
+                  void * fieldData,
+                  Alignment alignment,
+                  DataDisplayFlags flags)
+   {
+      ColorKey keys[] = {{Color {digest[0] , digest[1] , digest[2] }, 0.0f},
+                         {Color {digest[3] , digest[4] , digest[5] }, 0.2f},
+                         {Color {digest[6] , digest[7] , digest[8] }, 0.4f},
+                         {Color {digest[9] , digest[10], digest[11]}, 0.6f},
+                         {Color {digest[12], digest[13], digest[14]}, 0.8f},
+                         {Color {digest[15], digest[0] , digest[1] }, 1.0f}};
+                        
+      surface.Gradient(keys, 6, 0, horizontal, x+1, y+1, x+34, y+14);
+      surface.Rectangle(x+1,y+1,x+34,y+14);
+   }
+
+   void OnSerialize(IOChannel channel)
+   {
+      channel.WriteData(digest,16);
+   }
+   void OnUnserialize(IOChannel channel)
+   {
+      channel.ReadData(digest,16);
+   }
+   property TestRun 
+   {
+      set {
+         TempFile hashTemp { };
+
+         Array<OutputFile> files = AccessMacros::allOutputFiles(value);
+         for(of : files)
+         {
+            File f = of.outputFile;
+            if(f)
+            {
+               while(!f.Eof())
+               {
+                  byte buffer[1024];
+                  uint count = f.Read(buffer,1,sizeof(buffer));
+                  hashTemp.Write(buffer, 1, count);
+               }
+               delete f;
+            }
+         }
+         {
+            uint size;
+            MD5_CTX ctx;
+            MD5Init(&ctx);
+            size = hashTemp.GetSize();
+            MD5Update(&ctx, hashTemp.buffer, size);
+            MD5Final(digest, &ctx);
+         }
+         delete hashTemp;
+         files.Free();
+         delete files;
+      }
+      // TOFIX: When this is commented out, no warning at conversion but conversion not acting as such!!
+      get { }
+   }
+};
+
+CompilerConfig defaultCompiler;
+
+void CreateMakefile(String epjPath, String makePath)
+{
+   Project project;
+   bool valid = true;
+   char* configName = null;//"release";
+
+   defaultCompiler = MakeDefaultCompiler("Default", true);
+   defaultCompiler.eccCommand = "ecc -nolinenumbers";
+
+   if(FileExists(epjPath).isFile)
+   {
+      char extension[MAX_EXTENSION] = "";
+      GetExtension(epjPath, extension);
+      strlwr(extension);
+      if(!strcmp(extension, ProjectExtension))
+      {
+         project = LoadProject(epjPath);
+         if(project)
+         {
+            ProjectConfig defaultConfig = null;
+            if(configName)
+            {
+               valid = false;
+               for(config : project.configurations)
+               {
+                  if(!strcmpi(configName, config.name))
+                  {
+                     project.config = config;
+                     valid = true;
+                     break;
+                  }
+               }
+               if(!valid)
+                  printf("Error: Project configuration (%s) was not found.\n", configName);
+            }
+            else
+            {
+               ProjectConfig releaseConfig;
+               for(config : project.configurations)
+               {
+                  if(!strcmpi(config.name, "Release"))
+                  {
+                     project.config = config;
+                     releaseConfig = config;
+                     break;
+                  }
+               }
+               if(!releaseConfig && project.configurations.count)
+               {
+                  project.config = project.configurations.firstIterator.data;
+                  /*
+                  releaseConfig = project.configs.first;
+                  releaseConfig.objDir.dir = "release";
+                  releaseConfig.targetDir.dir = "release";
+                  releaseConfig.optimize = forSpeed;
+                  releaseConfig.debug = false;
+                  releaseConfig.name = "Release";
+                  */
+               }
+
+               if(!releaseConfig)
+               {
+                  printf("Error: There are no Project configurations.\n", configName);
+                  valid = false;
+                  // we don't need to create a config to compile a config-less project, do we? 
+                  /*
+                  char targetName[MAX_FILENAME];
+                  GetLastDirectory(epjPath, targetName);
+                  StripExtension(targetName);
+                  defaultConfig = ProjectConfig
+                  {
+                     name = "Release";
+                     targetType = executable;
+                     targetName = targetName;
+                     defaultNameSpace = "";
+                     objDir.dir = "release";
+                     targetDir.dir = "release";
+                     optimize = forSpeed;
+                     debug = false;
+                     allWarnings = true;
+                     makingModified = true;
+                     compilingModified = true;
+                     linkingModified = true;
+                  };
+                  project.config = defaultConfig;
+                  */
+               }
+            }
+            if(valid)
+               project.GenerateMakefile(makePath, false, "");
+
+            delete defaultConfig;
+            delete project;
+         }
+      }
+   }
+}
+
+
+dbtable "Tests" Test
+{
+   Test       id         "id";
+   String     name       "name";
+   String     filePath   "filePath";
+   TestRun    reference  "reference";
+   bool       active     "active";
+}
+
+enum OutputFileType
+{
+   sym, imp, c, ec
+};
+
+dbtable "OutputFiles" OutputFile
+{
+   OutputFile     id         "id";
+   File           outputFile "outputFile";
+   String         filePath   "filePath";
+   Test           test       "test";
+   TestRun        run        "run";
+   OutputFileType type       "type";
+   DateTime       created    "created";
+   dbindex run;
+}
+
+dbtable "Runs" TestRun
+{
+   TestRun   id              "id";
+   File      makeOutput      "makeOutput";
+   bool      makeReturnValue "makeReturnValue";
+   Test      test            "test";
+   TimeStamp time            "time";
+   bool      save            "save";
+   MD5Digest digest          "digest";
+   dbindex test, time testAndTime;
+}
+
+Database db;
+
+class AccessMacros
+{
+   TestRun ::lastTestRun(Test t)
+   {
+      TestRun tr = 0;
+      Row tRow { tbl = dbindex("Runs", testAndTime) };
+      if(tRow.Find(dbfield("Runs",test),middle,nil,t)) {
+         tr = tRow.sysID;
+      }
+      delete tRow;
+      return tr;
+   }
+   Array<TestRun> ::allTestRuns(Test t)
+   {
+      Array<TestRun> runs { };
+      Row tRow { tbl = dbindex("Runs",testAndTime) };
+      for(tRow.Find(dbfield("Runs",test),middle,nil,t); !tRow.nil; tRow.Next())
+         runs.Add(tRow.sysID);
+      delete tRow;
+      return runs;
+   }
+   Array<OutputFile> ::allOutputFiles(TestRun t)
+   {
+      Array<OutputFile> files { };
+      RowOutputFiles tRow { tbl = dbindex("OutputFiles",run) };
+      for(tRow.Find(dbfield("OutputFiles",run),middle,nil,t); !tRow.nil; tRow.Next())
+      {
+         if(t != tRow.run)
+            break;
+         files.Add(tRow.sysID);
+      }
+      delete tRow;
+      return files;
+   }
+}
+
+class TestSuiteApp : GuiApplication
+{
+   DataSource ds { driver = /*"SQLite"*/"EDB" };
+   TestSuiteApp()
+   {
+      db = database_open(ds, "TestSuite");
+   }
+   ~TestSuiteApp()
+   {
+      Array<OutputFile> files;
+      Array<TestRun> runs;
+      RowTests tests { };
+      RowRuns rRuns { };
+      RowOutputFiles rFiles { };
+      while(tests.Next())
+      {
+         Test t = (Test)tests.id;
+         runs = AccessMacros::allTestRuns(t);
+         for(run : runs)
+         {
+            if(!run.save)
+            {
+               if(t.reference == run)
+               {
+                  t.reference = null;
+               }
+               files = AccessMacros::allOutputFiles(run);
+               for(file : files)
+               {
+                  rFiles.sysID=file;
+                  rFiles.Delete();
+               }
+               rRuns.sysID=run;
+               rRuns.Delete();
+            }
+         }
+      }
+      delete tests;
+      delete rRuns;
+      delete rFiles;
+      delete db;
+      delete ds;
+   }
+}
+
+class TestSuiteGlobalSettings : GlobalAppSettings
+{
+   settingsName = "testSuite";
+   char * diffTool;
+   char * testDB_file;
+   ~TestSuiteGlobalSettings()
+   {
+      delete diffTool;
+      delete testDB_file;
+   }
+   void OnAskReloadSettings()
+   {
+      Load();
+   }
+   void Load()
+   {
+      if(GlobalAppSettings::Load())
+      {
+         delete diffTool;
+         delete testDB_file;
+         GetGlobalValue("Tools","diff", singleString, &diffTool);
+         GetGlobalValue("Tests","list", singleString, $testDB_file);
+         CloseAndMonitor();
+      }
+   }
+   void Save()
+   {
+      if(GlobalAppSettings::Save())
+      {
+         PutGlobalValue("Tools","diff", singleString, diffTool);
+         PutGlobalValue("Tests","list", singleString, testDB_file);
+         CloseAndMonitor();
+      }
+   }
+}
+
+class DiffThread : Thread
+{
+   char origDir[MAX_LOCATION];
+   char newDir[MAX_LOCATION];
+   
+   uint Main()
+   {
+      DualPipe diff;
+      FileListing flOrig { origDir, 0 };
+      FileListing flNew { newDir, 0 };
+      diff = DualPipeOpenf({ true }, "%s \"%s\" \"%s\"", theGlobalSettings.diffTool, origDir, newDir);
+      if(diff)
+      {
+         diff.Wait();
+      }
+      while(flOrig.Find())
+      {
+         DeleteFile(flOrig.path);
+      }
+      rmdir(origDir);
+      while(flNew.Find())
+      {
+         DeleteFile(flNew.path);
+      }
+      rmdir(newDir);
+      delete diff;
+      return 0;
+   }
+   void DiffDir(Array<OutputFile> outputOrig, Array<OutputFile> outputNew)
+   {
+      //char origDir[MAX_LOCATION];
+      //char newDir[MAX_LOCATION];
+      char tmpname[MAX_LOCATION];
+
+      CreateTemporaryDir(origDir, "TestSuiteOrigDir");
+      CreateTemporaryDir(newDir,"TestSuiteNewDir");
+
+      for(d : [ outputOrig, outputNew ])
+      {
+         for(of : d)
+         {
+            char tmpname[MAX_LOCATION];
+            char dummybuffer[MAX_LOCATION];
+            File outputFile = of.outputFile;
+            if(outputFile)
+            {
+               File tmp;
+               GetLastDirectory(of.filePath, dummybuffer);
+               strcpy(tmpname, (d == outputOrig) ? origDir : newDir);
+               PathCat(tmpname, dummybuffer);
+               tmp = FileOpen(tmpname, write);
+               if(tmp)
+               {
+                  CopyFile(outputFile, tmp);
+                  delete tmp;
+               }
+            }
+            delete outputFile;
+         }
+      }
+
+      Create();
+   }
+}
+
+class BuildTool
+{
+   int GenerateMakefile(Test t)
+   {
+      DualPipe epj2makePipe;
+      char testFolder[MAX_LOCATION];
+      char makefileOut[MAX_LOCATION];
+      char epjName[MAX_LOCATION];
+      char * filePath = t.filePath;
+      
+      StripLastDirectory(filePath, testFolder);
+      PathCat(testFolder, "release");
+      MakeDir(testFolder);
+      
+      StripLastDirectory(filePath, testFolder);
+      GetLastDirectory(filePath, epjName);
+      strcpy(makefileOut, testFolder);
+      PathCat(makefileOut, "makefile.test");
+      
+      // epj2makePipe = DualPipeOpenf({ output = true },"epj2make -l /usr/ecere/lib -o %s %s > /dev/null", makefileOut, t.filePath);
+      //epj2makePipe = DualPipeOpenf({ output = true },"epj2make -cpp cpp -l /usr/ecere/lib -o %s %s", makefileOut, filePath);
+      //epj2makePipe.Wait();
+      CreateMakefile(filePath, makefileOut);
+
+      delete filePath;
+      //return epj2makePipe.GetExitCode();
+      return 0;
+   }
+   
+   char* Copy2Temp(File f, String tmpPath)
+   {
+      File tmp;
+      char tmpFilename[MAX_LOCATION];
+      tmp = CreateTemporaryFile(tmpFilename, tmpPath);
+      CopyFile(f, tmp);
+      tmp.Flush();
+      return tmpFilename;
+   }
+   
+   int BuildTest(TestRun run)
+   {
+      DualPipe makePipe;
+      int makeSuccess;
+      char makefileOut[MAX_LOCATION];
+      char * filePath = run.test.filePath;
+      char oldDir[MAX_LOCATION];
+      StripLastDirectory(filePath, makefileOut);
+
+      GetWorkingDir(oldDir, sizeof(oldDir));
+      ChangeWorkingDir(makefileOut);
+      PathCat(makefileOut, "makefile.test");
+      
+      //Execute("make -f %s clean", makefileOut);
+
+      makePipe = DualPipeOpenf({ output = true },"make -f %s clean", makefileOut);
+      {
+         TempFile f { };
+         char makeBuffer[1024];
+         int size=1024;
+         uint count;
+         while(!makePipe.Eof())
+         {
+            count = makePipe.Read(makeBuffer,1,sizeof(makeBuffer));
+            f.Write(makeBuffer, 1, count);
+         }
+         run.makeOutput = f;
+      }
+      delete makePipe;
+
+      makePipe = DualPipeOpenf({ output = true },"make -f %s", makefileOut);
+      {
+         TempFile f { };
+         char makeBuffer[1024];
+         int size=1024;
+         uint count;
+         while(!makePipe.Eof())
+         {
+            count = makePipe.Read(makeBuffer,1,sizeof(makeBuffer));
+            f.Write(makeBuffer, 1, count);
+         }
+         run.makeOutput = f;
+      }
+      //makePipe.Wait();
+      ChangeWorkingDir(oldDir);
+      makeSuccess = makePipe.GetExitCode();
+      run.makeReturnValue = makeSuccess;
+      return makeSuccess;
+   }
+   void AddOutputFiles(TestRun run)
+   {
+      char testFolder[MAX_LOCATION];
+      FileListing fl;
+      char * filePath = run.test.filePath;
+      StripLastDirectory(filePath, testFolder);
+      PathCat(testFolder, "release");
+      
+      fl = { testFolder, extensions = "c, ec, sym, imp" }; // ["c", "ec", "sym", "imp"] perhaps?
+      while(fl.Find())
+      {
+         File f;
+         RowOutputFiles of { };
+         char extension[MAX_LOCATION];
+         DateTime rightNow { };
+         f = FileOpen(fl.path, read);
+         
+         of.Add();
+         of.id = of.sysID;
+         of.outputFile = f;
+         of.filePath = fl.path;
+         of.test = run.test;
+         of.run = run;
+         GetExtension(fl.path, extension);
+         if(!strcmp(extension,"c"))
+         {
+            of.type = c;
+         }
+         else if(!strcmp(extension,"ec"))
+         {
+            of.type = ec;
+         }
+         else if(!strcmp(extension,"sym"))
+         {
+            of.type = sym;
+         }
+         else if(!strcmp(extension,"imp"))
+         {
+            of.type = imp;
+         }
+         rightNow.GetLocalTime();
+         of.created = (TimeStamp)rightNow;
+         delete f;
+      }
+      delete filePath;
+   }
+}
+
+void CopyFile(File input, File output)
+{
+   byte buffer[65536];
+   input.Seek(0, start);
+   for(;!input.Eof();)
+   {
+      uint count = input.Read(buffer, 1, sizeof(buffer));
+      if(count)
+      {
+         int yo = output.Write(buffer, 1, count);
+      }
+   }
+}
+
+class RelativeTimeStamp : TimeStamp
+{
+   char * OnGetString(char * tempString, void * fieldData, bool * needClass)
+   {
+      int64 count;
+      int days;
+      String units;
+      DateTime dt { };
+      TimeStamp rightNow;
+      dt.GetLocalTime();
+      rightNow = (TimeStamp)dt;
+      count = (int64)(rightNow - (TimeStamp)this);
+      if(count >= 60)
+      {
+         count /= 60;
+         if(count >= 60)
+         {
+            count /= 60;
+            if(count >= 24)
+            {
+               count /= 24;
+               if(count >= 7)
+               {
+                  days = (int)count;
+                  count /= 7;
+                  if(days >= 30)
+                  {
+                     count = days / 30;
+                     if(days >= 365)
+                     {
+                        count = days / 365;
+                        units = "year";
+                     }
+                     else
+                     {
+                        units = "month";
+                     }
+                  }
+                  else
+                  {
+                     units = "week";
+                  }
+               }
+               else
+               {
+                  units = "day";
+               }
+            }
+            else
+            {
+               units = "hour";
+            }
+         }
+         else
+         {
+            units = "minute";
+         }
+      }
+      else
+      {
+         count = 0;
+      }
+      if(count == 0)
+      {
+         PrintBuf(tempString, 1024, "<1 minute ago");
+      }
+      else
+      {
+         PrintBuf(tempString, 1024, count, " ", units, (count > 1)? "s" : "", " ago");
+      }
+      return tempString;
+   }
+}
+
+enum BuildStatus
+{
+   success, failure;
+   void OnDisplay(Surface surface,
+                  int x, int y, int width,
+                  TrumpDialog trumpDialog,
+                  Alignment alignment,
+                  DataDisplayFlags flags)
+   {
+      Bitmap b;
+      if(this==success)
+      {
+         b = theTestSuiteWindow.makeSuccess.bitmap;
+      }
+      else
+      {
+         b = theTestSuiteWindow.makeFailure.bitmap;
+      }
+      surface.Blit(b, x, y, 0, 0, b.width, b.height);
+   }
+};
+
+class TestSuiteWindow : Window
+{
+   text = "Ecere Compiler Tests";
+   background = activeBorder;
+   borderStyle = sizable;
+   hasMaximize = true;
+   hasMinimize = true;
+   hasClose = true;
+   size = { 520, 600 };
+   anchor = { horz = -3, vert = 80 };
+
+   BitmapResource makeSuccess { ":check.png" , window = this };
+   BitmapResource makeFailure { ":x.png", window = this };
+   
+   TestSuiteWindow()
+   {
+      testListBox.AddField(fieldName);
+      testListBox.AddField(fieldActive);
+      
+      runsListBox.AddField(fieldBuildStatus);
+      runsListBox.AddField(fieldAuraColor);
+      runsListBox.AddField(fieldRun);
+      runsListBox.AddField(fieldSaved);
+      // LoadTests(); FIXME this causes a segfault (but not OnCreate)
+      Print("starting up...\n");
+   }
+
+   PaneSplitter windowPanes
+   {
+      this, leftPane = testListBox, rightPane = runSection, split = 200;
+   };
+   Window runSection
+   {
+      this, anchor = { top = 0, bottom = 30, left = 0, right = 0 };
+   };
+   PaneSplitter runPanes
+   {
+      runSection, leftPane = dataBox, rightPane = runsListBox, split = 100, orientation = horizontal;
+   };
+
+   Timer timer
+   {
+      this,
+      delay = 30,
+      started = true;
+
+      bool DelayExpired()
+      {
+         runsListBox.Update(null);
+         return true;
+      }
+   };
+
+   void NewTest(RowTests tRow, char* testName, char* testFilePath)
+   {
+      tRow.Add();
+      tRow.id = tRow.sysID;
+      tRow.active=true;
+      tRow.name=testName;
+      tRow.filePath = testFilePath;
+   }
+
+   DataRow AddTest(RowTests tRow)
+   {
+      DataRow row;
+      String s;
+      row = testListBox.AddRow();
+      row.tag = tRow.id;
+      s = tRow.name; row.SetData(fieldName, s); delete s;
+      row.SetData(fieldActive, tRow.active);
+      return row;
+   }
+
+   void DeleteTest(ListBox testListBox, DataRow row)
+   {
+      // do we also delete all runs and files associated with this test?
+      RowTests tRow { };
+      tRow.sysID=row.tag;
+      tRow.Delete();
+      delete tRow; // won't be needed in the (distant?) future :-)
+      testListBox.DeleteRow(row);
+   }
+
+   void LoadTests()
+   {
+      RowTests tRow { };
+      while(tRow.Next())
+      {
+         theTestSuiteWindow.AddTest(tRow);
+      }
+      delete tRow;
+   }
+   
+   DataField fieldName   { width = 140, header = "Name"  , dataType = class(char*), editable = true };
+   DataField fieldActive { width = 40,  header = "Active", dataType = class(bool)                   };
+
+   ListBox testListBox
+   {
+      this,
+      anchor = { top = 2, bottom = 30, left = 2, right = 2 },
+      hasHeader = true;
+
+      bool NotifyChanged(ListBox testListBox, DataRow row)
+      {
+         RowTests tRow { };
+         tRow.sysID=row.tag;
+         tRow.name = row.GetData(fieldName);
+         tRow.active = row.GetData(fieldActive);
+         delete tRow;
+         return true;
+      }
+
+      bool NotifyKeyDown(ListBox testListBox, DataRow row, Key key, unichar ch)
+      {
+         if(key == del)
+         {
+            DeleteTest(testListBox, row);
+            // bRemove.NotifyClicked(this, bRemove,0,0,0);
+         }
+         else if(key == space)
+         {
+            //bool newActiveStatus = !(*(bool*)row.GetData(fieldActive)); // FIXME is needed?
+            row.SetData(fieldActive, !row.GetData(fieldActive));
+            testListBox.NotifyChanged(this, testListBox, row);
+            return false;
+         }
+         return true;
+      }
+
+      bool NotifySelect(ListBox listBox, DataRow row, Modifiers mods)
+      {
+         dataBox.Clear();
+         if(row)
+         {
+            { // print to data box
+               Test t;
+               char* tname;
+               char* tpath;
+               bool tactive;
+               t = (Test)row.tag;
+               tname = t.name;
+               tpath = t.filePath;
+               tactive = t.active;
+               dataBox.Printf("Name: %s\n", tname);
+               dataBox.Printf("Location: %s\n", tpath);
+               dataBox.Printf("Active: %d\n", tactive);
+               delete tname; delete tpath;
+            }
+            { // show test runs
+               Test t;
+               Array<TestRun> runs;
+               AccessMacros amacs{ };
+               t = (Test)row.tag;
+               runs = amacs.allTestRuns(t);
+               runsListBox.Clear();
+               for(tr : runs)
+               {
+                  DataRow row;
+                  BuildStatus buildStatus;
+                  TimeStamp time = tr.time;
+                  MD5Digest aura = tr.digest;
+                  buildStatus = tr.makeReturnValue ? failure : success;
+                  row = runsListBox.AddRow();
+                  row.tag = tr;
+                  row.SetData(fieldBuildStatus, buildStatus);
+                  row.SetData(fieldAuraColor, aura);
+                  row.SetData(fieldRun, time);
+                  row.SetData(fieldSaved, tr.save);
+               }
+            }
+         }
+         return true;
+      }
+   };
+   
+   DataField fieldBuildStatus { width = 40,  dataType = class(BuildStatus),       header = "Make" };
+   DataField fieldAuraColor   { width = 40,  dataType = class(MD5Digest),         header = "Aura" };
+   DataField fieldRun         { width = 120, dataType = class(RelativeTimeStamp), header = "Run" };
+   DataField fieldSaved       { width = 80,  dataType = class(bool),              header = "Save", editable = true };
+
+   ListBox runsListBox
+   {
+      parent = runSection,
+      master=this,
+      anchor = { top = 0, bottom = 0, left = 0, right = 0 },
+      hasHeader = true;
+
+      bool NotifyChanged(ListBox listBox, DataRow row)
+      {
+         TestRun tr = (TestRun)row.tag;
+         tr.save = row.GetData(fieldSaved);
+         return true;
+      }
+   };
+   EditBox dataBox
+   {
+      parent = runSection,
+      master=this,
+      opacity = 0,
+      inactive = true,
+      anchor = { top = 0, bottom = 0, left = 0, right = 0 },
+      hasHorzScroll = true,
+      readOnly = true,
+      multiLine = true,
+      noCaret = true
+   };
+   Button addTest
+   {
+      parent=this,
+      text = "Add",
+      hotKey=altA,
+      anchor = { bottom = 2, left = 2 };
+
+      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
+      {
+         FileFilter projectFilters[] =
+            {
+               { "eC project and source files (.ec, .epj)", "ec, epj" }
+            };
+         FileDialog f { filters = projectFilters, sizeFilters = sizeof(projectFilters), type = multiOpen };
+         incref f;
+         if(f.Modal()== ok)
+         {
+            int i;
+            RowTests tRow { };
+            for(i=0; i<f.numSelections; ++i)
+            {
+               char testName[MAX_LOCATION];
+               DataRow row;
+               GetLastDirectory(f.multiFilePaths[i], testName);
+               NewTest(tRow, testName, f.multiFilePaths[i]);
+               row = AddTest(tRow);
+               if(i+1 == f.numSelections)
+               {
+                  testListBox.SelectRow(row);
+               }
+            } 
+         }
+         delete f;
+         //Refocus();
+         return true;
+      }
+   };
+   Button runTests
+   {
+      parent = this,
+      text = "Run",
+      hotKey = altR,
+      anchor = { bottom = 2, right = 2 };
+
+      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
+      {
+         DataRow row;
+         for(row=testListBox.firstRow; row; row=row.next)
+         {
+            Test t = (Test)row.tag;
+            if(!t.active)
+            {
+               continue;
+            }
+            else
+            {
+               BuildTool bt { };
+               DiffThread dt { };
+               DateTime rightNow { };
+               RowRuns runs { };
+               TestRun run;
+               runs.Add();
+               runs.id = runs.sysID;
+               rightNow.GetLocalTime();
+               runs.time = (TimeStamp)rightNow;
+               runs.test = t;
+               run = runs.id;
+               bt.GenerateMakefile(t);
+               bt.BuildTest(run);
+               bt.AddOutputFiles(run);
+               runs.digest = run;
+               if(!t.reference)
+               {
+                  t.reference = run;
+               }
+               else
+               {
+                  DiffThread dt { };
+                  Array<OutputFile> outputsLatest = AccessMacros::allOutputFiles(run);
+                  Array<OutputFile> outputsReference = AccessMacros::allOutputFiles(t.reference);
+                  dt.DiffDir(outputsReference, outputsLatest);
+               }
+            }
+         testListBox.NotifySelect(this, testListBox, testListBox.currentRow, mods);
+         }
+         return true;
+      }
+   };
+   Button acceptTest
+   {
+      parent=this,
+      text = "Accept Run (x)",
+      hotKey=altX,
+      anchor={ bottom = 2, right = 52 };
+
+      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
+      {
+         DataRow row;
+         MessageBox confirmation
+         {
+            type = yesNo,
+            text = "Accept Test",
+            contents = "Accept the selected test?"
+         };
+         if(confirmation.Modal() == yes)
+         {
+            TestRun tr;
+            row = runsListBox.currentRow;
+            if(row)
+            {
+               tr = (TestRun)row.tag;
+               tr.save = true;
+               tr.test.reference = tr;
+            }
+         }
+         return true;
+      }
+   }
+   bool OnCreate()
+   {
+      globalSettings.Load();
+
+      LoadTests();
+      delete theGlobalSettings.diffTool;
+      theGlobalSettings.diffTool = CopyString("meld");
+      // theGlobalSettings.diffTool = CopyString("C:/Program Files/Araxis/Araxis Merge v6.5/compare.exe /wait");
+      return true;
+   }
+}
+
+TestSuiteWindow theTestSuiteWindow { };
+TestSuiteGlobalSettings theGlobalSettings { };
diff --git a/testSuite.epj b/testSuite.epj
new file mode 100644 (file)
index 0000000..1b9e69d
--- /dev/null
@@ -0,0 +1,63 @@
+{
+   "Version" : 0.2,
+   "ModuleName" : "testSuite",
+   "Options" : {
+      "Warnings" : "All",
+      "PreprocessorDefinitions" : [
+         "MAKEFILE_GENERATOR",
+         "TEST_SUITE"
+      ],
+      "TargetType" : "Executable",
+      "TargetFileName" : "testSuite",
+      "Libraries" : [
+         "ecere"
+      ],
+      "Console" : true
+   },
+   "Configurations" : [
+      {
+         "Name" : "Debug",
+         "Options" : {
+            "Debug" : true,
+            "Optimization" : "None",
+            "PreprocessorDefinitions" : [
+               "_DEBUG"
+            ]
+         }
+      },
+      {
+         "Name" : "Release",
+         "Options" : {
+            "Debug" : false,
+            "Optimization" : "Speed"
+         }
+      }
+   ],
+   "Files" : [
+      {
+         "Folder" : "extern",
+         "Files" : [
+            {
+               "Folder" : "project",
+               "Files" : [
+                  "/sdk/ide/src/project/Project.ec",
+                  "/sdk/ide/src/project/ProjectConfig.ec",
+                  "/sdk/ide/src/project/ProjectNode.ec"
+               ]
+            },
+            "/sdk/extras/md5.ec",
+            "../sdk/ide/src/IDESettings.ec",
+            "../sdk/extras/gui/controls/PathBox.ec",
+            "../sdk/extras/gui/controls/StringsBox.ec",
+            "../sdk/ide/src/OldIDESettings.ec"
+         ]
+      },
+      "testSuite.ec",
+      "TODO.txt"
+   ],
+   "ResourcesPath" : "",
+   "Resources" : [
+      "res/check.png",
+      "res/x.png"
+   ]
+}
\ No newline at end of file