Skip to content

Commit cf6765a

Browse files
authored
Merge pull request #438 from DataObjects-NET/7.0-exp-types-registration-imp
Tests infrastructure improvements - domain types' registration with cache
2 parents 1fcd7b9 + e4f0788 commit cf6765a

459 files changed

Lines changed: 902 additions & 756 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,151 @@
1-
// Copyright (C) 2018 Xtensive LLC.
1+
// Copyright (C) 2018-2025 Xtensive LLC.
22
// All rights reserved.
33
// For conditions of distribution and use, see license.
44
// Created by: Alexey Kulakov
55
// Created: 2018.08.31
66

7+
using System;
8+
using System.Collections.Concurrent;
9+
using System.Collections.Generic;
710
using System.Configuration;
11+
using System.Linq;
812
using System.Reflection;
13+
using System.Runtime.CompilerServices;
914

1015
namespace Xtensive.Orm.Tests
1116
{
1217
public static class AssemblyExtensions
1318
{
19+
private const string MainTestAsseblyNsPrefix = "Xtensive.Orm.Tests.";
20+
private const string IssuesNsPrefix = "Xtensive.Orm.Tests.Issues.";
21+
private const string UpgradeNsPrefix = "Xtensive.Orm.Tests.Upgrade.";
22+
23+
private static readonly byte[] ThisAssemblyPkt = typeof(AssemblyExtensions).Assembly.GetName().GetPublicKeyToken();
24+
25+
private static readonly ConcurrentDictionary<Assembly, Type[]> TypesPerAssembly = new();
26+
private static readonly ConcurrentDictionary<char, int> MainTestsAssemblyNsAlphabeticIndex = new();
27+
private static readonly ConcurrentDictionary<char, int> MainTestsAssemblyIssuesNsAlphabeticIndex = new();
28+
private static readonly ConcurrentDictionary<char, int> MainTestsAssemblyUpgradeNsAlphabeticIndex = new();
29+
1430
public static System.Configuration.Configuration GetAssemblyConfiguration(this Assembly assembly)
1531
{
1632
return ConfigurationManager.OpenExeConfiguration(assembly.Location);
1733
}
34+
35+
public static IReadOnlyList<Type> GetTypesFromNamespaceCaching(this Assembly assembly, string @namespace)
36+
{
37+
if (string.IsNullOrWhiteSpace(@namespace))
38+
throw new ArgumentException("Namespace cannot be null, empty or contain only white spaces");
39+
40+
// These two dummy mentions are here to not forget to sync filtration algorithm here and in the classes,
41+
// in particular BaseType property, if the property changed result type then this algorighm should be updated as well
42+
_ = nameof(Xtensive.IoC.ServiceTypeRegistrationProcessor.BaseType);
43+
_ = nameof(Xtensive.Orm.Configuration.DomainTypeRegistrationHandler.BaseType);
44+
45+
var assemblyNameInfo = assembly.GetName();
46+
var isMainTestAssembly = assemblyNameInfo.Name == "Xtensive.Orm.Tests"
47+
&& !ThisAssemblyPkt.Except(assemblyNameInfo.GetPublicKeyToken()).Any();
48+
49+
var assemblyTypes = TypesPerAssembly.GetOrAdd(assembly, static (a, isMain) => {
50+
var allTypes = a.GetTypes();
51+
52+
var objectType = typeof(object);
53+
var list = new List<Type>(allTypes.Length);
54+
var currentIndex = 0;
55+
foreach (var t in allTypes) {
56+
if (t.IsSubclassOf(objectType) && t.GetCustomAttribute<CompilerGeneratedAttribute>() == null) {
57+
list.Add(t);
58+
if (isMain) {
59+
var nSpace = t.Namespace;
60+
if (nSpace != null && nSpace.StartsWith(MainTestAsseblyNsPrefix, StringComparison.Ordinal)) {
61+
var firstLetter = nSpace[MainTestAsseblyNsPrefix.Length];
62+
63+
_ = MainTestsAssemblyNsAlphabeticIndex.TryAdd(firstLetter, currentIndex);
64+
if (firstLetter == 'I'/*ssue*/ && nSpace.StartsWith(IssuesNsPrefix, StringComparison.Ordinal)) {
65+
var firstIssuesLetter = nSpace[IssuesNsPrefix.Length];
66+
_ = MainTestsAssemblyIssuesNsAlphabeticIndex.TryAdd(firstIssuesLetter, currentIndex);
67+
}
68+
if (firstLetter == 'U'/*pdate*/ && nSpace.StartsWith(UpgradeNsPrefix, StringComparison.Ordinal)) {
69+
var firstIssuesLetter = nSpace[UpgradeNsPrefix.Length];
70+
_ = MainTestsAssemblyUpgradeNsAlphabeticIndex.TryAdd(firstIssuesLetter, currentIndex);
71+
}
72+
}
73+
}
74+
currentIndex++;
75+
}
76+
}
77+
return list.ToArray();
78+
}, isMainTestAssembly);
79+
80+
return FindSegment(assemblyTypes, @namespace, isMainTestAssembly);
81+
}
82+
83+
private static IReadOnlyList<Type> FindSegment(Type[] types, string ns, bool isMainAssembly)
84+
{
85+
// We rely on the fact that types are sorted by full name.
86+
// That opens an opportunity to optimize search of types from given
87+
// namespace and subnamespaces.
88+
89+
const int windowSize = 6;
90+
91+
var searchFrom = GetSearchStartPosition(ns, isMainAssembly);
92+
93+
var nsWithDot = ns + ".";
94+
var startSearchBoundary = FindFirstEntry(types, searchFrom, nsWithDot);
95+
96+
var endSearchBoundary = startSearchBoundary;
97+
var lastItemIndex = types.Length - 1;
98+
99+
bool wrongNsFound;
100+
do {
101+
endSearchBoundary += windowSize;
102+
if (endSearchBoundary > lastItemIndex)
103+
endSearchBoundary = lastItemIndex;
104+
105+
var tail = types[endSearchBoundary];
106+
wrongNsFound = tail.FullName.IndexOf(nsWithDot, StringComparison.InvariantCulture) < 0;
107+
}
108+
while (!wrongNsFound && endSearchBoundary < lastItemIndex);
109+
110+
for (var tailIndex = endSearchBoundary; tailIndex >= startSearchBoundary; tailIndex--) {
111+
var tail = types[tailIndex];
112+
endSearchBoundary = tailIndex;
113+
if (tail.FullName.IndexOf(nsWithDot, StringComparison.InvariantCulture) >= 0) {
114+
break;
115+
}
116+
}
117+
118+
return new ArraySegment<Type>(types, startSearchBoundary, endSearchBoundary - startSearchBoundary + 1);
119+
}
120+
121+
private static int GetSearchStartPosition(string ns, bool isMainAssembly)
122+
{
123+
if (!isMainAssembly)
124+
return 0;
125+
if (ns.StartsWith(IssuesNsPrefix, StringComparison.Ordinal))
126+
return MainTestsAssemblyIssuesNsAlphabeticIndex[ns[IssuesNsPrefix.Length]];
127+
else if (ns.StartsWith(UpgradeNsPrefix, StringComparison.Ordinal))
128+
return MainTestsAssemblyUpgradeNsAlphabeticIndex[ns[UpgradeNsPrefix.Length]];
129+
else if (ns.StartsWith(MainTestAsseblyNsPrefix, StringComparison.Ordinal))
130+
return MainTestsAssemblyNsAlphabeticIndex [ns[MainTestAsseblyNsPrefix.Length]];
131+
return 0;
132+
}
133+
134+
private static int FindFirstEntry(Type[] types, in int serachFrom, in string nsAndDot)
135+
{
136+
var firstEntryIndex = -1;
137+
138+
for (int headIndex = serachFrom, count = types.Length; headIndex < count; headIndex++) {
139+
var head = types[headIndex];
140+
if (head.FullName.IndexOf(nsAndDot, StringComparison.InvariantCulture) >= 0) {
141+
firstEntryIndex = headIndex;
142+
break;
143+
}
144+
}
145+
146+
if (firstEntryIndex == -1)
147+
throw new Exception($"There is no any entry for given namespace.");
148+
return firstEntryIndex;
149+
}
18150
}
19151
}

