Skip to content
This repository has been archived by the owner on Aug 4, 2022. It is now read-only.

[REEF-2044] Serializing a List in Tang for C# has missing functionality #1479

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

singlis
Copy link
Contributor

@singlis singlis commented Sep 12, 2018

The current state did not handle serializing out a list when a list was
set in the configuration. This now adds that functionality along with
unit tests.

JIRA: REEF-2044

Closes #

The current state did not handle serializing out a list when a list was
set in the configuration. This now adds that functionality along with
unit tests.

JIRA: [REEF-2044](https://issues.apache.org/jira/browse/REEF-2044)

Closes #
@singlis
Copy link
Contributor Author

singlis commented Sep 19, 2018

@jwang98052 - Hello Julia, would you be able to take a look? I believe this is an area you are familiar with. #WontFix

@singlis
Copy link
Contributor Author

singlis commented Oct 3, 2018

@jwang98052 - have you had a chance to look at these changes? Thanks! #WontFix

.BindList<ListOfStrings2, string>(GenericType<ListOfStrings2>.Class, stringList2)
.Build();

ConfigurationFile.WriteConfigurationFile(conf, "ListOfStringsConf.txt");
Copy link
Contributor

@markusweimer markusweimer Oct 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should be deleted by the end of the tests. Or, better yet, serialize the configuration to a byte[] and back. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed -- I actually convert the config to a string and then read back in the string. So I have deleted this line.


In reply to: 223778711 [](ancestors = 223778711)

@@ -260,6 +260,28 @@ public AvroConfiguration ToAvroConfiguration(IConfiguration c)
l.Add(new ConfigurationEntry(e.Key.GetFullName(), val));
}

IEnumerator bl = conf.GetBoundList().GetEnumerator();
while (bl.MoveNext())
Copy link
Contributor

@markusweimer markusweimer Oct 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not a for each loop? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the expected behavior for an empty list? The code does run - however nothing is written out because there is nothing to serialize. If that is expected, I will modify the test accordingly.


In reply to: 223779318 [](ancestors = 223779318)

Copy link
Contributor

