// Copyright Keysight Technologies 2012-2019
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at http://mozilla.org/MPL/2.0/.
using NUnit.Framework;
using System;
using System.Threading;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Xml.Serialization;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
using OpenTap.EngineUnitTestUtils;
using System.Threading.Tasks;
using OpenTap;
namespace OpenTap.Engine.UnitTests
{
///
///This is a test class for PluginManagerTest and is intended
///to contain all PluginManagerTest Unit Tests
///
[TestFixture]
public class PluginManagerTest
{
///
///A test for SearchAsync
///
[Test]
public void SearchAsyncTest()
{
PluginManager.SearchAsync().Wait();
Assert.IsTrue(PluginManager.GetPlugins(typeof(IResultListener)).Any(), "Search did not find database interface.");
Assert.IsTrue(PluginManager.GetPlugins(typeof(ITestStep)).Any(), "Search did not find any teststeps.");
}
private static void DeleteFile(string assemblyFileName)
{
int retries = 0;
while (File.Exists(assemblyFileName))
{
try
{
File.Delete(assemblyFileName);
}
catch
{
Thread.Sleep(50);
if (retries++ > 20)
break;
}
}
}
private static void GeneratePluginAssembly(string assemblyFileName, string testStepName, string guid = "634896ca-7e6a-c66d-5ef7-2c2d2a5c3f30", string customcode = "")
{
string cs = "";
cs += "using System.Reflection;";
cs += "using System.Runtime.InteropServices;";
cs += "using OpenTap;";
cs += "[assembly: Guid(\"" + guid + "\")]";
cs += customcode;
cs += "public class " + testStepName + " : TestStep { public override void Run(){} }";
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add("System.dll");
parameters.ReferencedAssemblies.Add("OpenTap.dll");
parameters.GenerateInMemory = false;
parameters.GenerateExecutable = false;
parameters.OutputAssembly = Path.Combine(Path.GetTempPath(), Path.GetFileName(assemblyFileName));
CompilerResults results = provider.CompileAssemblyFromSource(parameters, cs);
if (results.Errors.HasErrors)
{
var errors = results.Errors.Cast().Select(err => err.ToString());
Assert.Inconclusive(String.Join("\r\n", errors));
}
DeleteFile(assemblyFileName);
File.Move(results.PathToAssembly, assemblyFileName);
DeleteFile(results.PathToAssembly);
}
//[Test, Ignore("We dont know what this actually does")]
//public void DirectoriesToSearchTest()
//{
// //This Test requires OpenTAP to be installed
// // Make sure we have the stuff we need in a temp dir:
// string baseDir = Path.Combine(Path.GetTempPath(), "DirectoriesToSearchTest");
// Directory.CreateDirectory(baseDir);
// File.Copy(Assembly.GetExecutingAssembly().Location, Path.Combine(baseDir, Path.GetFileName(Assembly.GetExecutingAssembly().Location)), true);
// File.Copy("OpenTap.dll", Path.Combine(baseDir, "OpenTap.dll"), true);
// File.Copy("Keysight.Ccl.Licensing.Api.dll", Path.Combine(baseDir, "Keysight.Ccl.Licensing.Api.dll"), true);
// File.Copy("System.Reflection.Metadata.dll", Path.Combine(baseDir, "System.Reflection.Metadata.dll"), true);
// File.Copy("System.Collections.Immutable.dll", Path.Combine(baseDir, "System.Collections.Immutable.dll"), true);
// // Run PluginManager.Search from the temp dir while specifying TAP_PATH as an additional directory to search:
// AppDomainSetup setup = new AppDomainSetup { ApplicationBase = baseDir };
// AppDomain domain = AppDomain.CreateDomain("TestSearchDomain", null, setup);
// List asms = new List();
// domain.DoCallBack(() =>
// {
// TestTraceListener log = new TestTraceListener();
// Log.AddListener(logOpenTAP
// // Ask to look for plugins in TAP installation dir as well as current dir
// // (current dir should be different from OpenTAP installation dir for the bug to show)
// PluginManager.DirectoriesToSearch.Add(Environment.GetEnvironmentVariable("TAP_PATH"));
// var d = AppDomain.CurrentDomain;
// var l = log.GetLog();
// d.SetData("log", l);
// });
// string logText = (string)domain.GetData("log");
// AppDomain.Unload(domain);
// // This can fail if a plugin in the other Dir to search (in this test TAP_PATH)
// // cannot be loaded because a dependency cannot be found.
// Assert.IsFalse(System.Text.RegularExpressions.Regex.IsMatch(logText, "Missing required assembly"));
//}
[Test]
public void ExtensionTest()
{
int Min = -10;
int Max = 20;
IEnumerable ints = Enumerable.Range(Min, Max - Min + 1).ToList();
int min = ints.FindMin(i => i);
int max = ints.FindMax(i => i);
Debug.Assert(min == Min);
Debug.Assert(max == Max);
Debug.Assert(max == ints.FindMin(i => -i));
}
///
/// Same assembly two different locations.
///
[Test, Ignore("?")]
public void SameAssemblyTwiceLoaded()
{
OpenTap.SessionLogs.Initialize("StartupLog.txt");
var trace = new TestTraceListener();
OpenTap.Log.AddListener(trace);
Directory.CreateDirectory("PluginManager.Test");
string path1 = "twice.dll";
GeneratePluginAssembly(path1, "Test1");
string path2 = "PluginManager.Test\\twice.dll";
GeneratePluginAssembly(path2, "Test1");
try
{
var d = AppDomain.CurrentDomain;
var assemblies = d.GetAssemblies().Where(asm => !asm.IsDynamic).Select(asm => Path.GetFileName(asm.Location)).ToList();
Assert.IsTrue(assemblies.Contains("twice.dll"));
Log.Flush();
var all = trace.allLog.ToString();
int times = 0;
foreach (var line in all.Split('\n'))
if (line.Contains("Warning") && line.Contains("Not loading") && line.Contains("twice.dll"))
times++;
Assert.AreEqual(times, 1);
}
finally
{
DeleteFile(path1); //loaded dlls cannot be deleted...
DeleteFile(path2);
}
}
static void GenerateAssemblyWithVersion(string assemblyFileName, string testStepName, string version, string strongNameKeyFile = null)
{
string cs = "";
cs += "using System.Reflection;\n";
cs += "using System.Runtime.InteropServices;\n";
cs += "[assembly: Guid(\"634896ca-7e6a-c66d-5ef7-2c2d2a5c3f31\")]\n";
cs += "[assembly: AssemblyVersion(\"" + version + "\")]\n";
cs += "public class " + testStepName + " { public void Run(){} }";
var asmname = Path.GetFileNameWithoutExtension(assemblyFileName);
var buildResult = CodeGen.BuildCode(cs, asmname, strongNameKeyFile);
Assert.IsTrue(buildResult.Success,buildResult.Log);
DeleteFile(assemblyFileName);
File.WriteAllBytes(assemblyFileName, buildResult.Bytes);
}
[Test]
public void SameAssemblyDifferentVersions()
{
Directory.CreateDirectory("Test1");
Directory.CreateDirectory("Test2");
Directory.CreateDirectory("Test3");
GenerateAssemblyWithVersion("Test1/Dual1.dll", "MyStep1", version: "1.0.0");
GenerateAssemblyWithVersion("Test2/Dual1.dll", "MyStep1", version: "1.2.0");
GenerateAssemblyWithVersion("Test3/Dual1.dll", "MyStep1", version: "1.1.0");
PluginManager.SearchAsync().Wait();
Assert.AreEqual(3,PluginManager.GetSearcher().Assemblies.Count(asm => asm.Name == "Dual1"));
var asm1 = Assembly.Load("Dual1");
Assert.IsTrue(asm1.GetName().Version.Minor == 2);
}
[Test]
public void SameAssemblyDifferentVersions3()
{
Directory.CreateDirectory("Test1");
Directory.CreateDirectory("Test2");
Directory.CreateDirectory("Test3");
GenerateAssemblyWithVersion("Test1/SameAssemblyDifferentVersions3.dll", "MyStep1", version: "1.0.0");
GenerateAssemblyWithVersion("Test2/SameAssemblyDifferentVersions3.dll", "MyStep1", version: "1.2.0");
GenerateAssemblyWithVersion("Test3/SameAssemblyDifferentVersions3.dll", "MyStep1", version: "1.1.0");
PluginManager.SearchAsync().Wait();
var asm1 = Assembly.Load("SameAssemblyDifferentVersions3, Version=1.1.0.0");
Assert.AreEqual(1, asm1.GetName().Version.Minor);
var asm2 = Assembly.Load("SameAssemblyDifferentVersions3, Version=1.0.0.0"); // here we get still get 1.1 even though we asked for 1.0, because it was already loaded.
Assert.AreEqual(1, asm2.GetName().Version.Minor);
}
interface FancyInterface
{
T X { get; set; }
}
class FancyType : FancyInterface
{
public int X { get; set; }
}
[Test]
public void ReflectionTest()
{
Assert.IsTrue(typeof(ITestStep).DescendsTo(typeof(object)));
Assert.IsTrue(typeof(ITestStep).DescendsTo(typeof(IValidatingObject)));
Assert.IsTrue(typeof(ITestStep).DescendsTo(typeof(System.ComponentModel.INotifyPropertyChanged)));
Assert.IsFalse(typeof(ITestStep).DescendsTo(typeof(IInstrument)));
Assert.IsTrue(typeof(Instrument).DescendsTo(typeof(IResource)));
Assert.IsTrue(typeof(FancyType).DescendsTo(typeof(FancyType<>)));
Assert.IsTrue(typeof(FancyType).DescendsTo(typeof(FancyInterface<>)));
Assert.IsTrue(typeof(FancyType).DescendsTo(typeof(FancyInterface)));
Assert.IsFalse(typeof(FancyType).DescendsTo(typeof(FancyInterface)));
}
///
/// Loading of dlls at runtime.
///
[Test]
public void DynamicLoading()
{
// test loading dlls at runtime, by the following procedure:
// 1. call search() and get number of TestSteps
// 2. Create a new dll with a TestStep in it. (by calling the compiler)
// 3. call search() and get number of TestSteps.
// 4. The number of steps should have increased by the number of steps in the new assembly.
// some of this is done in an AppDomain to be able to unload and delete the DLL
// guids are used to generate unique names.
// the new assembly must have a GUID.
var classname = "cls" + Guid.NewGuid().ToString().Replace("-", "");
var code =
"using OpenTap;\nusing System.Runtime.InteropServices;\nusing System.Reflection;\n"
+ "using System.Runtime.CompilerServices;\n[assembly: Guid(\"__GUID__\")]\n"
+ "namespace test{public class __NAME__ : TestStep\n{\npublic override void Run(){}\n}}\n";
code = code.Replace("__NAME__", classname).Replace("__GUID__", Guid.NewGuid().ToString());
var dll = string.Format("Keysight.Tap.Engine.TestModule_{0}.dll", Guid.NewGuid());
int prevcnt = PluginManager.GetAllPlugins().Count();
var results = CodeGen.BuildCode(code, Path.GetFileNameWithoutExtension(dll));
Assert.IsTrue(results.Success);
try
{
File.WriteAllBytes(dll, results.Bytes);
PluginManager.SearchAsync(); // starta new search to find the new assembly
int postcnt = PluginManager.GetAllPlugins().Count();
Assert.IsTrue(prevcnt == postcnt - 1);
}
finally
{
File.Delete(dll);
}
}
[Test]
public void MemorizerThreadTest()
{
List tasks = new List();
// Testing a very short operation in the memorizer
// With a relatively short running tasks to test if we can break the internals of the memorizer.
var numberThingTest = new Memorizer(thing => thing % 25)
{ MaxNumberOfElements = 25, SoftSizeDecayTime = TimeSpan.MaxValue };
var numberRevertThingTest = new Memorizer(thing => -thing)
{ MaxNumberOfElements = 25, SoftSizeDecayTime = TimeSpan.MaxValue };
for (int i = 0; i < 10; i++)
{
int _i = i;
tasks.Add(Task.Factory.StartNew(() =>
{
for (int j = 0; j < 1000; j++)
{
var value1 = numberThingTest.Invoke((_i + j) % 71); // ensure collision of keys.
var value2 = numberRevertThingTest.Invoke(value1);
Assert.IsTrue(value2 == -(((_i + j) % 71) % 25)); // check calculation.
}
}));
}
Task.WhenAll(tasks).Wait();
Task.WaitAll(tasks.ToArray());
}
double modf(double val, double mod)
{
return Math.Floor((val - Math.Floor(val / mod) * mod) * 1000.0) / 1000.0;
}
[Test]
public void MemorizerTest()
{
var times = Enumerable.Range(1, 4).Select(i =>
{
Memorizer mem = new Memorizer(d => modf(d, Math.PI), Math.Sin);
var timer = Stopwatch.StartNew();
var output = Enumerable.Range(0, (int)Math.Pow(3, i))
.Select(i2 => i2 / 20.0)
.Select(mem.Invoke).ToList();
return timer.Elapsed;
}).ToList();
var times2 = Enumerable.Range(1, 4).Select(i =>
{
//Memorizer mem = new Memorizer(d => modf(d, Math.PI), Math.Sin);
var timer = Stopwatch.StartNew();
var output = Enumerable.Range(0, (int)Math.Pow(3, i))
.Select(i2 => i2 / 20.0)
.Select(Math.Sin).ToList();
return timer.Elapsed;
}).ToList();
Debug.WriteLine(times);
Debug.WriteLine(times2);
}
public class Algorithm
{
public string Name { get; set; }
public Algorithm()
{
Name = "";
}
}
public class Sequence
{
[XmlAttribute("Name")]
public string Name { get; set; }
[XmlArray]
public List Items { get; set; }
public Sequence()
{
Name = "test2";
Items = new List { new Algorithm { Name = "Test Algorithm" } };
}
}
[Test]
public void TestLoadSequence()
{
XmlSerializer serializer = new XmlSerializer(typeof(Sequence));
string testData = "";
MemoryStream memStream = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(testData));
//var output = serializer.Deserialize(memStream);
memStream = new MemoryStream(10000);
serializer.Serialize(memStream, new Sequence() { Items = new List { new Algorithm { Name = "test" } } });
//Console.WriteLine(output);
System.Text.Encoding.ASCII.GetString(memStream.GetBuffer());
}
[Test]
public void TestTapPluginTypeAttribute()
{
var pluginCategory = typeof(ScpiInstrument).GetPluginType();
Assert.IsTrue(typeof(IInstrument) == pluginCategory[0]);
}
public class TestStep
{
// This is not a test step!
}
[Test]
public void SameNamedTypes()
{
Assert.AreEqual(typeof(OpenTap.TestStep), PluginManager.LocateType("OpenTap.TestStep"));
Assert.AreEqual(typeof(TestStep), PluginManager.LocateType(typeof(TestStep).FullName));
}
[Test]
public void RfConnectionDisplayAttributeTest()
{
// The following works because the ScpiInstrument gets discovered by PluginManager
// and gets its Display attribute from it.
var instr = TypeData.GetDerivedTypes().FirstOrDefault(x => x.Name.Contains("ScpiInstrument"));
Assert.IsTrue(instr.GetDisplayAttribute().Name == "Generic SCPI Instrument");
// The following did not work because RfConnection connection was not an ITapPlugin type.
var rf = TypeData.GetDerivedTypes().FirstOrDefault(x => x.Name == "OpenTap.RfConnection");
Assert.IsTrue(rf.GetDisplayAttribute().Name == "RF Connection");
}
[Test]
public void ResolveAssemblyVersions()
{
string asm1name = "../ResolveAssemblyVersions/ResolveAssemblyVersions.dll";
string asm2name = "../ResolveAssemblyVersions/ResolveAssemblyVersions.2.dll";
if(Directory.Exists("../ResolveAssemblyVersions/"))
Directory.Delete("../ResolveAssemblyVersions/", true);
Directory.CreateDirectory("../ResolveAssemblyVersions/");
GenerateAssemblyWithVersion(asm1name, "test", "2.2.0.0");
GenerateAssemblyWithVersion(asm2name, "test", "2.3.0.0");
var asm = Assembly.LoadFrom(asm1name);
var asm2 = Assembly.Load("ResolveAssemblyVersions, Version=2.1.0.0, Culture=neutral, PublicKeyToken=null");
// this should give a warning
var asm3 = Assembly.Load("ResolveAssemblyVersions, Version=2.3.0.0, Culture=neutral, PublicKeyToken=null");
bool noexec = false;
if (!noexec)
{
// This does not work. Once the assembly has failed to load once
// we wont try again, even if DirectoriesToSearch has been changed.
try
{
Assembly.Load("ResolveAssemblyVersions.2, Version=2.3.0.0, Culture=neutral, PublicKeyToken=null");
Assert.Fail("Load ResolveAssemblyVersions.2 should throw an exception");
}
catch
{
}
}
PluginManager.DirectoriesToSearch.Add(Path.GetFullPath(@"../ResolveAssemblyVersions/"));
var asm4 = Assembly.Load("ResolveAssemblyVersions.2, Version=2.3.0.0, Culture=neutral, PublicKeyToken=null");
var asm5 = Assembly.Load("ResolveAssemblyVersions.2, Version=2.1.0.0, Culture=neutral, PublicKeyToken=null");
Assert.IsTrue(asm == asm2);
Assert.IsTrue(asm3 == asm2);
Assert.IsTrue(asm4 != asm2);
Assert.IsTrue(asm4 == asm5);
}
[Test]
public void PluginInitializerTest()
{
var code =
"using OpenTap;"
+ "[assembly: PluginAssemblyAttribute (false, \"PluginAssemblyTest.StaticClassTest.Invoke\")]\n"
+ "namespace PluginAssemblyTest {public static class StaticClassTest{ public static bool Initialized; public static void Invoke(){Initialized = true;}}" +
" public class StaticClassTestTestStep : TestStep {public override void Run(){}}" +
"}\n";
var dirname = Guid.NewGuid().ToString();
var asmName = "PluginAssemblyTest";
var tempDir = Path.Combine(Path.GetTempPath(), asmName + dirname);
var fileName = Path.Combine(tempDir, asmName + ".dll");
if (File.Exists(fileName)) File.Delete(fileName);
Directory.CreateDirectory(tempDir);
PluginManager.DirectoriesToSearch.Add(tempDir);
var results = CodeGen.BuildCode(code, asmName);
File.WriteAllBytes(fileName, results.Bytes);
PluginManager.Search();
var td = TypeData.GetTypeData("PluginAssemblyTest.StaticClassTestTestStep");
Assert.IsNotNull(td);
var td2 = TypeData.GetTypeData("PluginAssemblyTest.StaticClassTest");
Assert.IsNotNull(td);
var initialized = (bool)td2.As().Type.GetField("Initialized").GetValue(null);
Assert.IsTrue(initialized);
PluginManager.DirectoriesToSearch.Remove(tempDir);
}
}
public class SimpleListTest : TestStep
{
public List Doubles { get; set; }
public List Floats { get; set; }
public List Decimals { get; set; }
public List Bytes { get; set; }
public List Bools { get; set; }
public double ADouble { get; set; }
public int AInt { get; set; }
public override void Run()
{
throw new NotImplementedException();
}
}
}