Orm/Xtensive.Orm.Tests.Framework/TestHelper.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
// Copyright (C) 2008-2021 Xtensive LLC.
1+
// Copyright (C) 2008-2025 Xtensive LLC.
22
// This code is distributed under MIT license terms.
33
// See the License.txt file in the project root for more information.
44
// Created by: Alex Yakunin
55
// Created: 2008.02.09
66

77
using System;
8+
using System.Reflection;
89
using System.Threading;
910

1011
namespace Xtensive.Orm.Tests
@@ -82,5 +83,18 @@ public static DateTime FixDateTimeForProvider(this DateTime origin, StorageProvi
8283
var newTicks = ticks - (ticks % divider.Value);
8384
return new DateTime(newTicks);
8485
}
86+
87+
/// <summary>
88+
/// Optimized Domain types registration of types from certain namespace.
89+
/// </summary>
90+
/// <param name="registry"><see cref="Configuration.DomainConfiguration.Types"/> registry</param>
91+
/// <param name="assembly">Assembly which types should be registered</param>
92+
/// <param name="namespace">Namespace within <paramref name="assembly"/>types should be registered.</param>
93+
public static void RegisterCaching(this Xtensive.Orm.Configuration.DomainTypeRegistry registry, Assembly assembly, string @namespace)
94+
{
95+
foreach (var t in assembly.GetTypesFromNamespaceCaching(@namespace)) {
96+
registry.Register(t);
97+
}
98+
}
8599
}
86100
}

