import "ecere" import "EDA" import "md5" import "Project" 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 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"; if(false) //noGlobalSettings) { defaultCompiler = MakeDefaultCompiler("Default", true); delete ideSettings; } else { char * compiler = getenv("COMPILER"); if(!compiler) compiler = "Default"; // TODO: Command line option to choose between the two // or a command line option to not use global settings //defaultCompiler = MakeDefaultCompiler(); defaultCompiler = ideSettings.GetCompilerConfig(compiler); // possible TODO: use the workspace to select the active compiler // TODO: option to specify compiler name when using global settings } defaultCompiler.eccCommand = "ecc -nolinenumbers"; if(FileExists(epjPath).isFile) { char extension[MAX_EXTENSION] = ""; GetExtension(epjPath, extension); strlwr(extension); if(!strcmp(extension, ProjectExtension)) { project = LoadProject(epjPath, "Debug"); 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.GenerateCrossPlatformMk(); project.GenerateCompilerCf(defaultCompiler); project.GenerateMakefile(makePath, false, null, project.config); } 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 ::allTestRuns(Test t) { Array runs { }; Row tRow { tbl = dbindex("Runs",testAndTime) }; for(tRow.Find(dbfield("Runs",test),middle,nil,t); !tRow.nil; tRow.Find(dbfield("Runs", test), next, nil, t)) //Next()) runs.Add(tRow.sysID); delete tRow; return runs; } Array ::allOutputFiles(TestRun t) { Array files { }; RowOutputFiles tRow { tbl = dbindex("OutputFiles",run) }; for(tRow.Find(dbfield("OutputFiles",run),middle,nil,t); !tRow.nil; tRow.Find(dbfield("OutputFiles", run), next, nil, t)) //tRow.Next()) { if(t != tRow.run) break; files.Add(tRow.sysID); } delete tRow; return files; } } 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 outputOrig, Array 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, "project-testingSuite.Makefile"); // 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 = -1; char makefileOut[MAX_LOCATION]; char * filePath = run.test.filePath; char oldDir[MAX_LOCATION]; StripLastDirectory(filePath, makefileOut); GetWorkingDir(oldDir, sizeof(oldDir)); ChangeWorkingDir(makefileOut); PathCat(makefileOut, "project-testingSuite.Makefile"); //Execute("make -f %s clean", makefileOut); makePipe = DualPipeOpenf({ output = true },"%s -f %s clean", defaultCompiler.makeCommand, makefileOut); if(makePipe) { 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 },"%s -f %s", defaultCompiler.makeCommand, makefileOut); if(makePipe) { 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; delete makePipe; } return makeSuccess; } void AddOutputFiles(TestRun run) { char testFolder[MAX_LOCATION]; FileListing fl; char * filePath = run.test.filePath; StripLastDirectory(filePath, testFolder); // TODO: Get from config PathCat(testFolder, "obj/release.win32"); 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 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 outputsLatest = AccessMacros::allOutputFiles(run); Array 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 { }; class TestSuiteApp : GuiApplication { DataSource ds { driver = /*"SQLite" */"EDB" }; TestSuiteApp() { //File f { }; //f.OnSerialize(0); db = database_open(ds, "TestSuite"); } ~TestSuiteApp() { Array files; Array 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; } bool Init() { settingsContainer.Load(); delete settingsContainer; return true; } }