1 /**
2  * @file   TestRunner.cs
3  * @brief  Test Runner for C# test files.
4  * @author Frank Bergmann (fbergman@u.washington.edu)
5  *
6  *<!---------------------------------------------------------------------------
7  * This file is part of libSBML.  Please visit http://sbml.org for more
8  * information about SBML, and the latest version of libSBML.
9  *
10  * Copyright (C) 2020 jointly by the following organizations:
11  *     1. California Institute of Technology, Pasadena, CA, USA
12  *     2. University of Heidelberg, Heidelberg, Germany
13  *     3. University College London, London, UK
14  *
15  * Copyright (C) 2019 jointly by the following organizations:
16  *     1. California Institute of Technology, Pasadena, CA, USA
17  *     2. University of Heidelberg, Heidelberg, Germany
18  *
19  * Copyright (C) 2013-2018 jointly by the following organizations:
20  *     1. California Institute of Technology, Pasadena, CA, USA
21  *     2. EMBL European Bioinformatics Institute (EMBL-EBI), Hinxton, UK
22  *     3. University of Heidelberg, Heidelberg, Germany
23  *
24  * Copyright (C) 2009-2013 jointly by the following organizations:
25  *     1. California Institute of Technology, Pasadena, CA, USA
26  *     2. EMBL European Bioinformatics Institute (EMBL-EBI), Hinxton, UK
27  *
28  * Copyright (C) 2006-2008 by the California Institute of Technology,
29  *     Pasadena, CA, USA
30  *
31  * Copyright (C) 2002-2005 jointly by the following organizations:
32  *     1. California Institute of Technology, Pasadena, CA, USA
33  *     2. Japan Science and Technology Agency, Japan
34  *
35  * This library is free software; you can redistribute it and/or modify it
36  * under the terms of the GNU Lesser General Public License as published by
37  * the Free Software Foundation.  A copy of the license agreement is provided
38  * in the file named "LICENSE.txt" included with this software distribution
39  * and also available online as http://sbml.org/software/libsbml/license.html
40  *--------------------------------------------------------------------------->*/
41 
42 using System;
43 using System.Collections.Generic;
44 using System.Text;
45 using System.IO;
46 using System.Reflection;
47 using System.Diagnostics;
48 
49 namespace LibSBMLCSTestRunner
50 {
51 
52 
53     /// <summary>
54     /// <para>This test Programm takes a directory of C# files, compiles them and
55     /// then runs all test methods found. </para>
56     ///
57     /// <para>- currently no support for test data</para>
58     ///
59     /// <para>To use it simply invoke it with three arguments, for example:
60     ///
61     /// <c>LibSBMLCSTestRunner \\libsbml\\src\\sbml\\test \\libsbml\\src\\sbml\\test-data libsbmlCS.dll</c>
62     /// </para>
63     ///
64     /// author: Frank Bergmann (fbergman@u.washington.edu)
65     ///
66     /// </summary>
67     class TestRunner
68     {
PrintUsageAndExit()69         private static void PrintUsageAndExit()
70         {
71 
72             Console.WriteLine("Usage: TestRunner");
73             Console.WriteLine();
74             Console.WriteLine("\t -t | --tests <Directory containing generated test files>");
75             Console.WriteLine("\t -d | --data <Directory containing test-data>");
76             Console.WriteLine("\t -l | --lib  <Full path to libsbml C# bindings to be used>");
77             Console.WriteLine("\t -a | --additional-tests  <space separated list of additional directories>");
78             Console.WriteLine("\t -w | --working-dir   <directory to change into before running tests.>");
79             Console.WriteLine("\t -p | --path   <directory add to the path.>");
80             Console.WriteLine();
81             Console.WriteLine("For backwards compatibility it is also possible to invoke the testrunner");
82             Console.WriteLine("with only the test data directory, in which case tests compiled into the");
83             Console.WriteLine("testrunner will be executed. Or it could be invoked with three arguments");
84             Console.WriteLine("1. the test directory, 2. the test data directory, 3. the library to use.");
85             Console.WriteLine();
86             Environment.Exit(-1);
87         }
88 
Main(string[] args)89         static void Main(string[] args)
90         {
91             TestArguments arguments = new TestArguments(args);
92 			if (arguments.HaveAdditionalPath)
93 			{
94                 ProcessStartInfo info = new ProcessStartInfo(
95                                     new FileInfo(Assembly.GetEntryAssembly().Location).FullName,
96                                     arguments.StrippedArgs()
97                                     );
98                 info.UseShellExecute = false;
99                 info.EnvironmentVariables["PATH"] = arguments.AdditionalPath + ";" + info.EnvironmentVariables["PATH"];
100                 info.EnvironmentVariables["LD_LIBRARY_PATH"] = arguments.AdditionalPath + ":" + info.EnvironmentVariables["LD_LIBRARY_PATH"];
101                 info.EnvironmentVariables["DYLD_LIBRARY_PATH"] = arguments.AdditionalPath + ":" + info.EnvironmentVariables["DYLD_LIBRARY_PATH"];
102                 Process.Start(info);
103 				return;
104 			}
105 
106 			Console.WriteLine("LibSBML C# Testrunner");
107             Console.WriteLine("=====================");
108 
109 
110             if (!arguments.IsValid)
111             {
112                 // for backwards compatibility
113                 if (args.Length == 1 )
114                 {
115                     arguments = new TestArguments();
116                     arguments.TestDataDirectory = args[0];
117                 }
118                 else if (args.Length == 3)
119                 {
120                     arguments = new TestArguments();
121                     arguments.TestDirectory = args[0];
122                     arguments.TestDataDirectory = args[1];
123                     arguments.ManagedLibrary = args[2];
124                 }
125 
126                 if (!arguments.IsValid)
127                 {
128                     PrintUsageAndExit();
129                 }
130             }
131 
132 			if (arguments.HaveWorkingDirectory)
133 				Directory.SetCurrentDirectory(arguments.WorkingDirectory);
134 
135             if (File.Exists(arguments.ManagedLibrary))
136                 AppDomain.CurrentDomain.AssemblyResolve += delegate(object s, ResolveEventArgs e)
137                                                            {
138                                                                string filename = new AssemblyName(e.Name).Name;
139                                                                string path = string.Format(@"{0}.dll", Path.Combine(new FileInfo(arguments.ManagedLibrary).DirectoryName, filename));
140                                                                return Assembly.LoadFrom(path);
141                                                            };
142 
143             if (arguments.UseCompiledTests)
144             {
145                 RunTestsInNamespace(arguments);
146             }
147             else
148             {
149                 CompileAndRunTests(arguments);
150             }
151 
152 
153             if (arguments.HaveAdditionalDirectories)
154             {
155                 Console.WriteLine();
156                 Console.WriteLine("AdditionalTests");
157                 Console.WriteLine("===============");
158                 Console.WriteLine();
159 
160                 foreach (string path in arguments.AdditionalTestDirectories)
161                 {
162                     Console.WriteLine("running tests from: " + path);
163                     Console.WriteLine();
164                     RunTests(arguments.ManagedLibrary, path, arguments.TestDataDirectory);
165                     Console.WriteLine();
166                 }
167             }
168 
169 
170         }
171 
172         /// <summary>
173         /// This runs all tests in the 'LibSBMLCSTest' namespace, which
174         /// presumably are included in this assembly.
175         /// </summary>
176         /// <param name="args">TestRunner arguments</param>
RunTestsInNamespace(TestArguments args)177         private static void RunTestsInNamespace(TestArguments args)
178         {
179 
180             string sData = args.TestDataDirectory;
181 
182             if (!Directory.Exists(sData))
183             {
184                 Console.WriteLine("Data Directory does not exist" + Environment.NewLine);
185                 Environment.Exit(-1);
186             }
187 
188 
189             // all seems well so let us run through the tests:
190             Console.WriteLine("Running the tests with: ");
191             Console.WriteLine("\tData Directory:   " + sData);
192             Console.WriteLine();
193 
194             RunTestsInAssembly(Assembly.GetExecutingAssembly(), sData);
195 
196             Console.WriteLine(Environment.NewLine);
197             Console.WriteLine(String.Format("Total Number of Tests {0}, failures {1}",
198                                  nTestFunc, nFailureSum));
199             if (nFailureSum == 0)
200             {
201                 Console.WriteLine("\nAll tests passed." + Environment.NewLine);
202                 return;
203             }
204 
205             PrintErrors();
206             Environment.Exit(1);
207 
208         }
209 
210         /// <summary>
211         /// This runs the tests by recompiling all tests in the specified
212         /// source directory.
213         /// </summary>
214         /// <param name="args">TestRunner arguments</param>
CompileAndRunTests(TestArguments args)215         private static void CompileAndRunTests(TestArguments args)
216         {
217 
218             string sSource = args.TestDirectory;
219             string sData = args.TestDataDirectory;
220             string sLibrary = args.ManagedLibrary;
221 
222             if (!Directory.Exists(sSource))
223             {
224                 Console.WriteLine("Source Directory does not exist" + Environment.NewLine);
225                 PrintUsageAndExit();
226             }
227 
228             if (!Directory.Exists(sData))
229             {
230                 Console.WriteLine("Data Directory does not exist" + Environment.NewLine);
231                 PrintUsageAndExit();
232             }
233 
234             if (!File.Exists(sLibrary))
235             {
236                 Console.WriteLine("libsbml C# binding assembly does not exist." + Environment.NewLine);
237                 PrintUsageAndExit();
238             }
239 
240             // all seems well so let us run through the tests:
241             Console.WriteLine("Running the tests with: ");
242             Console.WriteLine("\tSource Directory: " + sSource);
243             Console.WriteLine("\tData Directory:   " + sData);
244             Console.WriteLine("\tC# binding:       " + sLibrary);
245             Console.WriteLine();
246 
247             RunTests(sLibrary, sSource, sData);
248         }
249 
RunTestsInDirectory(string testDir, string sData)250         private static int RunTestsInDirectory(string testDir, string sData)
251         {
252             // then compile and run all C# files
253             string[] testFiles = Directory.GetFiles(testDir, "*.cs");
254 
255             foreach (string testFile in testFiles)
256             {
257                 RunTestFile(testFile, testDir, sData);
258             }
259             return testFiles.Length;
260         }
261 
RunTests(string sLibrary, string sSource, string sData)262         private static void RunTests(string sLibrary, string sSource, string sData)
263         {
264             // add reference library to the compiler so that it will be referenced
265             // by the test files
266             Compiler.addAssembly(sLibrary);
267 
268             int testFileNum = 0;
269 
270             nCompileErrors = 0;
271             nSuccessSum = 0;
272             nFailureSum = 0;
273             nTestFunc = 0;
274 
275             string[] testDirs = Directory.GetDirectories(sSource);
276             foreach (string testDir in testDirs)
277             {
278                 testFileNum += RunTestsInDirectory(testDir, sData);
279             }
280 
281             if (testFileNum == 0)
282             {
283                 testFileNum += RunTestsInDirectory(sSource, sData);
284             }
285 
286             Console.WriteLine();
287             Console.WriteLine();
288             Console.WriteLine(String.Format("Encountered {0} compile errors (invalid tests)", nCompileErrors));
289             Console.WriteLine(String.Format("Total Number of Test files {0}, Tests {1}, failures {2}",
290                                              testFileNum, nTestFunc, nFailureSum));
291             if (nFailureSum == 0 && nCompileErrors == 0)
292             {
293                 Console.WriteLine("\nAll tests passed.");
294                 return;
295             }
296 
297             PrintErrors();
298             Environment.Exit(1);
299         }
300 
301         static int nCompileErrors;
302         static int nSuccessSum;
303         static int nFailureSum;
304         static int nTestFunc;
305 
306         readonly static List<ErrorDetails> _errors = new List<ErrorDetails>();
307 
308         /// <summary>
309         /// Prints all errors that occured
310         /// </summary>
PrintErrors()311         private static void PrintErrors()
312         {
313             if (_errors == null || _errors.Count == 0) return;
314 
315             foreach (ErrorDetails item in _errors)
316             {
317                 Console.WriteLine();
318                 Console.WriteLine();
319 
320                 Console.WriteLine(item.Message);
321                 Console.WriteLine(new string('=', 20));
322                 Console.WriteLine(item.Exception.Message);
323                 Console.WriteLine(item.Exception.StackTrace);
324             }
325 
326             Console.WriteLine();
327             Console.WriteLine();
328         }
329 
RunTestFile(string testFile, string testDir, string sData)330         private static void RunTestFile(string testFile, string testDir, string sData)
331         {
332 #if DEBUG
333             Console.WriteLine(String.Format("Runing test file: '{0}' in {1}", new FileInfo(testFile).Name, testDir));
334             Console.WriteLine("----------------------------------------------------------------");
335 #endif
336             // read C# code
337             string source = File.ReadAllText(testFile);
338 
339             // compile the test file and create an assembly
340             Assembly oTestClass = Compiler.GetAssembly(source);
341 
342             if (oTestClass == null)
343             {
344                 Console.WriteLine("Error compiling the test class (details on std::error) ");
345                 Console.Error.WriteLine(Compiler.getLastErrors());
346                 nCompileErrors++;
347                 Console.WriteLine();
348                 return;
349             }
350 
351             // test compiled so now we can run the tests
352             RunTestsInAssembly(oTestClass, sData);
353 #if DEBUG
354             Console.WriteLine();
355             Console.WriteLine();
356 #endif
357         }
358 
RunTestsInAssembly(Assembly oTestClass, string sData)359         private static void RunTestsInAssembly(Assembly oTestClass, string sData)
360         {
361             // get all classes, we know that all test-classes begin with Test
362             Type[] types = oTestClass.GetExportedTypes();
363             foreach (Type type in types)
364             {
365                 if (type.Name.StartsWith("Test"))
366                 {
367                     // we have a test class:
368                     RunTestsInType(oTestClass, type, sData);
369                 }
370             }
371         }
372 
RunTestsInType(Assembly oTestClass, Type type, string sData)373         private static void RunTestsInType(Assembly oTestClass, Type type, string sData)
374         {
375             // counting successes and failures
376             int nSuccess = 0;
377             int nFailure = 0;
378 
379             try
380             {
381                 // get all methods
382                 MemberInfo[] members = type.GetMembers();
383 
384                 foreach (MemberInfo member in members)
385                 {
386 
387                     // test methods begin with test_
388                     if (member.Name.StartsWith("test_"))
389                     {
390                         ++nTestFunc;
391                         // set up the class
392                         object testClass = SetupTestClass(oTestClass, type);
393 
394                         // run the test
395                         try
396                         {
397                             type.InvokeMember(member.Name, BindingFlags.InvokeMethod |
398                                         BindingFlags.Default, null, testClass, null);
399                         }
400                         catch (TargetInvocationException ex)
401                         {
402                             Console.Write("E");
403                             _errors.Add(new ErrorDetails(
404                                 String.Format("Error in '{0}': ", member.Name),
405                                 ex.InnerException));
406                             nFailure++;
407                             continue;
408                         }
409                         catch (Exception ex)
410                         {
411                             Console.Write("E");
412                             _errors.Add(new ErrorDetails(
413                                 string.Format("Error in '{0}': ", member.Name),
414                                 ex));
415                             nFailure++;
416                             continue;
417                         }
418 
419                         // if we are still here the test was successful
420 #if DEBUG
421                         Console.WriteLine(String.Format("Calling '{0}'", member.Name));
422 #else
423                         Console.Write(".");
424 #endif
425                         nSuccess++;
426 
427                     }
428                 }
429             }
430             catch (Exception ex)
431             {
432                 Console.Write("E");
433                 _errors.Add(new ErrorDetails(
434                     String.Format("Error running tests for {0}: ", type.Name),
435                     ex));
436                 return;
437             }
438 
439 #if DEBUG
440             Console.WriteLine();
441             Console.WriteLine(
442                 String.Format("Testing completed: Pass:{0}, Fail:{1} (Total:{2})",
443                               nSuccess, nFailure, nSuccess+nFailure));
444 #else
445             Console.Write(".");
446 #endif
447             nSuccessSum += nSuccess;
448             nFailureSum += nFailure;
449 
450         }
451 
SetupTestClass(Assembly oTestClass, Type type)452         private static object SetupTestClass(Assembly oTestClass, Type type)
453         {
454 
455             object oClass = Activator.CreateInstance(type);
456             try
457             {
458                 type.InvokeMember("setUp",
459                                   BindingFlags.InvokeMethod | BindingFlags.Default,
460                                   null, oClass, null);
461             }
462             catch (Exception)
463             {
464                 // 2010-07-22 <mhucka@caltech.edu> Some just don't have a
465                 // setup class.  It's confusing to see these errors.
466 
467                 // Console.WriteLine("Could not run setUp class ... ");
468             }
469             return oClass;
470         }
471     }
472 
473     /// <summary>
474     /// Internal class holding all error information
475     /// </summary>
476     public class ErrorDetails
477     {
478         private Exception _exception;
479 
480         /// <summary>
481         /// Gets / Sets the Exception
482         /// </summary>
483         public Exception Exception
484         {
485             get { return _exception; }
486             set { _exception = value; }
487         }
488 
489         private string _message;
490 
491         /// <summary>
492         /// Gets / Sets the Error message
493         /// </summary>
494         public string Message
495         {
496             get { return _message; }
497             set { _message = value; }
498         }
499 
500         /// <summary>
501         /// Initializes a new instance of the ErrorDetails class.
502         /// </summary>
503         /// <param name="message">Mesage to print</param>
504         /// <param name="exception">exception object</param>
ErrorDetails(string message, Exception exception)505         public ErrorDetails(string message, Exception exception)
506         {
507             Exception = exception;
508             Message = message;
509         }
510     }
511 
512     /// <summary>
513     /// Test arguments parses the command line arguments given to the testrunner
514     /// </summary>
515     public class TestArguments
516     {
517         /// <summary>
518         /// Constructs a new TestArguments object from command line arguments.
519         /// </summary>
520         /// <param name="args">command line arguments</param>
TestArguments(string[] args)521         public TestArguments(string[] args)
522             : this()
523         {
524             ParseArguments(args);
525         }
526 
527         /// <summary>
528         /// Initializes a new instance of the TestArguments class.
529         /// </summary>
TestArguments()530         public TestArguments()
531         {
532             ManagedLibrary = "libsbmlcsP.dll";
533             AdditionalTestDirectories = new List<string>();
534         }
535 
536         private string[] _OriginalArgs;
537         public string[] OriginalArgs
538         {
539             get
540             {
541                 return _OriginalArgs;
542             }
543             set
544             {
545                 _OriginalArgs = value;
546             }
547         }
548 
ParseArguments(string[] args)549         public void ParseArguments(string[] args)
550         {
551             _OriginalArgs = args;
552 
553             for (int i = 0; i < args.Length; i++)
554             {
555                 string current = args[i].ToLowerInvariant();
556                 bool haveNext = i + 1 < args.Length;
557                 string next = (haveNext ? args[i + 1] : null);
558 
559                 if (haveNext && (current == "-l" || current == "--lib"))
560                 {
561                     ManagedLibrary = next;
562                     i++;
563                 }
564                 else if (haveNext && (current == "-d" || current == "--data"))
565                 {
566                     TestDataDirectory = next;
567                     i++;
568                 }
569                 else if (haveNext && (current == "-p" || current == "--path"))
570                 {
571                     AdditionalPath = next;
572                     i++;
573                 }
574                 else if (haveNext && (current == "-t" || current == "--tests"))
575                 {
576                     TestDirectory = next;
577                     i++;
578                 }
579                 else if (haveNext && (current == "-w" || current == "--working-dir"))
580                 {
581                     WorkingDirectory = next;
582                     i++;
583                 }
584                 else if (haveNext && (current == "-a" || current == "--additional-tests"))
585                 {
586                     // consume remaining arguments
587                     string[] additionalDirs = new string[args.Length - (i + 1)];
588                     Array.Copy(args, i + 1, additionalDirs, 0, args.Length - (i + 1));
589                     AdditionalTestDirectories.AddRange(additionalDirs);
590                     return;
591                 }
592             }
593         }
594 
595         public bool HaveAdditionalDirectories
596         {
597             get
598             {
599                 return AdditionalTestDirectories != null &&
600                 AdditionalTestDirectories.Count > 0;
601             }
602         }
603 
604         public bool UseCompiledTests
605         {
606             get
607             {
608                 return !string.IsNullOrEmpty(TestDataDirectory)
609                     && string.IsNullOrEmpty(TestDirectory);
610             }
611         }
612 
613         private string _workingDirectory;
614         public string WorkingDirectory
615         {
616             get { return _workingDirectory; }
617             set { _workingDirectory = value; }
618         }
619 
620         private string _additionalPath;
621         public string AdditionalPath
622         {
623             get { return _additionalPath; }
624             set { _additionalPath = value; }
625         }
626 
627         public bool HaveAdditionalPath
628         {
629             get
630             {
631                 return !string.IsNullOrEmpty(_additionalPath) &&
632                     Directory.Exists(_additionalPath);
633             }
634         }
635 
636         private string _testDirectory;
637         public string TestDirectory
638         {
639             get { return _testDirectory; }
640             set { _testDirectory = value; }
641         }
642 
643         private string _testDataDirectory;
644         public string TestDataDirectory
645         {
646             get { return _testDataDirectory; }
647             set { _testDataDirectory = value; }
648         }
649 
650         private string _managedLibrary;
651         public string ManagedLibrary
652         {
653             get { return _managedLibrary; }
654             set { _managedLibrary = value; }
655         }
656 
657         private List<string> _additionalTestDirectories;
658         public List<string> AdditionalTestDirectories
659         {
660             get { return _additionalTestDirectories; }
661             set { _additionalTestDirectories = value; }
662         }
663 
664         public bool HaveWorkingDirectory
665         {
666             get
667             {
668                 return !string.IsNullOrEmpty(WorkingDirectory) && Directory.Exists(WorkingDirectory);
669             }
670         }
671 
672 
673         /// <summary>
674         /// Ensures that all needed parameters are present.
675         /// </summary>
676         public bool IsValid
677         {
678             get
679             {
680                 bool valid = true;
681                 if (UseCompiledTests)
682                 {
683                     valid &= Directory.Exists(TestDataDirectory);
684                 }
685                 else
686                 {
687                     valid &= Directory.Exists(TestDataDirectory) &&
688                         Directory.Exists(TestDirectory);
689 
690                 }
691 
692                 valid &= !String.IsNullOrEmpty(ManagedLibrary) &&
693                     File.Exists(ManagedLibrary);
694 
695                 if (HaveAdditionalDirectories)
696                 {
697                     foreach (string path in AdditionalTestDirectories)
698                     {
699                         valid &= Directory.Exists(path);
700                     }
701                 }
702 
703                 return valid;
704             }
705         }
706 
707         /// <summary>
708         /// Return current arguments
709         /// </summary>
710         /// <returns></returns>
ToString()711         public override string ToString()
712         {
713             return string.Format(
714                 "{0}\tTest Directory        : {1}" +
715                 "{0}\tTest Data Directory   : {2}" +
716                 "{0}\tTest Managed Library  : {3}" +
717                 "{0}\tUse Compiled Tests    : {4}" +
718                 "{0}\tHave Additional Tests : {5}" +
719                 "{0}\tIsValid               : {6}",
720                 Environment.NewLine,
721                 TestDirectory,
722                 TestDataDirectory,
723                 ManagedLibrary,
724                 UseCompiledTests,
725                 HaveAdditionalDirectories,
726                 IsValid
727                 );
728         }
729 
StrippedArgs()730         public string StrippedArgs()
731         {
732             //-t | --tests <Directory containing generated test files>");
733             //-d | --data <Directory containing test-data>");
734             //-l | --lib  <Full path to libsbml C# bindings to be used>");
735             //-a | --additional-tests  <space separated list of additional directories>");
736             //-w | --working-dir   <directory to change into before running tests.>");
737             //-p | --path   <directory add to the path.>");
738             StringBuilder builder = new StringBuilder();
739             for (int i = 0; i < OriginalArgs.Length; i++)
740             {
741                 if (OriginalArgs[i] == "-p" || OriginalArgs[i] == "--path")
742                     i = i + 1;
743                 else
744                     builder.AppendFormat("\"{0}\" ", OriginalArgs[i]);
745             }
746             return builder.ToString();
747         }
748     }
749 
750 }
751 
752