Orm/Xtensive.Orm.Tests/Interfaces/DoubleIndexedInterfaceTest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2011 Xtensive LLC.
1+
// Copyright (C) 2011 Xtensive LLC.
22
// All rights reserved.
33
// For conditions of distribution and use, see license.
44
// Created by: Denis Krjuchkov
@@ -59,7 +59,7 @@ public class DoubleIndexedInterfaceTest : AutoBuildTest
5959
protected override DomainConfiguration BuildConfiguration()
6060
{
6161
var config = base.BuildConfiguration();
62-
config.Types.Register(typeof (ITest).Assembly, typeof (ITest).Namespace);
62+
config.Types.RegisterCaching(typeof (ITest).Assembly, typeof (ITest).Namespace);
6363
return config;
6464
}
6565

Orm/Xtensive.Orm.Tests/Interfaces/InterfaceImplementationTest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public class InterfaceImplementationTest
7070
public void CompsiteIndexTest()
7171
{
7272
var config = DomainConfigurationFactory.Create();
73-
config.Types.Register(typeof(IHasName).Assembly, typeof(IHasName).Namespace);
73+
config.Types.RegisterCaching(typeof(IHasName).Assembly, typeof(IHasName).Namespace);
7474
var domain = GetClassTableDomain(config);
7575
using (var session = domain.OpenSession())
7676
using (var t = session.OpenTransaction()) {
@@ -99,7 +99,7 @@ public void CombinedTest()
9999
public void InternalTest(Func<DomainConfiguration, Domain> generator)
100100
{
101101
var config = DomainConfigurationFactory.Create();
102-
config.Types.Register(typeof(IHasName).Assembly, typeof(IHasName).Namespace);
102+
config.Types.RegisterCaching(typeof(IHasName).Assembly, typeof(IHasName).Namespace);
103103
var domain = generator(config);
104104
using (var session = domain.OpenSession())
105105
using (var t = session.OpenTransaction()) {

Orm/Xtensive.Orm.Tests/Interfaces/InterfaceTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public class InterfaceTest : AutoBuildTest
109109
protected override DomainConfiguration BuildConfiguration()
110110
{
111111
var config = base.BuildConfiguration();
112-
config.Types.Register(typeof (Person1).Assembly, typeof (Person1).Namespace);
112+
config.Types.RegisterCaching(typeof (Person1).Assembly, typeof (Person1).Namespace);
113113
return config;
114114
}
115115

Orm/Xtensive.Orm.Tests/Interfaces/KeyStructureConflictTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public class KeyStructureConflictTest : AutoBuildTest
5050
protected override DomainConfiguration BuildConfiguration()
5151
{
5252
var config = base.BuildConfiguration();
53-
config.Types.Register(typeof (Root1).Assembly, typeof (Root1).Namespace);
53+
config.Types.RegisterCaching(typeof (Root1).Assembly, typeof (Root1).Namespace);
5454
return config;
5555
}
5656

Orm/Xtensive.Orm.Tests/Interfaces/TypeIdModeConflictTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class TypeIdModeConflictTest : AutoBuildTest
3939
protected override DomainConfiguration BuildConfiguration()
4040
{
4141
var config = base.BuildConfiguration();
42-
config.Types.Register(typeof (Root1).Assembly, typeof (Root1).Namespace);
42+
config.Types.RegisterCaching(typeof (Root1).Assembly, typeof (Root1).Namespace);
4343
return config;
4444
}
4545

Orm/Xtensive.Orm.Tests/Interfaces/UnusedTypeRemovalTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public void MainTest()
6262
protected override DomainConfiguration BuildConfiguration()
6363
{
6464
var config = base.BuildConfiguration();
65-
config.Types.Register(typeof (IFirst).Assembly, typeof (IFirst).Namespace);
65+
config.Types.RegisterCaching(typeof (IFirst).Assembly, typeof (IFirst).Namespace);
6666
return config;
6767
}
6868

Orm/Xtensive.Orm.Tests/Issues/BatchingCommandProcessorFrenzy.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2012 Xtensive LLC.
1+
// Copyright (C) 2012 Xtensive LLC.
22
// All rights reserved.
33
// For conditions of distribution and use, see license.
44
// Created by: Denis Krjuchkov
@@ -27,7 +27,7 @@ public class BatchingCommandProcessorFrenzy : AutoBuildTest
2727
protected override DomainConfiguration BuildConfiguration()
2828
{
2929
var configuration = base.BuildConfiguration();
30-
configuration.Types.Register(typeof (FrenzyEntity).Assembly, typeof (FrenzyEntity).Namespace);
30+
configuration.Types.RegisterCaching(typeof (FrenzyEntity).Assembly, typeof (FrenzyEntity).Namespace);
3131
return configuration;
3232
}
3333

Orm/Xtensive.Orm.Tests/Issues/ClientProfileEntitySetTest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2012 Xtensive LLC.
1+
// Copyright (C) 2012 Xtensive LLC.
22
// All rights reserved.
33
// For conditions of distribution and use, see license.
44
// Created by: Denis Krjuchkov
@@ -48,7 +48,7 @@ public class ClientProfileEntitySetTest : AutoBuildTest
4848
protected override DomainConfiguration BuildConfiguration()
4949
{
5050
var configuration = base.BuildConfiguration();
51-
configuration.Types.Register(typeof (Owner).Assembly, typeof (Owner).Namespace);
51+
configuration.Types.RegisterCaching(typeof (Owner).Assembly, typeof (Owner).Namespace);
5252
configuration.Sessions.Default.Options = SessionOptions.ClientProfile;
5353
return configuration;
5454
}

0 commit comments

Comments
 (0)