| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| #include "DFPlatform.h" |
| #include "DFUnitTest.h" |
| #include "TextPackage.h" |
| #include "DFString.h" |
| #include "DFFilesystem.h" |
| #include "DFCommon.h" |
| #include "DFUnitTest.h" |
| #include "DFArray.h" |
| #include "DFBuffer.h" |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| |
| extern TestGroup APITests; |
| extern TestGroup CSSTests; |
| extern TestGroup HTMLTests; |
| extern TestGroup LibTests; |
| extern TestGroup XMLTests; |
| extern TestGroup LaTeXTests; |
| extern TestGroup ODFTests; |
| extern TestGroup WordTests; |
| extern TestGroup PlatformOSTests; |
| extern TestGroup PlatformWrapperTests; |
| extern TestGroup BDTTests; |
| |
| TestGroup *allGroups[] = { |
| &APITests, |
| &BDTTests, |
| &CSSTests, |
| &HTMLTests, |
| &LibTests, |
| &XMLTests, |
| &LaTeXTests, |
| &ODFTests, |
| &WordTests, |
| &PlatformOSTests, |
| &PlatformWrapperTests, |
| NULL |
| }; |
| |
| typedef struct { |
| int showResults; |
| int showDiffs; |
| int passed; |
| int failed; |
| } TestHarness; |
| |
| static int diffResults(const char *from, const char *to, DFError **error) |
| { |
| const char *fromFilename = "dftest-diff-from.tmp"; |
| const char *toFilename = "dftest-diff-to.tmp"; |
| int result = 0; |
| if (!DFStringWriteToFile(from,fromFilename,error)) { |
| DFErrorFormat(error,"%s: %s",fromFilename,DFErrorMessage(error)); |
| } |
| else if (!DFStringWriteToFile(to,toFilename,error)) { |
| DFErrorFormat(error,"%s: %s",toFilename,DFErrorMessage(error)); |
| } |
| else { |
| char *cmd = DFFormatString("diff -u %s %s",fromFilename,toFilename); |
| system(cmd); |
| free(cmd); |
| result = 1; |
| } |
| DFDeleteFile(fromFilename,NULL); |
| DFDeleteFile(toFilename,NULL); |
| return result; |
| } |
| |
| static char *getCommandFromCode(const char *code, DFError **error) |
| { |
| char *command = NULL; |
| const char **lines = DFStringSplit(code,"\n",0); |
| int count = 0; |
| for (int i = 0; lines[i]; i++) { |
| if (!DFStringIsWhitespace(lines[i]) && strncmp(lines[i],"//",2)) { |
| if (command == NULL) |
| command = xstrdup(lines[i]); |
| count++; |
| } |
| } |
| free(lines); |
| |
| if (count != 1) { |
| DFErrorFormat(error,"%d commands found",count); |
| free(command); |
| return NULL; |
| } |
| else { |
| return command; |
| } |
| } |
| |
| static const char **parseCommand(const char *command, DFError **error) |
| { |
| const char *openbr = strchr(command,'('); |
| const char *closebr = strrchr(command,')'); |
| |
| if ((openbr == NULL) && (closebr == NULL)) { |
| DFArray *array = DFArrayNew((DFCopyFunction)xstrdup,free); |
| DFArrayAppend(array,(void *)command); |
| const char **result = DFStringArrayFlatten(array); |
| DFArrayRelease(array); |
| return result; |
| } |
| |
| if ((openbr == NULL) || (closebr == NULL)) { |
| DFErrorFormat(error,"Malformed command: %s\n",command); |
| return NULL; |
| } |
| |
| size_t openpos = openbr - command; |
| size_t closepos = closebr - command; |
| size_t nameend = openpos; |
| |
| if ((openpos+2 <= closepos) && (command[openpos+1] == '{') && (command[closepos-1] == '}')) { |
| openpos++; |
| closepos--; |
| } |
| |
| char *name = DFSubstring(command,0,nameend); |
| char *arguments = DFSubstring(command,openpos+1,closepos); |
| |
| const char **components = DFStringSplit(arguments,",",0); |
| DFArray *array = DFArrayNew((DFCopyFunction)xstrdup,free); |
| DFArrayAppend(array,name); |
| for (int i = 0; components[i]; i++) { |
| char *trimmed = DFStringTrimWhitespace(components[i]); |
| DFArrayAppend(array,trimmed); |
| free(trimmed); |
| } |
| |
| const char **result = DFStringArrayFlatten(array); |
| |
| free(components); |
| free(name); |
| free(arguments); |
| DFArrayRelease(array); |
| return result; |
| } |
| |
| static void TestRun(TestHarness *harness, const char *path) |
| { |
| DFError *error = NULL; |
| TextPackage *package = TextPackageNewWithFile(path,&error); |
| int pass = 0; |
| if (package != NULL) { |
| DFHashTable *dict = DFHashTableCopy(package->items); |
| |
| char *code = DFStrDup(DFHashTableLookup(dict,"")); |
| DFHashTableRemove(dict,""); |
| |
| char *command = getCommandFromCode(code,&error); |
| if (command == NULL) { |
| printf("%s: %s\n",path,DFErrorMessage(&error)); |
| exit(1); |
| } |
| |
| const char **arguments = parseCommand(command,&error); |
| if (arguments == NULL) { |
| printf("%s: %s\n",path,DFErrorMessage(&error)); |
| exit(1); |
| } |
| |
| char *expectedRaw = DFStrDup(DFHashTableLookup(dict,"expected")); |
| if (expectedRaw == NULL) { |
| printf("%s: no expected output given\n",path); |
| exit(1); |
| } |
| DFHashTableRemove(dict,"expected"); |
| |
| char *parentPath = DFPathDirName(path); |
| DFBuffer *outputBuf = DFBufferNew(); |
| utsetup(parentPath,dict,(int)(DFStringArrayCount(arguments)-1),&arguments[1], |
| outputBuf); |
| |
| TestCase *tc = utlookup(allGroups,arguments[0]); |
| if (tc != NULL) { |
| tc->fun(); |
| } |
| else { |
| DFBufferFormat(utgetoutput(),"Unknown test: %s\n",arguments[0]); |
| } |
| |
| char *output = DFStringTrimWhitespace(outputBuf->data); |
| char *expected = DFStringTrimWhitespace(expectedRaw); |
| |
| if (harness->showResults && !harness->showDiffs) { |
| printf("%s",outputBuf->data); |
| } |
| else { |
| pass = ((expected != NULL) && DFStringEquals(expected,output)); |
| } |
| |
| if (!pass && harness->showDiffs) { |
| if (!diffResults(expected,output,&error)) |
| printf("%s\n",DFErrorMessage(&error)); |
| } |
| |
| free(output); |
| free(code); |
| free(command); |
| free(arguments); |
| free(expectedRaw); |
| free(parentPath); |
| free(expected); |
| utteardown(); |
| DFBufferRelease(outputBuf); |
| DFHashTableRelease(dict); |
| } |
| else { |
| if (harness->showResults || harness->showDiffs) |
| printf("%s\n",DFErrorMessage(&error)); |
| } |
| |
| if (!harness->showResults) { |
| if (pass) { |
| printf("%-80s PASS\n",path); |
| harness->passed++; |
| } |
| else { |
| printf("%-80s FAIL\n",path); |
| harness->failed++; |
| } |
| } |
| TextPackageRelease(package); |
| } |
| |
| static void TestGetFilenamesRecursive(const char *path, DFArray *result) |
| { |
| if (!DFFileExists(path)) |
| return; |
| int isDirectory = DFIsDirectory(path); |
| if (isDirectory) { |
| DFError *error = NULL; |
| const char **contents = DFContentsOfDirectory(path,0,&error); |
| if (contents == NULL) { |
| printf("%s: %s\n",path,DFErrorMessage(&error)); |
| DFErrorRelease(error); |
| return; |
| } |
| |
| DFSortStringsCaseInsensitive(contents); |
| |
| for (int i = 0; contents[i]; i++) { |
| const char *filename = contents[i]; |
| char *childPath = DFAppendPathComponent(path,filename); |
| TestGetFilenamesRecursive(childPath,result); |
| free(childPath); |
| } |
| free(contents); |
| } |
| else { |
| char *extension = DFPathExtension(path); |
| if (DFStringEqualsCI(extension,"test")) |
| DFArrayAppend(result,(void *)path); |
| free(extension); |
| } |
| } |
| |
| void runTests(int argc, const char **argv, int diff) |
| { |
| DFArray *tests = DFArrayNew((DFCopyFunction)xstrdup,(DFFreeFunction)free); |
| for (int i = 0; i < argc; i++) { |
| const char *path = argv[i]; |
| if (!DFFileExists(path)) { |
| fprintf(stderr,"%s: No such file or directory\n",path); |
| exit(1); |
| } |
| TestGetFilenamesRecursive(path,tests); |
| } |
| |
| TestHarness harness; |
| bzero(&harness,sizeof(TestHarness)); |
| harness.showResults = (DFArrayCount(tests) == 1); |
| harness.showDiffs = diff; |
| |
| if (DFArrayCount(tests) == 1) { |
| TestRun(&harness,(const char *)DFArrayItemAt(tests,0)); |
| } |
| else { |
| for (size_t i = 0; i < DFArrayCount(tests); i++) { |
| const char *test = DFArrayItemAt(tests,i); |
| TestRun(&harness,test); |
| } |
| printf("Passed: %d\n",harness.passed); |
| printf("Failed: %d\n",harness.failed); |
| } |
| DFArrayRelease(tests); |
| } |
| |
| int main(int argc, const char **argv) |
| { |
| // Ensure that if a segfault occurs half-way through printing a line, |
| // we still get the partial output. |
| setbuf(stdout,NULL); |
| |
| if ((argc >= 2) && !strcmp(argv[1],"-plain")) { |
| if (argc == 2) |
| utrun(allGroups, 1, 0, NULL); |
| else { |
| // Arg[2] == testgroup to run |
| TestGroup *singleGroup[] = { NULL, NULL }; |
| int i = 0; |
| |
| for (; allGroups[i] && strcmp(argv[2], allGroups[i]->name); i++) ; |
| if (allGroups[i]) { |
| singleGroup[0] = allGroups[i]; |
| utrun(singleGroup, 1, 0, NULL); |
| } |
| else |
| printf("\n function group \"%s\" does not exist!\n\n", argv[2]); |
| } |
| } |
| else if ((argc >= 3) && !strcmp(argv[1],"-diff")) { |
| runTests(argc-2,&argv[2],1); |
| } |
| else if (argc >= 2) { |
| runTests(argc-1,&argv[1],0); |
| } |
| else { |
| // Usage |
| printf("Usage:\n" |
| "\n" |
| "dftest path1 path2 ...\n" |
| "\n" |
| " Run a series of automated tests, consisting of all the .test files in the\n" |
| " specified path(s). If only one file is found, the test is run and the\n" |
| " result is printed to standard output. If multiple files are found, they are\n" |
| " all run, and their pass/fail status is printed.\n" |
| "\n" |
| "dftest -diff path1 path2 ...\n" |
| "\n" |
| " As above, but for each test that fails, print out a diff between the expected\n" |
| " and actual results.\n" |
| "\n" |
| "dftest -plain\n" |
| "\n" |
| " Run all tests functions of type PlainTest, which don't use any data files\n" |
| "\n"); |
| } |
| return 0; |
| } |