@markusweimer markusweimer Oct 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I believe that is the expected behavior. #Resolved

}
else
{
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Throw(new IllegalStateException(), LOGGER);
Copy link
Contributor

@markusweimer markusweimer Oct 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please don't use the Exceptions class anymore. Instead, just throw the exception. #Resolved

}
else
{
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Throw(new IllegalStateException(), LOGGER);
Copy link
Contributor

@markusweimer markusweimer Oct 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: Replace with throw. Also, add a message to the exception. #Resolved

using Org.Apache.REEF.Tang.Interface;
using Org.Apache.REEF.Tang.Types;
using System.Collections.Generic;

namespace Org.Apache.REEF.Tang.Implementations.Configuration
Copy link
Contributor

@markusweimer markusweimer Oct 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes in this file seem superficial only. Maybe revert? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was sorting the using - but there are no additional changes so I dont think its justified. I will revert.


In reply to: 223779942 [](ancestors = 223779942)

@@ -39,6 +39,7 @@ public interface IConfiguration
ICollection<IClassNode> GetLegacyConstructors();

IEnumerator<KeyValuePair<INamedParameterNode, object>> GetBoundSets();

Copy link
Contributor

@markusweimer markusweimer Oct 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes in this file are superficial only. Please revert. #Resolved

@singlis
Copy link
Contributor Author

singlis commented Oct 19, 2018

Thanks for the feedback @markusweimer. I have posted an update. Although there is one issue that bothers me with lists which is no code seems to serialize a list of classes, it looks to always be a string.

Is serializing out a list of classes that can be injected supposed to work?

I started to make changes to this to see what it would take. First I had to modify this signature:

ICsConfigurationBuilder BindList<U, V, T>(GenericType<U> iface, IList<GenericType<V>> impl)

to

ICsConfigurationBuilder BindList<U, V, T>(GenericType<U> iface, IList<V> impl)

Then I added a test similar to what is done with Sets:

ICsConfigurationBuilder cb = TangFactory.GetTang().NewConfigurationBuilder();
var t = new System.Collections.Generic.List<Integer1>();
t.Add(new Integer1(2));
t.Add(new Integer1(3));
cb.BindList<ListOfClasses, Integer1, INumber>(GenericType<ListOfClasses>.Class, t)

IInjector i = TangFactory.GetTang().NewInjector(cb.Build());
IList<INumber> actual = i.GetInstance<PoolList>().Numbers;

The error is on the last line when we try to call GetInstance to construct the object. It looks like it has to do with resolving the parameters (2,3) to pass to the class on construction. Note PoolList is a class that has an inject constructor. It takes in IList as the parameter, it has a property called Numbers that returns an IList of INumbers:

            [Inject]
            private PoolList([Parameter(typeof(ListOfClasses))] IList<INumber> numbers)

Copy link
Contributor

@markusweimer markusweimer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Will run tests and merge, when they pass.

@markusweimer
Copy link
Contributor

Is serializing out a list of classes that can be injected supposed to work?

I don't think so. Tang enforces singleton semantics for objects to be instantiated by it. And in a List, one can refer to the same type multiple times, without giving it a name as in NamedParameter. Hence, I believe it is expected behavior for this to only ever work with Sets.

Copy link
Contributor

@markusweimer markusweimer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually see relevant test failures:

Test Name:	Org.Apache.REEF.Tang.Tests.Configuration.TestConfiguration.TestListSerializeEmptyStrings
Test FullName:	Org.Apache.REEF.Tang.Tests.Configuration.TestConfiguration.TestListSerializeEmptyStrings
Test Source:	C:\src\reef\lang\cs\Org.Apache.REEF.Tang.Tests\Configuration\TestConfiguration.cs : line 567
Test Outcome:	Failed
Test Duration:	0:00:00.002

Result StackTrace:	
at Org.Apache.REEF.Tang.Implementations.Configuration.CsConfigurationBuilderImpl.Org.Apache.REEF.Tang.Interface.ICsInternalConfigurationBuilder.BindList(Type iface, IList`1 implList) in C:\src\reef\lang\cs\Org.Apache.REEF.Tang\Implementations\Configuration\CsConfigurationBuilderImpl.cs:line 408
   at Org.Apache.REEF.Tang.Implementations.Configuration.CsConfigurationBuilderImpl.BindList[U,T](GenericType`1 iface, IList`1 impl) in C:\src\reef\lang\cs\Org.Apache.REEF.Tang\Implementations\Configuration\CsConfigurationBuilderImpl.cs:line 234
   at Org.Apache.REEF.Tang.Tests.Configuration.TestConfiguration.TestSerializeListHelper(IList`1 items, Int32 expectedLists) in C:\src\reef\lang\cs\Org.Apache.REEF.Tang.Tests\Configuration\TestConfiguration.cs:line 513
   at Org.Apache.REEF.Tang.Tests.Configuration.TestConfiguration.TestListSerializeEmptyStrings() in C:\src\reef\lang\cs\Org.Apache.REEF.Tang.Tests\Configuration\TestConfiguration.cs:line 573
Result Message:	Org.Apache.REEF.Tang.Exceptions.BindException : BindList failed to bind for ListOfStrings, reason: List cannot contain string that are null or empty

and

Test Name:	Org.Apache.REEF.Tang.Tests.Configuration.TestConfiguration.TestListSerializeNullStringValue
Test FullName:	Org.Apache.REEF.Tang.Tests.Configuration.TestConfiguration.TestListSerializeNullStringValue
Test Source:	C:\src\reef\lang\cs\Org.Apache.REEF.Tang.Tests\Configuration\TestConfiguration.cs : line 538
Test Outcome:	Failed
Test Duration:	0:00:00.001

Result StackTrace:	
at Org.Apache.REEF.Tang.Implementations.Configuration.CsConfigurationBuilderImpl.Org.Apache.REEF.Tang.Interface.ICsInternalConfigurationBuilder.BindList(Type iface, IList`1 implList) in C:\src\reef\lang\cs\Org.Apache.REEF.Tang\Implementations\Configuration\CsConfigurationBuilderImpl.cs:line 408
   at Org.Apache.REEF.Tang.Implementations.Configuration.CsConfigurationBuilderImpl.BindList[U,T](GenericType`1 iface, IList`1 impl) in C:\src\reef\lang\cs\Org.Apache.REEF.Tang\Implementations\Configuration\CsConfigurationBuilderImpl.cs:line 234
   at Org.Apache.REEF.Tang.Tests.Configuration.TestConfiguration.TestListSerializeNullStringValue() in C:\src\reef\lang\cs\Org.Apache.REEF.Tang.Tests\Configuration\TestConfiguration.cs:line 546
Result Message:	Org.Apache.REEF.Tang.Exceptions.BindException : BindList failed to bind for ListOfStrings, reason: List cannot contain string that are null or empty

@jwang98052
Copy link
Contributor

Sorry for late response. I will have a quick look today.

public void TestListConfigWithEmptyString()
{
IList<string> stringList1 = new List<string>();
stringList1.Add("");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check the behavior of set for empty string to make the behavior consistent.

public void TestListConfigWithNullStringValue()
{
IList<string> stringList1 = new List<string>();
stringList1.Add(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for null

{
val = GetFullName((string)item);
}
else if (kvp.Value is INode)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can try a test case for List of instances from different subclasses of the same interface, similar to Set to see if this scenario is working.

Copy link
Contributor

@motus motus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very useful functionality! Will post more comments soon.

{
IList<string> stringList1 = new List<string>();
stringList1.Add("foo");
stringList1.Add("bar");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and everywhere: can be simply

var stringList1 = new List<string> { "foo", "bar" };

IConfiguration conf2 = ConfigurationFile.GetConfiguration(s);

ListInjectTest injectTest = (ListInjectTest)TangFactory.GetTang().
NewInjector(conf2).GetInstance(typeof(ListInjectTest));
Copy link
Contributor

@motus motus Jan 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to cast - simply write

var injectTest = TangFactory.GetTang().NewInjector(conf2).GetInstance<ListInjectTest>();

return;
}

Assert.True(false, "Failed to throw expected exception.");
Copy link
Contributor

@motus motus Jan 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and everywhere: the whole test can be just

Assert.Throws<BindException>(() =>
    TangFactory.GetTang().NewConfigurationBuilder()
        .BindList<ListOfStrings, string>(new List<string>() { null })
        .Build());

@@ -448,6 +587,7 @@ public void TestSetConfig()
Assert.True(actual.Contains("six"));
}


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why an extra empty line?

@@ -254,12 +254,32 @@ public AvroConfiguration ToAvroConfiguration(IConfiguration c)
}
else
{
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Throw(new IllegalStateException(), LOGGER);
throw new TangApplicationException("Unable to serialize set of type {e.Value.GetType()}");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to use $"" for interpolation.

}
else
{
throw new TangApplicationException("Unable to serialize list of type {item.GetType()}");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above - use $"". please check for other places as well

{
BindList((INamedParameterNode)n, implList);
}
catch(ArgumentException ex)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add space before ( - and maybe use formatting tool like CodeMaid or similar?

Copy link
Contributor

@motus motus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I am done with the first pass. Left a few minor nit picks

IConfiguration conf = TangFactory.GetTang().NewConfigurationBuilder()
.BindList<ListOfStrings, string>(GenericType<ListOfStrings>.Class, stringList1)
.BindList<ListOfStrings2, string>(GenericType<ListOfStrings2>.Class, stringList2)
.Build();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for the first argument in BindList(): that information is already passed as a generic parameter. can simply write

IConfiguration conf = TangFactory.GetTang().NewConfigurationBuilder()
    .BindList<ListOfStrings, string>(stringList1)
    .BindList<ListOfStrings2, string>(stringList2)
    .Build();

(also, maybe define static ITang Tang = TangFactory.GetTang(); that can be used in all tests?)

return;
}

Assert.True(false, "Failed to throw expected exception.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above: the entire test can be written as

Assert.Throws<BindException>(() =>
    Tang.NewConfigurationBuilder()
        .BindList<ListOfStrings, string>(new List<string>() { "" })
        .Build());

private void TestSerializeListHelper(IList<string> items, int expectedLists = 1)
{
IConfiguration conf = TangFactory.GetTang().NewConfigurationBuilder()
.BindList<ListOfStrings, string>(GenericType<ListOfStrings>.Class, items)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here and everywhere: no need to pass GenericType<ListOfStrings>.Class explicitly

IList<string> stringList = new List<string>();
stringList.Add("foo");
stringList.Add("bar");
TestSerializeListHelper(stringList);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just

TestSerializeListHelper(new List<string> { "foo", "bar" });

[Fact]
public void TestListSerializeNullStringValue()
{
string msg = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

msg is not used anywhere

}
l.Add(new ConfigurationEntry(kvp.Key.GetFullName(), val));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, maybe the whole loop can be expressed in Linq terms? e.g. along the lines of

l.UnionWith(conf.GetBoundList().SelectMany(kvp =>
{
    string key = kvp.Key.GetFullName();
    return kvp.Value.Select(item =>
    {
        if (item is string val) return new ConfigurationEntry(key, val);
        if (item is INode node) return new ConfigurationEntry(key, node.GetFullName());
        throw new TangApplicationException($"Unable to serialize list of type {item.GetType()}");
    });
}));

{
val = GetFullName((string)item);
}
else if (kvp.Value is INode)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't it be item is INode instead of kvp.Value is INode? also, can use if (item is INode node) ... syntax to avoid the cast

public void BindList(INamedParameterNode iface, string impl)
{
IList<object> l;
if (!BoundLists.TryGetValue(iface, out l))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can join these two lines, i.e.

if (!BoundLists.TryGetValue(iface, out IList<object> l))

etc.

l = new List<object>();
BoundLists.Add(iface, l);
}
l.Add((object)impl);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to cast

{
throw new ArgumentException("List cannot contain string that are null or empty");
}

l.Add((object)n);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cast is redundant

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, maybe we can write something like

if (impl.Any(string.IsNullOrEmpty))
{
    throw new ArgumentException("List cannot contain string that are null or empty");
}
BoundLists.Add(iface, impl.ToList<object>());

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants