diff --git a/.vsts-dotnet-ci.yml b/.vsts-dotnet-ci.yml index 0a721add5f..1f9d585e97 100644 --- a/.vsts-dotnet-ci.yml +++ b/.vsts-dotnet-ci.yml @@ -10,7 +10,7 @@ phases: name: Windows_NT buildScript: build.cmd queue: - name: DotNetCore-Windows + name: Hosted VS2017 - template: /build/ci/phase-template.yml parameters: diff --git a/src/Microsoft.ML.PipelineInference/ColumnTypeInference.cs b/src/Microsoft.ML.PipelineInference/ColumnTypeInference.cs index 5c589c50bf..03aca5c19b 100644 --- a/src/Microsoft.ML.PipelineInference/ColumnTypeInference.cs +++ b/src/Microsoft.ML.PipelineInference/ColumnTypeInference.cs @@ -132,10 +132,10 @@ public void Apply(IntermediateColumn[] columns) { if (!col.RawData.Skip(1) .All(x => - { - bool value; - return Conversions.Instance.TryParse(ref x, out value); - }) + { + bool value; + return Conversions.Instance.TryParse(ref x, out value); + }) ) { continue; @@ -157,10 +157,10 @@ public void Apply(IntermediateColumn[] columns) { if (!col.RawData.Skip(1) .All(x => - { - Single value; - return Conversions.Instance.TryParse(ref x, out value); - }) + { + Single value; + return Conversions.Instance.TryParse(ref x, out value); + }) ) { continue; @@ -240,7 +240,7 @@ private static InferenceResult InferTextFileColumnTypesCore(IHostEnvironment env ch.AssertValue(fileSource); ch.AssertValue(args); - if (args.ColumnCount==0) + if (args.ColumnCount == 0) { ch.Error("Too many empty columns for automatic inference."); return InferenceResult.Fail(); diff --git a/src/Microsoft.ML.PipelineInference/PurposeInference.cs b/src/Microsoft.ML.PipelineInference/PurposeInference.cs index b2f57328a8..7ea19127a4 100644 --- a/src/Microsoft.ML.PipelineInference/PurposeInference.cs +++ b/src/Microsoft.ML.PipelineInference/PurposeInference.cs @@ -339,7 +339,7 @@ public static InferenceResult InferPurposes(IHostEnvironment env, IDataView data if (dataRoles != null) { var items = dataRoles.Schema.GetColumnRoles(); - foreach(var item in items) + foreach (var item in items) { Enum.TryParse(item.Key.Value, out ColumnPurpose purpose); var col = cols.Find(x => x.ColumnName == item.Value.Name); diff --git a/src/Microsoft.ML.PipelineInference/RecipeInference.cs b/src/Microsoft.ML.PipelineInference/RecipeInference.cs index c19fabb251..65c44c6c6e 100644 --- a/src/Microsoft.ML.PipelineInference/RecipeInference.cs +++ b/src/Microsoft.ML.PipelineInference/RecipeInference.cs @@ -547,7 +547,7 @@ public static SuggestedRecipe.SuggestedLearner[] AllowedLearners(IHostEnvironmen LearnerName = tt.Name }; - if (sl.PipelineNode != null && availableLearnersList.FirstOrDefault(l=> l.Name.Equals(sl.PipelineNode.GetEpName())) != null) + if (sl.PipelineNode != null && availableLearnersList.FirstOrDefault(l => l.Name.Equals(sl.PipelineNode.GetEpName())) != null) learners.Add(sl); } diff --git a/test/Microsoft.ML.Predictor.Tests/Global.cs b/test/Microsoft.ML.Predictor.Tests/Global.cs index 39f92e9762..af4292e6a9 100644 --- a/test/Microsoft.ML.Predictor.Tests/Global.cs +++ b/test/Microsoft.ML.Predictor.Tests/Global.cs @@ -2,12 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.ML.Runtime.Internal.Internallearn.Test; using Xunit; -namespace Microsoft.ML.Runtime.Internal.Internallearn.Test +namespace Microsoft.ML.Runtime.RunTests { - public sealed class GlobalRT + public sealed class Global { + // See https://github.com/dotnet/machinelearning/issues/1095 + public const string AutoInferenceAndPipelineSweeperTestCollectionName = "TestPipelineSweeper and TestAutoInference should not be run at the same time since it causes deadlocks"; + [Fact(Skip = "Disabled")] public void AssertHandlerTest() { diff --git a/test/Microsoft.ML.Predictor.Tests/TestAutoInference.cs b/test/Microsoft.ML.Predictor.Tests/TestAutoInference.cs index 9d0af99420..6a6a9409ef 100644 --- a/test/Microsoft.ML.Predictor.Tests/TestAutoInference.cs +++ b/test/Microsoft.ML.Predictor.Tests/TestAutoInference.cs @@ -16,6 +16,7 @@ namespace Microsoft.ML.Runtime.RunTests { + [Collection(Global.AutoInferenceAndPipelineSweeperTestCollectionName)] public sealed class TestAutoInference : BaseTestBaseline { public TestAutoInference(ITestOutputHelper helper) diff --git a/test/Microsoft.ML.Predictor.Tests/TestPipelineSweeper.cs b/test/Microsoft.ML.Predictor.Tests/TestPipelineSweeper.cs index e3f144b215..f5a527a91e 100644 --- a/test/Microsoft.ML.Predictor.Tests/TestPipelineSweeper.cs +++ b/test/Microsoft.ML.Predictor.Tests/TestPipelineSweeper.cs @@ -15,6 +15,7 @@ namespace Microsoft.ML.Runtime.RunTests { + [Collection(Global.AutoInferenceAndPipelineSweeperTestCollectionName)] public sealed class TestPipelineSweeper : BaseTestBaseline { public TestPipelineSweeper(ITestOutputHelper helper) @@ -22,9 +23,9 @@ public TestPipelineSweeper(ITestOutputHelper helper) { } - protected override void InitializeCore() + protected override void Initialize() { - base.InitializeCore(); + base.Initialize(); Env.ComponentCatalog.RegisterAssembly(typeof(AutoInference).Assembly); } diff --git a/test/Microsoft.ML.Predictor.Tests/TestPredictors.cs b/test/Microsoft.ML.Predictor.Tests/TestPredictors.cs index fb1ae555ff..6ebfa5fc05 100644 --- a/test/Microsoft.ML.Predictor.Tests/TestPredictors.cs +++ b/test/Microsoft.ML.Predictor.Tests/TestPredictors.cs @@ -28,9 +28,9 @@ namespace Microsoft.ML.Runtime.RunTests /// public sealed partial class TestPredictors : BaseTestPredictors { - protected override void InitializeCore() + protected override void Initialize() { - base.InitializeCore(); + base.Initialize(); InitializeEnvironment(Env); } diff --git a/test/Microsoft.ML.TestFramework/BaseTestBaseline.cs b/test/Microsoft.ML.TestFramework/BaseTestBaseline.cs index 455693ab22..ad077a85d3 100644 --- a/test/Microsoft.ML.TestFramework/BaseTestBaseline.cs +++ b/test/Microsoft.ML.TestFramework/BaseTestBaseline.cs @@ -9,7 +9,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; @@ -22,23 +21,12 @@ namespace Microsoft.ML.Runtime.RunTests /// /// This is a base test class designed to support baseline comparison. /// - public abstract partial class BaseTestBaseline : BaseTestClass, IDisposable + public abstract partial class BaseTestBaseline : BaseTestClass { public const decimal Tolerance = 10_000_000; - private readonly ITestOutputHelper _output; - protected BaseTestBaseline(ITestOutputHelper helper) : base(helper) + protected BaseTestBaseline(ITestOutputHelper output) : base(output) { - _output = helper; - ITest test = (ITest)helper.GetType().GetField("test", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(helper); - FullTestName = test.TestCase.TestMethod.TestClass.Class.Name + "." + test.TestCase.TestMethod.Method.Name; - TestName = test.TestCase.TestMethod.Method.Name; - Init(); - } - - void IDisposable.Dispose() - { - Cleanup(); } internal const string RawSuffix = ".raw"; @@ -93,11 +81,10 @@ void IDisposable.Dispose() private bool _normal; private bool _passed; - public string TestName { get; set; } - public string FullTestName { get; set; } - - public void Init() + protected override void Initialize() { + base.Initialize(); + // Create the output and log directories. Contracts.Check(Directory.Exists(Path.Combine(RootDir, TestDir, "BaselineOutput"))); string logDir = Path.Combine(OutDir, _logRootRelPath); @@ -111,15 +98,17 @@ public void Init() _passed = true; Env = new ConsoleEnvironment(42, outWriter: LogWriter, errWriter: LogWriter) .AddStandardComponents(); - InitializeCore(); } - public void Cleanup() + // This method is used by subclass to dispose of disposable objects + // such as LocalEnvironment. + // It is called as a first step in test clean up. + protected override void Cleanup() { - // REVIEW: Is there a way to determine whether the test completed normally? - // Requiring tests to call Done() is hokey. + if (Env != null) + Env.Dispose(); + Env = null; - CleanupCore(); Contracts.Assert(IsActive); Log("Test {0}: {1}: {2}", TestName, _normal ? "completed normally" : "aborted", @@ -128,20 +117,8 @@ public void Cleanup() Contracts.AssertValue(LogWriter); LogWriter.Dispose(); LogWriter = null; - } - - protected virtual void InitializeCore() - { - } - // This method is used by subclass to dispose of disposable objects - // such as LocalEnvironment. - // It is called as a first step in test clean up. - protected virtual void CleanupCore() - { - if (Env != null) - Env.Dispose(); - Env = null; + base.Cleanup(); } protected bool IsActive { get { return LogWriter != null; } } @@ -210,7 +187,7 @@ protected void Log(string msg) Contracts.Assert(IsActive); Contracts.AssertValue(LogWriter); LogWriter.WriteLine(msg); - _output.WriteLine(msg); + Output.WriteLine(msg); } protected void Log(string fmt, params object[] args) @@ -218,7 +195,7 @@ protected void Log(string fmt, params object[] args) Contracts.Assert(IsActive); Contracts.AssertValue(LogWriter); LogWriter.WriteLine(fmt, args); - _output.WriteLine(fmt, args); + Output.WriteLine(fmt, args); } protected string GetBaselineDir(string subDir) @@ -898,4 +875,4 @@ public void TestTimeRegex() Done(); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.ML.TestFramework/BaseTestClass.cs b/test/Microsoft.ML.TestFramework/BaseTestClass.cs index b5d7a23db3..034565592e 100644 --- a/test/Microsoft.ML.TestFramework/BaseTestClass.cs +++ b/test/Microsoft.ML.TestFramework/BaseTestClass.cs @@ -3,19 +3,24 @@ // See the LICENSE file in the project root for more information. using Microsoft.ML.Runtime.Internal.Internallearn.Test; +using System; using System.Globalization; using System.IO; +using System.Reflection; using System.Threading; using Xunit.Abstractions; namespace Microsoft.ML.TestFramework { - public class BaseTestClass + public class BaseTestClass : IDisposable { private readonly string _rootDir; private readonly string _outDir; private readonly string _dataRoot; + public string TestName { get; set; } + public string FullTestName { get; set; } + static BaseTestClass() => GlobalBase.AssemblyInit(); public BaseTestClass(ITestOutputHelper output) @@ -31,6 +36,28 @@ public BaseTestClass(ITestOutputHelper output) Directory.CreateDirectory(_outDir); _dataRoot = Path.Combine(_rootDir, "test", "data"); Output = output; + + ITest test = (ITest)output.GetType().GetField("test", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(output); + FullTestName = test.TestCase.TestMethod.TestClass.Class.Name + "." + test.TestCase.TestMethod.Method.Name; + TestName = test.TestCase.TestMethod.Method.Name; + + // write to the console when a test starts and stops so we can identify any test hangs/deadlocks in CI + Console.WriteLine($"Starting test: {FullTestName}"); + Initialize(); + } + + void IDisposable.Dispose() + { + Cleanup(); + Console.WriteLine($"Finished test: {FullTestName}"); + } + + protected virtual void Initialize() + { + } + + protected virtual void Cleanup() + { } protected string RootDir => _rootDir; diff --git a/test/Microsoft.ML.TestFramework/DataPipe/Parquet.cs b/test/Microsoft.ML.TestFramework/DataPipe/Parquet.cs index b3b68c5a8a..eeebdaf6d7 100644 --- a/test/Microsoft.ML.TestFramework/DataPipe/Parquet.cs +++ b/test/Microsoft.ML.TestFramework/DataPipe/Parquet.cs @@ -10,9 +10,9 @@ namespace Microsoft.ML.Runtime.RunTests { public sealed partial class TestParquet : TestDataPipeBase { - protected override void InitializeCore() + protected override void Initialize() { - base.InitializeCore(); + base.Initialize(); Env.ComponentCatalog.RegisterAssembly(typeof(ParquetLoader).Assembly); } diff --git a/test/Microsoft.ML.TestFramework/GlobalBase.cs b/test/Microsoft.ML.TestFramework/GlobalBase.cs index 8ecdb24b36..ecb185b625 100644 --- a/test/Microsoft.ML.TestFramework/GlobalBase.cs +++ b/test/Microsoft.ML.TestFramework/GlobalBase.cs @@ -11,6 +11,7 @@ // } using System; +using System.Runtime.InteropServices; using Xunit; namespace Microsoft.ML.Runtime.Internal.Internallearn.Test @@ -22,6 +23,28 @@ public static void AssemblyInit() System.Diagnostics.Debug.WriteLine("*** Setting test assertion handler"); var prev = Contracts.SetAssertHandler(AssertHandler); Contracts.Check(prev == null, "Expected to replace null assertion handler!"); + + // HACK: ensure MklImports is loaded very early in the tests so it doesn't deadlock while loading it later. + // See https://github.com/dotnet/machinelearning/issues/1073 + Mkl.PptrfInternal(Mkl.Layout.RowMajor, Mkl.UpLo.Up, 0, Array.Empty()); + } + + private static class Mkl + { + public enum Layout + { + RowMajor = 101, + ColMajor = 102 + } + + public enum UpLo : byte + { + Up = (byte)'U', + Lo = (byte)'L' + } + + [DllImport("MklImports", EntryPoint = "LAPACKE_dpptrf")] + public static extern int PptrfInternal(Layout layout, UpLo uplo, int n, double[] ap); } public static void AssemblyCleanup()