From 641abbd423d376b7aeee7b6945a58ae0bf2af7e1 Mon Sep 17 00:00:00 2001 From: Meinrad Recheis Date: Mon, 8 Apr 2019 10:29:04 +0200 Subject: [PATCH 1/3] ControlDependenciesTest: added all the other test cases but currently can't translate them into c# --- .../ControlDependenciesTest.cs | 291 +++++++++++++++++- 1 file changed, 279 insertions(+), 12 deletions(-) diff --git a/test/TensorFlowNET.UnitTest/ControlDependenciesTest.cs b/test/TensorFlowNET.UnitTest/ControlDependenciesTest.cs index c2c40337..20821811 100644 --- a/test/TensorFlowNET.UnitTest/ControlDependenciesTest.cs +++ b/test/TensorFlowNET.UnitTest/ControlDependenciesTest.cs @@ -4,11 +4,12 @@ using System.Linq; using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using Tensorflow; +using Tensorflow.Eager; namespace TensorFlowNET.UnitTest { /// - /// tensorflow/python/framework/ops_test.py + /// excerpt of tensorflow/python/framework/ops_test.py /// [TestClass] public class ControlDependenciesTest : Python @@ -17,22 +18,288 @@ namespace TensorFlowNET.UnitTest public void TestBasic() { var graph = tf.Graph().as_default(); - Tensor a=null, b = null, c = null, d = null, e = null; + Tensor a = null, b = null, c = null, d = null, e = null; with(graph, g => { - a = constant_op.constant(1.0); - b = constant_op.constant(1.0); - with(g.control_dependencies(new ITensorOrOperation[] {a}), x => - { - c = constant_op.constant(1.0); - d = array_ops.identity(b); - e = array_ops.identity(c); - }); + a = constant_op.constant(1.0); + b = constant_op.constant(1.0); + with(g.control_dependencies(new ITensorOrOperation[] { a }), x => + { + c = constant_op.constant(1.0); + d = array_ops.identity(b); + e = array_ops.identity(c); + }); }); - Assert.IsTrue(Enumerable.SequenceEqual(c.op.control_inputs, new[] {a.op})); - Assert.IsTrue(Enumerable.SequenceEqual(d.op.control_inputs, new[] {a.op})); + Assert.IsTrue(Enumerable.SequenceEqual(c.op.control_inputs, new[] { a.op })); + Assert.IsTrue(Enumerable.SequenceEqual(d.op.control_inputs, new[] { a.op })); // e should be dominated by c. Assert.AreEqual(0, e.op.control_inputs.Length); } + + [Ignore("Part of this test is not compiling")] + [TestMethod] + public void TestEager() + { + Tensor a = null, b = null, c = null, d = null, e = null; + var calls = 0; + Func future = () => + { + + calls += 1; + return constant_op.constant(2.0); + }; + using (var opts = new ContextOptions()) + using (var status = new Status()) + using (var context = new Context(opts, status)) + { + if (context.executing_eagerly()) + { + // TODO: make this compile (see original Python code below) + //a = constant_op.constant(1.0); + //b = future; // <--- {henon} obviously, this doesn't compile, looks like control_dependencies needs to be able to take callables as well. + //with(ops.control_dependencies(new Operation[] {a, b}), ctrl => + //{ + // return c = constant_op.constant(3.0); + //}); + //Assert.AreEqual(calls, 1); + } + else + { + var graph = tf.Graph(); + with(graph.as_default(), g => + { + a = constant_op.constant(1.0); + b = future(); + with(g.control_dependencies(new ITensorOrOperation[] {a, b}), ctrl => + { + c = constant_op.constant(3.0); + }); + Assert.IsTrue(Enumerable.SequenceEqual(c.op.control_inputs, new[] {a.op, b.op})); + Assert.AreEqual(1, calls); + }); + + } + } +/* + def testEager(self): + def future(): + future.calls += 1 + return constant_op.constant(2.0) + future.calls = 0 + + if context.executing_eagerly(): + a = constant_op.constant(1.0) + b = future + with ops.control_dependencies([a, b]): + c = constant_op.constant(3.0) + self.assertEqual(future.calls, 1) + else: + g = ops.Graph() + with g.as_default(): + a = constant_op.constant(1.0) + b = future() + with g.control_dependencies([a, b]): + c = constant_op.constant(3.0) + self.assertEqual(c.op.control_inputs, [a.op, b.op]) + self.assertEqual(future.calls, 1) +*/ + } + + + [Ignore("How to translate _apply_op into c#?")] + [TestMethod] + public void TestBasicWithConversion() + { + /* + def testBasicWithConversion(self): + g = ops.Graph() + a = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + + class ConvertibleObj(object): + + def _as_graph_element(self): + return a + + with g.control_dependencies([ConvertibleObj()]): + c = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + + self.assertEqual(c.op.control_inputs, [a.op]) + */ + } + + [Ignore("How to translate _apply_op into c#?")] + [TestMethod] + public void TestNested() + { + /* + def testNested(self): + g = ops.Graph() + a_1 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + a_2 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + a_3 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + a_4 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + + with g.control_dependencies([a_1, a_2, a_3, a_4]): + b_1 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + + with g.control_dependencies([a_1]): + with g.control_dependencies([a_2]): + with g.control_dependencies([a_3]): + with g.control_dependencies([a_4]): + b_2 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + + self.assertItemsEqual([a_1.op, a_2.op, a_3.op, a_4.op], + b_1.op.control_inputs) + self.assertItemsEqual(b_1.op.control_inputs, b_2.op.control_inputs) + */ + } + + + [Ignore("How to translate _apply_op into c#?")] + [TestMethod] + public void TestClear() + { + /* + def testClear(self): + g = ops.Graph() + a_1 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + a_2 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + a_3 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + a_4 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + + with g.control_dependencies([a_1]): + with g.control_dependencies([a_2]): + with g.control_dependencies(None): + with g.control_dependencies([a_3]): + with g.control_dependencies([a_4]): + # deps [a_3, a_4] + b_3_4 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + # deps = [a_3] + b_3 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + # deps back to None + b_none = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + # deps back to [a_1, a_2] + b_1_2 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + # deps back to [a_1] + b_1 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + with g.control_dependencies(None): + # deps are None again + b_none2 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + + self.assertItemsEqual([a_3.op, a_4.op], b_3_4.op.control_inputs) + self.assertItemsEqual([a_3.op], b_3.op.control_inputs) + self.assertItemsEqual([], b_none.op.control_inputs) + self.assertItemsEqual([a_1.op, a_2.op], b_1_2.op.control_inputs) + self.assertItemsEqual([a_1.op], b_1.op.control_inputs) + self.assertItemsEqual([], b_none2.op.control_inputs) + */ + } + + [Ignore("How to translate _apply_op into c#?")] + [TestMethod] + public void TestComplex() + { + /* + def testComplex(self): + g = ops.Graph() + + # Usage pattern: + # * Nodes a_i are constants defined at the outermost scope, and are used + # as control inputs for the ith nested scope. + # * Nodes b_i are defined as Mul(a_3, a_4) at each scope. + # * Nodes c_i are defined as Mul(a_1, b_1) at each scope. + # * Nodes d_i are defined as Mul(b_i, c_i) at each scope. + # * Nodes e_i are defined as Mul(e_i-1, e_i-1) at each scope i > 1. + + a_1 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + a_2 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + a_3 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + a_4 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + + with g.control_dependencies([a_1]): + b_1 = _apply_op(g, "TwoFloatInputsFloatOutput", [a_3, a_4], + [dtypes.float32]) + c_1 = _apply_op(g, "TwoFloatInputsFloatOutput", [a_1, b_1], + [dtypes.float32]) + d_1 = _apply_op(g, "TwoFloatInputsFloatOutput", [b_1, c_1], + [dtypes.float32]) + e_1 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + with g.control_dependencies([a_2]): + b_2 = _apply_op(g, "TwoFloatInputsFloatOutput", [a_3, a_4], + [dtypes.float32]) + c_2 = _apply_op(g, "TwoFloatInputsFloatOutput", [a_1, b_1], + [dtypes.float32]) + d_2 = _apply_op(g, "TwoFloatInputsFloatOutput", [b_2, c_2], + [dtypes.float32]) + e_2 = _apply_op(g, "TwoFloatInputsFloatOutput", [e_1, e_1], + [dtypes.float32]) + with g.control_dependencies([a_3]): + b_3 = _apply_op(g, "TwoFloatInputsFloatOutput", [a_3, a_4], + [dtypes.float32]) + c_3 = _apply_op(g, "TwoFloatInputsFloatOutput", [a_1, b_1], + [dtypes.float32]) + d_3 = _apply_op(g, "TwoFloatInputsFloatOutput", [b_3, c_3], + [dtypes.float32]) + e_3 = _apply_op(g, "TwoFloatInputsFloatOutput", [e_2, e_2], + [dtypes.float32]) + with g.control_dependencies([a_4]): + b_4 = _apply_op(g, "TwoFloatInputsFloatOutput", [a_3, a_4], + [dtypes.float32]) + c_4 = _apply_op(g, "TwoFloatInputsFloatOutput", [a_1, b_1], + [dtypes.float32]) + d_4 = _apply_op(g, "TwoFloatInputsFloatOutput", [b_4, c_4], + [dtypes.float32]) + e_4 = _apply_op(g, "TwoFloatInputsFloatOutput", [e_3, e_3], + [dtypes.float32]) + + self.assertItemsEqual([a_1.op], b_1.op.control_inputs) + self.assertItemsEqual([a_1.op, a_2.op], b_2.op.control_inputs) + self.assertItemsEqual([a_1.op, a_2.op], b_3.op.control_inputs) + self.assertItemsEqual([a_1.op, a_2.op], b_4.op.control_inputs) + + self.assertItemsEqual([], c_1.op.control_inputs) + self.assertItemsEqual([a_2.op], c_2.op.control_inputs) + self.assertItemsEqual([a_2.op, a_3.op], c_3.op.control_inputs) + self.assertItemsEqual([a_2.op, a_3.op, a_4.op], c_4.op.control_inputs) + + self.assertItemsEqual([], d_1.op.control_inputs) + self.assertItemsEqual([], d_2.op.control_inputs) + self.assertItemsEqual([], d_3.op.control_inputs) + self.assertItemsEqual([], d_4.op.control_inputs) + + self.assertItemsEqual([a_1.op], e_1.op.control_inputs) + self.assertItemsEqual([a_2.op], e_2.op.control_inputs) + self.assertItemsEqual([a_3.op], e_3.op.control_inputs) + self.assertItemsEqual([a_4.op], e_4.op.control_inputs) + */ + } + + [Ignore("How to translate _apply_op into c#?")] + [TestMethod] + public void TestRepeatedDependency() + { + /* + def testRepeatedDependency(self): + g = ops.Graph() + a = g.create_op("TwoFloatOutputs", [], [dtypes.float32, dtypes.float32]) + a_0, a_1 = a.outputs + with g.control_dependencies([a_0]): + b = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + with g.control_dependencies([a_1]): + c = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + + self.assertEqual(b.op.control_inputs, [a]) + self.assertEqual(c.op.control_inputs, [a]) + + def testNoControlDependencyWithDataDependency(self): + g = ops.Graph() + a = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + with g.control_dependencies([a]): + b = _apply_op(g, "Identity", [a], [dtypes.float32]) + + self.assertEqual(b.op.control_inputs, []) + */ + } + } } From 98ff3c53a271610e263634ef5dea4dcf99991526 Mon Sep 17 00:00:00 2001 From: Meinrad Recheis Date: Mon, 8 Apr 2019 11:26:47 +0200 Subject: [PATCH 2/3] added PythonTest, a base class for tests that holds useful assertions like AssertItemsEqual --- src/TensorFlowNET.Core/Python.cs | 3 + .../ControlDependenciesTest.cs | 147 +++++++++++------- test/TensorFlowNET.UnitTest/PythonTest.cs | 27 ++++ 3 files changed, 124 insertions(+), 53 deletions(-) create mode 100644 test/TensorFlowNET.UnitTest/PythonTest.cs diff --git a/src/TensorFlowNET.Core/Python.cs b/src/TensorFlowNET.Core/Python.cs index d73b58d2..ce859ec8 100644 --- a/src/TensorFlowNET.Core/Python.cs +++ b/src/TensorFlowNET.Core/Python.cs @@ -17,6 +17,9 @@ namespace Tensorflow Console.WriteLine(obj.ToString()); } + protected int len(Array a) + => a.Length; + protected IEnumerable range(int end) { return Enumerable.Range(0, end); diff --git a/test/TensorFlowNET.UnitTest/ControlDependenciesTest.cs b/test/TensorFlowNET.UnitTest/ControlDependenciesTest.cs index 20821811..3ca25cfc 100644 --- a/test/TensorFlowNET.UnitTest/ControlDependenciesTest.cs +++ b/test/TensorFlowNET.UnitTest/ControlDependenciesTest.cs @@ -12,7 +12,7 @@ namespace TensorFlowNET.UnitTest /// excerpt of tensorflow/python/framework/ops_test.py /// [TestClass] - public class ControlDependenciesTest : Python + public class ControlDependenciesTest : PythonTest { [TestMethod] public void TestBasic() @@ -70,48 +70,64 @@ namespace TensorFlowNET.UnitTest { a = constant_op.constant(1.0); b = future(); - with(g.control_dependencies(new ITensorOrOperation[] {a, b}), ctrl => - { - c = constant_op.constant(3.0); - }); - Assert.IsTrue(Enumerable.SequenceEqual(c.op.control_inputs, new[] {a.op, b.op})); + with(g.control_dependencies(new ITensorOrOperation[] { a, b }), ctrl => + { + c = constant_op.constant(3.0); + }); + Assert.IsTrue(Enumerable.SequenceEqual(c.op.control_inputs, new[] { a.op, b.op })); Assert.AreEqual(1, calls); }); } } -/* - def testEager(self): - def future(): - future.calls += 1 - return constant_op.constant(2.0) - future.calls = 0 - - if context.executing_eagerly(): - a = constant_op.constant(1.0) - b = future - with ops.control_dependencies([a, b]): - c = constant_op.constant(3.0) - self.assertEqual(future.calls, 1) - else: - g = ops.Graph() - with g.as_default(): - a = constant_op.constant(1.0) - b = future() - with g.control_dependencies([a, b]): - c = constant_op.constant(3.0) - self.assertEqual(c.op.control_inputs, [a.op, b.op]) - self.assertEqual(future.calls, 1) -*/ - } + /* + def testEager(self): + def future(): + future.calls += 1 + return constant_op.constant(2.0) + future.calls = 0 + + if context.executing_eagerly(): + a = constant_op.constant(1.0) + b = future + with ops.control_dependencies([a, b]): + c = constant_op.constant(3.0) + self.assertEqual(future.calls, 1) + else: + g = ops.Graph() + with g.as_default(): + a = constant_op.constant(1.0) + b = future() + with g.control_dependencies([a, b]): + c = constant_op.constant(3.0) + self.assertEqual(c.op.control_inputs, [a.op, b.op]) + self.assertEqual(future.calls, 1) + */ + } - [Ignore("How to translate _apply_op into c#?")] + // Note: {henon}, all tests below use the function _apply_op which is not really portable in C#, see original source below + // but I think _apply_op(...) can just be replaced by g.create_op(...). + /* +def _apply_op(g, *args, **kwargs): + op = g.create_op(*args, **kwargs) + if len(op.outputs) == 1: + return op.outputs[0] + else: + return op.outputs + */ + + + [Ignore("")] [TestMethod] public void TestBasicWithConversion() { + var g = ops.get_default_graph(); + // Note: _apply_op can be replaced by g.create_op + var a = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); + // TODO: ConvertibleObj, see original source below /* - def testBasicWithConversion(self): + def testBasicWithConversion(self): g = ops.Graph() a = _apply_op(g, "FloatOutput", [], [dtypes.float32]) @@ -127,31 +143,56 @@ namespace TensorFlowNET.UnitTest */ } - [Ignore("How to translate _apply_op into c#?")] + //[Ignore()] [TestMethod] public void TestNested() { + var g = ops.get_default_graph(); + var a_1 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); + var a_2 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); + var a_3 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); + var a_4 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); + Operation b_1 = null, b_2 = null; + with(g.control_dependencies(new ITensorOrOperation[] { a_1, a_2, a_3, a_4 }), ctrl => + { + b_1 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); + }); + with(g.control_dependencies(new ITensorOrOperation[] { a_1 }), ctrl1 => + { + with(g.control_dependencies(new ITensorOrOperation[] { a_2 }), ctrl2 => + { + with(g.control_dependencies(new ITensorOrOperation[] { a_3 }), ctrl3 => + { + with(g.control_dependencies(new ITensorOrOperation[] { a_4 }), ctrl4 => + { + b_2 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); + }); + }); + }); + }); + AssertItemsEqual(new[] {a_1.op, a_2.op, a_3.op, a_4.op}, b_1.op.control_inputs); + AssertItemsEqual(b_1.op.control_inputs, b_2.op.control_inputs); /* - def testNested(self): - g = ops.Graph() - a_1 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) - a_2 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) - a_3 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) - a_4 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) - - with g.control_dependencies([a_1, a_2, a_3, a_4]): - b_1 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) - - with g.control_dependencies([a_1]): - with g.control_dependencies([a_2]): - with g.control_dependencies([a_3]): - with g.control_dependencies([a_4]): - b_2 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) - - self.assertItemsEqual([a_1.op, a_2.op, a_3.op, a_4.op], - b_1.op.control_inputs) - self.assertItemsEqual(b_1.op.control_inputs, b_2.op.control_inputs) - */ +def testNested(self): +g = ops.Graph() +a_1 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) +a_2 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) +a_3 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) +a_4 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + +with g.control_dependencies([a_1, a_2, a_3, a_4]): + b_1 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + +with g.control_dependencies([a_1]): + with g.control_dependencies([a_2]): + with g.control_dependencies([a_3]): + with g.control_dependencies([a_4]): + b_2 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) + +self.assertItemsEqual([a_1.op, a_2.op, a_3.op, a_4.op], + b_1.op.control_inputs) +self.assertItemsEqual(b_1.op.control_inputs, b_2.op.control_inputs) + */ } diff --git a/test/TensorFlowNET.UnitTest/PythonTest.cs b/test/TensorFlowNET.UnitTest/PythonTest.cs new file mode 100644 index 00000000..a20848c2 --- /dev/null +++ b/test/TensorFlowNET.UnitTest/PythonTest.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Tensorflow; + +namespace TensorFlowNET.UnitTest +{ + /// + /// Use as base class for test classes to get additional assertions + /// + public class PythonTest : Python + { + public void AssertItemsEqual(ICollection expected, ICollection given) + { + Assert.IsNotNull(expected); + Assert.IsNotNull(given); + var e = expected.OfType().ToArray(); + var g = given.OfType().ToArray(); + Assert.AreEqual(e.Length, g.Length, $"The collections differ in length expected {e.Length} but got {g.Length}"); + for(int i=0; i Date: Mon, 8 Apr 2019 11:27:24 +0200 Subject: [PATCH 3/3] ops.py.cs: fixed a bug in _NodeDef --- src/TensorFlowNET.Core/ops.py.cs | 7 ++++--- test/TensorFlowNET.UnitTest/ControlDependenciesTest.cs | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/TensorFlowNET.Core/ops.py.cs b/src/TensorFlowNET.Core/ops.py.cs index 514530f3..ff41e261 100644 --- a/src/TensorFlowNET.Core/ops.py.cs +++ b/src/TensorFlowNET.Core/ops.py.cs @@ -193,11 +193,12 @@ namespace Tensorflow node_def.Op = op_type; node_def.Name = name; - foreach (var attr in attrs) + if (attrs != null) { - node_def.Attr.Add(attr.Key, attr.Value); + foreach (var attr in attrs) + node_def.Attr.Add(attr.Key, attr.Value); } - + return node_def; } diff --git a/test/TensorFlowNET.UnitTest/ControlDependenciesTest.cs b/test/TensorFlowNET.UnitTest/ControlDependenciesTest.cs index 3ca25cfc..5146ae57 100644 --- a/test/TensorFlowNET.UnitTest/ControlDependenciesTest.cs +++ b/test/TensorFlowNET.UnitTest/ControlDependenciesTest.cs @@ -143,7 +143,7 @@ def _apply_op(g, *args, **kwargs): */ } - //[Ignore()] + [Ignore("Fails with message: Op type not registered 'FloatOutput' in binary running on ...")] [TestMethod] public void TestNested() { @@ -196,7 +196,7 @@ self.assertItemsEqual(b_1.op.control_inputs, b_2.op.control_inputs) } - [Ignore("How to translate _apply_op into c#?")] + [Ignore("will fail due to unsupported op 'FloatOutput'")] [TestMethod] public void TestClear() { @@ -236,7 +236,7 @@ self.assertItemsEqual(b_1.op.control_inputs, b_2.op.control_inputs) */ } - [Ignore("How to translate _apply_op into c#?")] + [Ignore("will fail due to unsupported op 'FloatOutput'")] [TestMethod] public void TestComplex() { @@ -315,7 +315,7 @@ self.assertItemsEqual(b_1.op.control_inputs, b_2.op.control_inputs) */ } - [Ignore("How to translate _apply_op into c#?")] + [Ignore("will fail due to unsupported op 'FloatOutput'")] [TestMethod] public void TestRepeatedDependency() {