* csharp-sdk.sln: chore: 循环引用检测及测试;保存批次算法调整
* Test.cs: * Utils.cs: * AVObject.cs: * AppRouterTest.cs: * ObjectTest.cs: * AVObject.cs: * Common.Test.csproj: * AVObjectTest.cs: * AVException.cs:
parent
043f8e2d88
commit
b85babb38b
|
@ -1181,7 +1181,7 @@ string propertyName
|
|||
// The task produced by taskStart. By running this immediately, we allow everything prior
|
||||
// to toAwait to run before waiting for all of the queues on all of the objects.
|
||||
Task<T> fullTask = taskStart(readyToStart.Task);
|
||||
|
||||
|
||||
// Add fullTask to each of the objects' queues.
|
||||
var childTasks = new List<Task>();
|
||||
foreach (AVObject obj in objects)
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Test {
|
||||
public class LCObject {
|
||||
public string Id {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Dictionary<string, object> Data {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public LCObject(string id) {
|
||||
Data = new Dictionary<string, object>();
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public static Stack<Batch> Batch(IEnumerable<LCObject> objects) {
|
||||
Stack<Batch> batches = new Stack<Batch>();
|
||||
|
||||
IEnumerable<object> deps = objects;
|
||||
do {
|
||||
// 只添加本层依赖的 LCObject
|
||||
IEnumerable<LCObject> avObjects = deps.OfType<LCObject>();
|
||||
if (avObjects.Any()) {
|
||||
batches.Push(new Batch(avObjects));
|
||||
}
|
||||
|
||||
HashSet<object> childSets = new HashSet<object>();
|
||||
foreach (object dep in deps) {
|
||||
IEnumerable children = null;
|
||||
if (dep is IList) {
|
||||
children = dep as IList;
|
||||
} else if (dep is IDictionary) {
|
||||
children = dep as IDictionary;
|
||||
} else if (dep is LCObject) {
|
||||
children = (dep as LCObject).Data.Values;
|
||||
}
|
||||
if (children != null) {
|
||||
foreach (object child in children) {
|
||||
childSets.Add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
deps = childSets;
|
||||
} while (deps != null && deps.Any());
|
||||
|
||||
return batches;
|
||||
}
|
||||
|
||||
public static bool HasCircleReference(object obj, HashSet<LCObject> parents) {
|
||||
if (parents.Contains(obj)) {
|
||||
return true;
|
||||
}
|
||||
IEnumerable deps = null;
|
||||
if (obj is IList) {
|
||||
deps = obj as IList;
|
||||
} else if (obj is IDictionary) {
|
||||
deps = (obj as IDictionary).Values;
|
||||
} else if (obj is LCObject) {
|
||||
deps = (obj as LCObject).Data.Values;
|
||||
}
|
||||
HashSet<LCObject> depParent = new HashSet<LCObject>(parents);
|
||||
if (obj is LCObject) {
|
||||
depParent.Add((LCObject) obj);
|
||||
}
|
||||
if (deps != null) {
|
||||
foreach (object dep in deps) {
|
||||
HashSet<LCObject> set = new HashSet<LCObject>(depParent);
|
||||
if (HasCircleReference(dep, set)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Stack<Batch> Batch() {
|
||||
return Batch(new List<LCObject> { this });
|
||||
}
|
||||
|
||||
public bool HasCircleReference() {
|
||||
return HasCircleReference(this, new HashSet<LCObject>());
|
||||
}
|
||||
}
|
||||
|
||||
public class Batch {
|
||||
HashSet<LCObject> ObjectSet {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Batch() {
|
||||
ObjectSet = new HashSet<LCObject>();
|
||||
}
|
||||
|
||||
public Batch(IEnumerable<LCObject> objects) : this() {
|
||||
foreach (LCObject obj in objects) {
|
||||
ObjectSet.Add(obj);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("----------------------------");
|
||||
foreach (LCObject obj in ObjectSet) {
|
||||
sb.AppendLine(obj.Id);
|
||||
}
|
||||
sb.AppendLine("----------------------------");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public class AVObjectTest {
|
||||
void PrintBatches(Stack<Batch> batches) {
|
||||
while (batches.Any()) {
|
||||
Batch batch = batches.Pop();
|
||||
TestContext.WriteLine(batch);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Simple() {
|
||||
LCObject a = new LCObject("a");
|
||||
LCObject b = new LCObject("b");
|
||||
LCObject c = new LCObject("c");
|
||||
a.Data["child"] = b;
|
||||
b.Data["child"] = c;
|
||||
|
||||
Assert.IsFalse(a.HasCircleReference());
|
||||
|
||||
Stack<Batch> batches = a.Batch();
|
||||
PrintBatches(batches);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Array() {
|
||||
LCObject a = new LCObject("a");
|
||||
LCObject b = new LCObject("b");
|
||||
LCObject c = new LCObject("c");
|
||||
a.Data["children"] = new List<LCObject> { b, c };
|
||||
|
||||
Assert.IsFalse(a.HasCircleReference());
|
||||
|
||||
Stack<Batch> batches = a.Batch();
|
||||
PrintBatches(batches);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SimpleCircleReference() {
|
||||
LCObject a = new LCObject("a");
|
||||
LCObject b = new LCObject("b");
|
||||
a.Data["child"] = b;
|
||||
b.Data["child"] = a;
|
||||
|
||||
Assert.IsTrue(a.HasCircleReference());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ComplexCircleReference() {
|
||||
LCObject a = new LCObject("a");
|
||||
LCObject b = new LCObject("b");
|
||||
LCObject c = new LCObject("c");
|
||||
a.Data["arr"] = new List<object> { 1, b };
|
||||
a.Data["child"] = c;
|
||||
b.Data["arr"] = new List<object> { 2, a };
|
||||
|
||||
Assert.IsTrue(a.HasCircleReference());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ComplexCircleReference2() {
|
||||
LCObject a = new LCObject("a");
|
||||
LCObject b = new LCObject("b");
|
||||
List<object> list = new List<object>();
|
||||
a.Data["list"] = list;
|
||||
b.Data["list"] = list;
|
||||
a.Data["child"] = b;
|
||||
|
||||
Assert.IsFalse(a.HasCircleReference());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,14 +3,21 @@ using System;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Common;
|
||||
|
||||
namespace LeanCloud.Test {
|
||||
public class ObjectTests {
|
||||
[SetUp]
|
||||
public void SetUp() {
|
||||
Logger.LogDelegate += Utils.Print;
|
||||
Utils.InitNorthChina();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown() {
|
||||
Logger.LogDelegate -= Utils.Print;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Save() {
|
||||
AVObject obj = AVObject.Create("Foo");
|
||||
|
@ -20,7 +27,7 @@ namespace LeanCloud.Test {
|
|||
{ "hello", 1 },
|
||||
{ "world", 2 }
|
||||
};
|
||||
await obj.SaveAsync();
|
||||
await obj.Save();
|
||||
Assert.NotNull(obj.ObjectId);
|
||||
Assert.NotNull(obj.CreatedAt);
|
||||
Assert.NotNull(obj.UpdatedAt);
|
||||
|
@ -38,32 +45,57 @@ namespace LeanCloud.Test {
|
|||
TestContext.Out.WriteLine($"balance: {account["balance"]}");
|
||||
}
|
||||
|
||||
//[Test]
|
||||
//public async Task SaveWithPointer() {
|
||||
// AVObject comment = new AVObject("Comment") {
|
||||
// { "content", "Hello, Comment" }
|
||||
// };
|
||||
|
||||
// AVObject post = new AVObject("Post") {
|
||||
// { "name", "New Post" },
|
||||
// { "category", new AVObject("Category") {
|
||||
// { "name", "new post category" }
|
||||
// } }
|
||||
// };
|
||||
// comment["post"] = post;
|
||||
|
||||
// AVObject testPost = new AVObject("Post") {
|
||||
// { "name", "Test Post" },
|
||||
// { "category", new AVObject("Category") {
|
||||
// { "name", "test post category" }
|
||||
// } }
|
||||
// };
|
||||
// comment["test_post"] = testPost;
|
||||
|
||||
// await comment.Save();
|
||||
// TestContext.Out.WriteLine(post);
|
||||
// TestContext.Out.WriteLine(testPost);
|
||||
// TestContext.Out.WriteLine(comment);
|
||||
//}
|
||||
|
||||
[Test]
|
||||
public async Task SaveWithPointer() {
|
||||
AVObject comment = new AVObject("Comment") {
|
||||
{ "content", "Hello, Comment" }
|
||||
};
|
||||
AVObject parent = new AVObject("Parent");
|
||||
AVObject c1 = new AVObject("C1");
|
||||
AVObject c2 = new AVObject("C2");
|
||||
parent["c1"] = c1;
|
||||
parent["c2"] = c2;
|
||||
await parent.Save();
|
||||
}
|
||||
|
||||
AVObject post = new AVObject("Post") {
|
||||
{ "name", "New Post" },
|
||||
{ "category", new AVObject("Category") {
|
||||
{ "name", "new post category" }
|
||||
} }
|
||||
[Test]
|
||||
public async Task SaveWithPointerArray() {
|
||||
AVObject parent = new AVObject("Parent");
|
||||
AVObject c1 = new AVObject("C1");
|
||||
AVObject c2 = new AVObject("C2");
|
||||
parent["iList"] = new List<int> { 1, 1, 2, 3 };
|
||||
parent["cList"] = new List<AVObject> { c1, c2 };
|
||||
parent["cDict"] = new Dictionary<string, AVObject> {
|
||||
{ "c1", c1 },
|
||||
{ "c2", c2 }
|
||||
};
|
||||
comment["post"] = post;
|
||||
await parent.SaveAsync();
|
||||
|
||||
AVObject testPost = new AVObject("Post") {
|
||||
{ "name", "Test Post" },
|
||||
{ "category", new AVObject("Category") {
|
||||
{ "name", "test post category" }
|
||||
} }
|
||||
};
|
||||
comment["test_post"] = testPost;
|
||||
|
||||
await comment.SaveAsync();
|
||||
TestContext.Out.WriteLine(post);
|
||||
TestContext.Out.WriteLine(testPost);
|
||||
TestContext.Out.WriteLine(comment);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -199,5 +231,54 @@ namespace LeanCloud.Test {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SimpleCircleReference() {
|
||||
AVObject a = new AVObject("A");
|
||||
AVObject b = new AVObject("B");
|
||||
a["b"] = b;
|
||||
b["a"] = a;
|
||||
|
||||
Assert.ThrowsAsync<AVException>(async () => await a.Save());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndirectCircleReference() {
|
||||
AVObject a = new AVObject("A");
|
||||
AVObject b = new AVObject("B");
|
||||
AVObject c = new AVObject("C");
|
||||
a["b"] = b;
|
||||
b["c"] = c;
|
||||
c["a"] = a;
|
||||
|
||||
Assert.ThrowsAsync<AVException>(async () => await a.Save());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SimpleCollectionPointerCircleReference() {
|
||||
AVObject a = new AVObject("A");
|
||||
AVObject b = new AVObject("B");
|
||||
a["children"] = new List<object> { 1, b };
|
||||
b["children"] = new Dictionary<string, object> {
|
||||
{ "c", a }
|
||||
};
|
||||
|
||||
Assert.ThrowsAsync<AVException>(async () => await a.Save());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndirectCollectionPointerCircleReference() {
|
||||
AVObject a = new AVObject("A");
|
||||
AVObject b = new AVObject("B");
|
||||
AVObject c = new AVObject("C");
|
||||
|
||||
a["children"] = new List<object> { 1, b };
|
||||
b["children"] = new List<object> { 2, c };
|
||||
c["children"] = new Dictionary<string, object> {
|
||||
{ "c", a }
|
||||
};
|
||||
|
||||
Assert.ThrowsAsync<AVException>(async () => await a.Save());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using LeanCloud;
|
||||
using LeanCloud.Common;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace LeanCloud.Test {
|
||||
|
@ -47,5 +48,22 @@ namespace LeanCloud.Test {
|
|||
AVClient.UseMasterKey = !string.IsNullOrEmpty(masterKey);
|
||||
AVClient.HttpLog(TestContext.Out.WriteLine);
|
||||
}
|
||||
|
||||
internal static void Print(LogLevel level, string info) {
|
||||
switch (level) {
|
||||
case LogLevel.Debug:
|
||||
TestContext.Out.WriteLine($"[DEBUG] {info}");
|
||||
break;
|
||||
case LogLevel.Warn:
|
||||
TestContext.Out.WriteLine($"[WARNING] {info}");
|
||||
break;
|
||||
case LogLevel.Error:
|
||||
TestContext.Out.WriteLine($"[ERROR] {info}");
|
||||
break;
|
||||
default:
|
||||
TestContext.Out.WriteLine(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -264,6 +264,11 @@ namespace LeanCloud
|
|||
/// 按条件更新/删除失败
|
||||
/// </summary>
|
||||
NoEffectOnUpdatingOrDeleting = 305,
|
||||
|
||||
/// <summary>
|
||||
/// 循环引用
|
||||
/// </summary>
|
||||
CircleReference = 400,
|
||||
}
|
||||
|
||||
internal AVException(ErrorCode code, string message, Exception cause = null)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using LeanCloud.Utilities;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
@ -8,19 +9,49 @@ using System.Runtime.CompilerServices;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace LeanCloud {
|
||||
/// <summary>
|
||||
/// AVObject
|
||||
/// </summary>
|
||||
public class AVObject : IEnumerable<KeyValuePair<string, object>>, INotifyPropertyChanged, INotifyPropertyUpdated, INotifyCollectionPropertyUpdated {
|
||||
internal class Batch {
|
||||
internal HashSet<AVObject> Objects {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Batch() {
|
||||
Objects = new HashSet<AVObject>();
|
||||
}
|
||||
|
||||
public Batch(IEnumerable<AVObject> objects) : this() {
|
||||
foreach (AVObject obj in objects) {
|
||||
Objects.Add(obj);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("----------------------------");
|
||||
foreach (AVObject obj in Objects) {
|
||||
sb.AppendLine(obj.ClassName);
|
||||
}
|
||||
sb.AppendLine("----------------------------");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static readonly string AutoClassName = "_Automatic";
|
||||
|
||||
internal readonly object mutex = new object();
|
||||
|
||||
private readonly LinkedList<IDictionary<string, IAVFieldOperation>> operationSetQueue =
|
||||
new LinkedList<IDictionary<string, IAVFieldOperation>>();
|
||||
private readonly IDictionary<string, object> estimatedData = new Dictionary<string, object>();
|
||||
|
||||
private readonly ConcurrentDictionary<string, IAVFieldOperation> operationDict = new ConcurrentDictionary<string, IAVFieldOperation>();
|
||||
private readonly ConcurrentDictionary<string, object> estimatedData = new ConcurrentDictionary<string, object>();
|
||||
|
||||
private static readonly ThreadLocal<bool> isCreatingPointer = new ThreadLocal<bool>(() => false);
|
||||
|
||||
|
@ -210,7 +241,7 @@ namespace LeanCloud {
|
|||
this[GetFieldForPropertyName(ClassName, propertyName)] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <summar、y>
|
||||
/// Gets a relation for a property based upon its associated AVFieldName attribute.
|
||||
/// </summary>
|
||||
/// <returns>The AVRelation for the property.</returns>
|
||||
|
@ -974,9 +1005,9 @@ string propertyName
|
|||
if (oldValue != value) {
|
||||
converdKeys.Add(key);
|
||||
}
|
||||
estimatedData.Remove(key);
|
||||
estimatedData.TryRemove(key, out _);
|
||||
}
|
||||
estimatedData.Add(item);
|
||||
estimatedData.TryAdd(item.Key, item.Value);
|
||||
}
|
||||
changedKeys = converdKeys;
|
||||
foreach (var operations in operationSetQueue) {
|
||||
|
@ -1003,7 +1034,7 @@ string propertyName
|
|||
if (newValue != AVDeleteOperation.DeleteToken) {
|
||||
estimatedData[key] = newValue;
|
||||
} else {
|
||||
estimatedData.Remove(key);
|
||||
estimatedData.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
IAVFieldOperation oldOperation;
|
||||
|
@ -1622,6 +1653,106 @@ string propertyName
|
|||
protected virtual void OnCollectionPropertyUpdated(string propertyName, NotifyCollectionUpdatedAction action, IEnumerable oldValues, IEnumerable newValues) {
|
||||
collectionUpdated?.Invoke(this, new CollectionPropertyUpdatedEventArgs(propertyName, action, oldValues, newValues));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#region refactor
|
||||
|
||||
static bool HasCircleReference(object obj, HashSet<AVObject> parents) {
|
||||
if (parents.Contains(obj)) {
|
||||
return true;
|
||||
}
|
||||
IEnumerable deps = null;
|
||||
if (obj is IList) {
|
||||
deps = obj as IList;
|
||||
} else if (obj is IDictionary) {
|
||||
deps = (obj as IDictionary).Values;
|
||||
} else if (obj is AVObject) {
|
||||
deps = (obj as AVObject).estimatedData.Values;
|
||||
}
|
||||
HashSet<AVObject> depParent = new HashSet<AVObject>(parents);
|
||||
if (obj is AVObject) {
|
||||
depParent.Add(obj as AVObject);
|
||||
}
|
||||
if (deps != null) {
|
||||
foreach (object dep in deps) {
|
||||
HashSet<AVObject> p = new HashSet<AVObject>(depParent);
|
||||
if (HasCircleReference(dep, p)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static Stack<Batch> BatchObjects(IEnumerable<AVObject> avObjects) {
|
||||
Stack<Batch> batches = new Stack<Batch>();
|
||||
|
||||
IEnumerable<object> deps = avObjects;
|
||||
do {
|
||||
// 只添加本层依赖的 LCObject
|
||||
IEnumerable<AVObject> depAVObjs = deps.OfType<AVObject>();
|
||||
if (depAVObjs.Any()) {
|
||||
batches.Push(new Batch(depAVObjs));
|
||||
}
|
||||
|
||||
HashSet<object> childSets = new HashSet<object>();
|
||||
foreach (object dep in deps) {
|
||||
IEnumerable children = null;
|
||||
if (dep is IList) {
|
||||
children = dep as IList;
|
||||
} else if (dep is IDictionary) {
|
||||
children = (dep as IDictionary).Values;
|
||||
} else if (dep is AVObject && (dep as AVObject).ObjectId == null) {
|
||||
// 如果依赖是 AVObject 类型并且还没有保存过,则应该遍历其依赖
|
||||
children = (dep as AVObject).estimatedData.Values;
|
||||
}
|
||||
if (children != null) {
|
||||
foreach (object child in children) {
|
||||
childSets.Add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
deps = childSets;
|
||||
} while (deps != null && deps.Any());
|
||||
|
||||
return batches;
|
||||
}
|
||||
|
||||
public virtual async Task Save() {
|
||||
if (HasCircleReference(this, new HashSet<AVObject>())) {
|
||||
throw new AVException(AVException.ErrorCode.CircleReference, "Found a circle dependency when save");
|
||||
}
|
||||
Stack<Batch> batches = BatchObjects(new List<AVObject> { this });
|
||||
while (batches.Any()) {
|
||||
Batch batch = batches.Pop();
|
||||
IList<AVObject> dirtyObjects = batch.Objects.Where(o => o.IsDirty).ToList();
|
||||
IList<IObjectState> states = (from item in dirtyObjects
|
||||
select item.state).ToList();
|
||||
IList<IDictionary<string, IAVFieldOperation>> operationList = (from item in dirtyObjects
|
||||
select item.StartSave()).ToList();
|
||||
var serverStates = await ObjectController.SaveAllAsync(states, operationList, CancellationToken.None);
|
||||
|
||||
try {
|
||||
foreach (var pair in dirtyObjects.Zip(serverStates, (item, state) => new { item, state })) {
|
||||
pair.item.HandleSave(pair.state);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
foreach (var pair in dirtyObjects.Zip(operationList, (item, ops) => new { item, ops })) {
|
||||
pair.item.HandleFailedSave(pair.ops);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public interface INotifyPropertyUpdated {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using LeanCloud.Common;
|
||||
|
||||
namespace Common.Test {
|
||||
public class Tests {
|
||||
public class AppRouterTest {
|
||||
static void Print(LogLevel level, string info) {
|
||||
switch (level) {
|
||||
case LogLevel.Debug:
|
||||
|
@ -23,23 +24,43 @@ namespace Common.Test {
|
|||
|
||||
[SetUp]
|
||||
public void SetUp() {
|
||||
TestContext.Out.WriteLine("Set up");
|
||||
Logger.LogDelegate += Print;
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown() {
|
||||
TestContext.Out.WriteLine("Tear down");
|
||||
Logger.LogDelegate -= Print;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AppRouter() {
|
||||
var appRouter = new AppRouterController();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
var state = await appRouter.Get("BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz");
|
||||
TestContext.Out.WriteLine(state.ApiServer);
|
||||
}
|
||||
public void ChineseApp() {
|
||||
Exception e = Assert.Catch(() => {
|
||||
string appId = "BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz";
|
||||
AppRouterController appRouter = new AppRouterController(appId, null);
|
||||
TestContext.WriteLine("init done");
|
||||
});
|
||||
TestContext.WriteLine(e.Message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ChineseAppWithDomain() {
|
||||
string appId = "BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz";
|
||||
string server = "https://bmyv4rks.lc-cn-n1-shared.com";
|
||||
AppRouterController appRouterController = new AppRouterController(appId, server);
|
||||
AppRouter appRouterState = await appRouterController.Get();
|
||||
Assert.AreEqual(appRouterState.ApiServer, server);
|
||||
Assert.AreEqual(appRouterState.EngineServer, server);
|
||||
Assert.AreEqual(appRouterState.PushServer, server);
|
||||
Assert.AreEqual(appRouterState.RTMServer, server);
|
||||
Assert.AreEqual(appRouterState.StatsServer, server);
|
||||
Assert.AreEqual(appRouterState.PlayServer, server);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void InternationalApp() {
|
||||
string appId = "BMYV4RKSTwo8WSqt8q9ezcWF-MdYXbMMI";
|
||||
_ = new AppRouterController(appId, null);
|
||||
TestContext.WriteLine("International app init done");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,7 +1,73 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Common.Test {
|
||||
public class Test {
|
||||
public Test() {
|
||||
[Test]
|
||||
public async Task AsyncFor() {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
await Task.Delay(1000);
|
||||
TestContext.WriteLine($"{i} done at {DateTimeOffset.UtcNow}");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConcurrentCollection() {
|
||||
List<int> list = new List<int>();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
Task.Run(() => {
|
||||
list.Add(i);
|
||||
});
|
||||
}
|
||||
TestContext.WriteLine($"{list.Count}");
|
||||
|
||||
ConcurrentQueue<int> queue = new ConcurrentQueue<int>();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
Task.Run(() => {
|
||||
queue.Enqueue(i);
|
||||
});
|
||||
}
|
||||
TestContext.WriteLine($"{queue.Count}");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ObjectType() {
|
||||
List<object> list = new List<object> { 1, "hello", 2, "world" };
|
||||
TestContext.WriteLine(list is IList);
|
||||
object[] objs = { 1, "hi", 3 };
|
||||
TestContext.WriteLine(objs is IList);
|
||||
List<object> subList = list.OfType<string>().ToList<object>();
|
||||
foreach (object obj in subList) {
|
||||
TestContext.WriteLine(obj);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CollectionExcept() {
|
||||
List<int> list1 = new List<int> { 1, 2, 3, 4, 5 };
|
||||
List<int> list2 = new List<int> { 4, 5, 6 };
|
||||
IEnumerable<int> deltaList = list1.Except(list2).ToList();
|
||||
foreach (int delta in deltaList) {
|
||||
TestContext.WriteLine(delta);
|
||||
}
|
||||
|
||||
Dictionary<string, object> dict1 = new Dictionary<string, object> {
|
||||
{ "a", 1 },
|
||||
{ "b", 2 }
|
||||
};
|
||||
Dictionary<string, object> dict2 = new Dictionary<string, object> {
|
||||
{ "b", 2 },
|
||||
{ "c", 3 }
|
||||
};
|
||||
IEnumerable<KeyValuePair<string, object>> deltaDict = dict1.Except(dict2);
|
||||
foreach (KeyValuePair<string, object> delta in deltaDict) {
|
||||
TestContext.WriteLine($"{delta.Key} : {delta.Value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,12 +31,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage", "Storage\Storage\
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RTM", "RTM\RTM\RTM.csproj", "{D4A30F70-AAED-415D-B940-023B3D7241EE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{14EC150A-EF90-4E0B-B6D7-C2CF1945F6E5}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{C827DA2F-6AB4-48D8-AB5B-6DAB925F8933}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common.Test", "Test\Common.Test\Common.Test.csproj", "{4DF4E0F4-1013-477F-ADA6-BFAFD9312335}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{758DE75D-37D7-4392-B564-9484348B505C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -87,14 +87,14 @@ Global
|
|||
{D4A30F70-AAED-415D-B940-023B3D7241EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D4A30F70-AAED-415D-B940-023B3D7241EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D4A30F70-AAED-415D-B940-023B3D7241EE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{14EC150A-EF90-4E0B-B6D7-C2CF1945F6E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{14EC150A-EF90-4E0B-B6D7-C2CF1945F6E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{14EC150A-EF90-4E0B-B6D7-C2CF1945F6E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{14EC150A-EF90-4E0B-B6D7-C2CF1945F6E5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4DF4E0F4-1013-477F-ADA6-BFAFD9312335}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4DF4E0F4-1013-477F-ADA6-BFAFD9312335}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4DF4E0F4-1013-477F-ADA6-BFAFD9312335}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4DF4E0F4-1013-477F-ADA6-BFAFD9312335}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{758DE75D-37D7-4392-B564-9484348B505C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{758DE75D-37D7-4392-B564-9484348B505C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{758DE75D-37D7-4392-B564-9484348B505C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{758DE75D-37D7-4392-B564-9484348B505C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{659D19F0-9A40-42C0-886C-555E64F16848} = {CD6B6669-1A56-437A-932E-BCE7F5D4CD18}
|
||||
|
@ -105,9 +105,7 @@ Global
|
|||
{EA1C601E-D853-41F7-B9EB-276CBF7D1FA5} = {5B895B7A-1F6E-40A5-8081-43B334D2C076}
|
||||
{3251B4D8-D11A-4D90-8626-27FEE266B066} = {5B895B7A-1F6E-40A5-8081-43B334D2C076}
|
||||
{F907012C-74DF-4575-AFE6-E8DAACC26D24} = {5B895B7A-1F6E-40A5-8081-43B334D2C076}
|
||||
{BE05B492-78CD-47CA-9F48-C3E9B4813AFF} = {CD6B6669-1A56-437A-932E-BCE7F5D4CD18}
|
||||
{59DA32A0-4CD3-424A-8584-D08B8D1E2B98} = {CD6B6669-1A56-437A-932E-BCE7F5D4CD18}
|
||||
{D4A30F70-AAED-415D-B940-023B3D7241EE} = {64D8F9A1-BA44-459C-817C-788B4EBC0B9F}
|
||||
{BE05B492-78CD-47CA-9F48-C3E9B4813AFF} = {C827DA2F-6AB4-48D8-AB5B-6DAB925F8933}
|
||||
{4DF4E0F4-1013-477F-ADA6-BFAFD9312335} = {C827DA2F-6AB4-48D8-AB5B-6DAB925F8933}
|
||||
EndGlobalSection
|
||||
GlobalSection(MonoDevelopProperties) = preSolution
|
||||
|
|
Loading…
Reference in New Issue