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