From 0cbf3a2c47957db1ad50ec2ac85fd0dcc19840ac Mon Sep 17 00:00:00 2001 From: Steven He Date: Fri, 6 Dec 2024 23:35:37 +0900 Subject: [PATCH 01/70] Try fixing remorph issue --- src/coreclr/jit/compiler.h | 2 +- src/coreclr/jit/gentree.cpp | 29 +++++++++++++++++++++++------ src/coreclr/jit/morph.cpp | 9 ++++++++- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 75381c2b22171f..c4cce85e431ded 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -3747,7 +3747,7 @@ class Compiler //------------------------------------------------------------------------- - GenTree* gtFoldExpr(GenTree* tree); + GenTree* gtFoldExpr(GenTree* tree, bool* folded = nullptr); GenTree* gtFoldExprConst(GenTree* tree); GenTree* gtFoldIndirConst(GenTreeIndir* indir); GenTree* gtFoldExprSpecial(GenTree* tree); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 2a3762aa3898c5..09a94a21b1d5b1 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -13572,10 +13572,13 @@ void Compiler::gtDispLIRNode(GenTree* node, const char* prefixMsg /* = nullptr * * and call the methods to perform the folding */ -GenTree* Compiler::gtFoldExpr(GenTree* tree) +GenTree* Compiler::gtFoldExpr(GenTree* tree, bool* folded) { unsigned kind = tree->OperKind(); + if (folded != nullptr) + *folded = false; + /* We must have a simple operation to fold */ // If we're in CSE, it's not safe to perform tree @@ -13595,13 +13598,19 @@ GenTree* Compiler::gtFoldExpr(GenTree* tree) { if (tree->OperIsConditional()) { - return gtFoldExprConditional(tree); + GenTree* newTree = gtFoldExprConditional(tree); + if (folded != nullptr) + *folded = newTree != tree; + return newTree; } #if defined(FEATURE_HW_INTRINSICS) if (tree->OperIsHWIntrinsic()) { - return gtFoldExprHWIntrinsic(tree->AsHWIntrinsic()); + GenTree* newTree = gtFoldExprHWIntrinsic(tree->AsHWIntrinsic()); + if (folded != nullptr) + *folded = newTree != tree; + return newTree; } #endif // FEATURE_HW_INTRINSICS @@ -13629,6 +13638,7 @@ GenTree* Compiler::gtFoldExpr(GenTree* tree) { if (op1->OperIsConst()) { + // constants folding results in a new tree that may be folded again, don't mark it as folded return gtFoldExprConst(tree); } } @@ -13640,7 +13650,8 @@ GenTree* Compiler::gtFoldExpr(GenTree* tree) // one of their arguments is an address. if (op1->OperIsConst() && op2->OperIsConst() && !tree->OperIsAtomicOp()) { - /* both nodes are constants - fold the expression */ + // both nodes are constants - fold the expression + // constants folding results in a new tree that may be folded again, don't mark it as folded return gtFoldExprConst(tree); } else if (op1->OperIsConst() || op2->OperIsConst()) @@ -13655,13 +13666,19 @@ GenTree* Compiler::gtFoldExpr(GenTree* tree) return tree; } - return gtFoldExprSpecial(tree); + GenTree* newTree = gtFoldExprSpecial(tree); + if (folded != nullptr) + *folded = newTree != tree; + return newTree; } else if (tree->OperIsCompare()) { /* comparisons of two local variables can sometimes be folded */ - return gtFoldExprCompare(tree); + GenTree* newTree = gtFoldExprCompare(tree); + if (folded != nullptr) + *folded = newTree != tree; + return newTree; } } diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index c0b7a6417b2980..b0ac4a65d81367 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -8305,10 +8305,17 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA } // Try to fold it, maybe we get lucky, - tree = gtFoldExpr(tree); + bool folded; + tree = gtFoldExpr(tree, &folded); if (oldTree != tree) { + if (folded) + { + INDEBUG(tree->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED); + return tree; + } + /* if gtFoldExpr returned op1 or op2 then we are done */ if ((tree == op1) || (tree == op2) || (tree == qmarkOp1) || (tree == qmarkOp2)) { From 8e63d8db6bdca2ed46799e709f017c6c1780daf5 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 9 Dec 2024 01:24:21 -0500 Subject: [PATCH 02/70] Delete unused static field from List (#110515) --- .../src/System/Collections/Generic/List.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs index 119f06ac9fecf5..2cc980619cf953 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs @@ -1184,8 +1184,6 @@ public bool TrueForAll(Predicate match) public struct Enumerator : IEnumerator, IEnumerator { - internal static IEnumerator? s_emptyEnumerator; - private readonly List _list; private int _index; private readonly int _version; From f7fad34e43db22000b06711d492dc6a9ae0b274a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 9 Dec 2024 08:46:46 +0100 Subject: [PATCH 03/70] Remove some RuntimeExport/RuntimeImport indirections (#110437) We had indirections set up where we'd `RuntimeExport` a method under a symbolic name and then `RuntimeImport` it elsewhere (or call the export from JIT-generated code). Most of this was motivated by the old Redhawk layering where things like casting and interface dispatch were part of Runtime.Base and not part of CoreLib. Since we decided if we ever reintroduce a Runtime.Base, we wouldn't have casting in it, this indirection will no longer be needed. Motivated by #110267 (and also the reason why I'm only doing it for `RuntimeExport`s used from the JIT right now). Once that PR gets in, calling methods through `RuntimeExport`s would actually become a bigger deoptimization than it is now. --- .../System/Runtime/CompilerServices/Unsafe.cs | 15 ++++++++ .../src/System/Runtime/RuntimeExports.cs | 4 --- .../src/System/Runtime/TypeCast.cs | 11 ------ .../Runtime/Augments/RuntimeAugments.cs | 10 +++--- .../src/System/Array.NativeAot.cs | 10 +++--- .../src/System/InvokeUtils.cs | 2 +- .../System/Reflection/DynamicInvokeInfo.cs | 8 ++--- .../RuntimeHelpers.NativeAot.cs | 2 +- .../src/System/Runtime/RuntimeImports.cs | 8 ----- .../src/System/RuntimeType.cs | 2 +- .../src/System/ValueType.cs | 2 +- .../Test.CoreLib/src/System/Buffer.cs | 15 ++++++++ .../Test.CoreLib/src/System/SpanHelpers.cs | 34 ++++++++++++++++++ .../Test.CoreLib/src/Test.CoreLib.csproj | 2 ++ .../Common/TypeSystem/IL/HelperExtensions.cs | 6 ++++ .../ILCompiler.Compiler/Compiler/ILScanner.cs | 13 +++++++ .../ILCompiler.Compiler/Compiler/JitHelper.cs | 36 +++++++++---------- .../IL/ILImporter.Scanner.cs | 10 +++++- .../src/System/Buffer.cs | 3 -- .../src/System/SpanHelpers.ByteMemOps.cs | 9 ----- 20 files changed, 130 insertions(+), 72 deletions(-) create mode 100644 src/coreclr/nativeaot/Test.CoreLib/src/System/Buffer.cs create mode 100644 src/coreclr/nativeaot/Test.CoreLib/src/System/SpanHelpers.cs diff --git a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CompilerServices/Unsafe.cs b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CompilerServices/Unsafe.cs index 89eedc1638a723..7f95b9bd401fca 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CompilerServices/Unsafe.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CompilerServices/Unsafe.cs @@ -16,6 +16,21 @@ namespace System.Runtime.CompilerServices /// public static unsafe class Unsafe { + /// + /// Determines the byte offset from origin to target from the given references. + /// + [Intrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IntPtr ByteOffset(ref readonly T origin, ref readonly T target) + { + throw new PlatformNotSupportedException(); + + // ldarg .1 + // ldarg .0 + // sub + // ret + } + /// /// Returns a pointer to the given by-ref parameter. /// diff --git a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs index b7a1863d3727db..ae2b81bfa418d7 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs @@ -73,7 +73,6 @@ public static unsafe object RhNewArray(MethodTable* pEEType, int length) } } - [RuntimeExport("RhBox")] public static unsafe object RhBox(MethodTable* pEEType, ref byte data) { // A null can be passed for boxing of a null ref. @@ -207,7 +206,6 @@ public static unsafe void RhUnboxAny(object? o, ref byte data, MethodTable* pUnb // // Unbox helpers with RyuJIT conventions // - [RuntimeExport("RhUnbox2")] public static unsafe ref byte RhUnbox2(MethodTable* pUnboxToEEType, object obj) { if ((obj == null) || !UnboxAnyTypeCompare(obj.GetMethodTable(), pUnboxToEEType)) @@ -218,7 +216,6 @@ public static unsafe ref byte RhUnbox2(MethodTable* pUnboxToEEType, object obj) return ref obj.GetRawData(); } - [RuntimeExport("RhUnboxNullable")] public static unsafe void RhUnboxNullable(ref byte data, MethodTable* pUnboxToEEType, object obj) { if (obj != null && obj.GetMethodTable() != pUnboxToEEType->NullableType) @@ -228,7 +225,6 @@ public static unsafe void RhUnboxNullable(ref byte data, MethodTable* pUnboxToEE RhUnbox(obj, ref data, pUnboxToEEType); } - [RuntimeExport("RhUnboxTypeTest")] public static unsafe void RhUnboxTypeTest(MethodTable* pType, MethodTable* pBoxType) { Debug.Assert(pType->IsValueType); diff --git a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs index 20427987539c39..8425f31f16cc15 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs @@ -63,7 +63,6 @@ internal enum AssignmentVariation // IsInstanceOf test used for unusual cases (naked type parameters, variant generic types) // Unlike the IsInstanceOfInterface and IsInstanceOfClass functions, // this test must deal with all kinds of type tests - [RuntimeExport("RhTypeCast_IsInstanceOfAny")] public static unsafe object? IsInstanceOfAny(MethodTable* pTargetType, object? obj) { if (obj != null) @@ -94,7 +93,6 @@ internal enum AssignmentVariation return IsInstanceOfAny_NoCacheLookup(pTargetType, obj); } - [RuntimeExport("RhTypeCast_IsInstanceOfInterface")] public static unsafe object? IsInstanceOfInterface(MethodTable* pTargetType, object? obj) { Debug.Assert(pTargetType->IsInterface); @@ -184,7 +182,6 @@ internal enum AssignmentVariation return obj; } - [RuntimeExport("RhTypeCast_IsInstanceOfClass")] public static unsafe object? IsInstanceOfClass(MethodTable* pTargetType, object? obj) { Debug.Assert(!pTargetType->IsParameterizedType, "IsInstanceOfClass called with parameterized MethodTable"); @@ -248,7 +245,6 @@ internal enum AssignmentVariation return obj; } - [RuntimeExport("RhTypeCast_IsInstanceOfException")] public static unsafe bool IsInstanceOfException(MethodTable* pTargetType, object? obj) { // Based on IsInstanceOfClass @@ -279,7 +275,6 @@ public static unsafe bool IsInstanceOfException(MethodTable* pTargetType, object // ChkCast test used for unusual cases (naked type parameters, variant generic types) // Unlike the ChkCastInterface and ChkCastClass functions, // this test must deal with all kinds of type tests - [RuntimeExport("RhTypeCast_CheckCastAny")] public static unsafe object CheckCastAny(MethodTable* pTargetType, object obj) { CastResult result; @@ -307,7 +302,6 @@ public static unsafe object CheckCastAny(MethodTable* pTargetType, object obj) return objRet; } - [RuntimeExport("RhTypeCast_CheckCastInterface")] public static unsafe object CheckCastInterface(MethodTable* pTargetType, object obj) { Debug.Assert(pTargetType->IsInterface); @@ -393,7 +387,6 @@ private static unsafe object CheckCastInterface_Helper(MethodTable* pTargetType, return ThrowInvalidCastException(pTargetType); } - [RuntimeExport("RhTypeCast_CheckCastClass")] public static unsafe object CheckCastClass(MethodTable* pTargetType, object obj) { Debug.Assert(!pTargetType->IsParameterizedType, "CheckCastClass called with parameterized MethodTable"); @@ -411,7 +404,6 @@ public static unsafe object CheckCastClass(MethodTable* pTargetType, object obj) // Optimized helper for classes. Assumes that the trivial cases // has been taken care of by the inlined check - [RuntimeExport("RhTypeCast_CheckCastClassSpecial")] private static unsafe object CheckCastClassSpecial(MethodTable* pTargetType, object obj) { Debug.Assert(!pTargetType->IsParameterizedType, "CheckCastClassSpecial called with parameterized MethodTable"); @@ -761,8 +753,6 @@ private static unsafe void ThrowArrayMismatchException(object?[] array) // // Array stelem/ldelema helpers with RyuJIT conventions // - - [RuntimeExport("RhpLdelemaRef")] public static unsafe ref object? LdelemaRef(object?[] array, nint index, MethodTable* elementType) { Debug.Assert(array is null || array.GetMethodTable()->IsArray, "first argument must be an array"); @@ -794,7 +784,6 @@ private static unsafe void ThrowArrayMismatchException(object?[] array) return ref element; } - [RuntimeExport("RhpStelemRef")] public static unsafe void StelemRef(object?[] array, nint index, object? obj) { // This is supported only on arrays diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs index 363a544f8c74d0..6fb23a261f3337 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs @@ -216,7 +216,7 @@ public static unsafe void StoreValueTypeField(IntPtr address, object fieldValue, public static unsafe object LoadValueTypeField(IntPtr address, RuntimeTypeHandle fieldType) { - return RuntimeImports.RhBox(fieldType.ToMethodTable(), ref *(byte*)address); + return RuntimeExports.RhBox(fieldType.ToMethodTable(), ref *(byte*)address); } public static unsafe object LoadPointerTypeField(IntPtr address, RuntimeTypeHandle fieldType) @@ -236,7 +236,7 @@ public static unsafe void StoreValueTypeField(object obj, int fieldOffset, objec public static unsafe object LoadValueTypeField(object obj, int fieldOffset, RuntimeTypeHandle fieldType) { ref byte address = ref Unsafe.AddByteOffset(ref obj.GetRawData(), new IntPtr(fieldOffset - ObjectHeaderSize)); - return RuntimeImports.RhBox(fieldType.ToMethodTable(), ref address); + return RuntimeExports.RhBox(fieldType.ToMethodTable(), ref address); } public static unsafe object LoadPointerTypeField(object obj, int fieldOffset, RuntimeTypeHandle fieldType) @@ -244,7 +244,7 @@ public static unsafe object LoadPointerTypeField(object obj, int fieldOffset, Ru ref byte address = ref Unsafe.AddByteOffset(ref obj.GetRawData(), new IntPtr(fieldOffset - ObjectHeaderSize)); if (fieldType.ToMethodTable()->IsFunctionPointer) - return RuntimeImports.RhBox(MethodTable.Of(), ref address); + return RuntimeExports.RhBox(MethodTable.Of(), ref address); return ReflectionPointer.Box((void*)Unsafe.As(ref address), Type.GetTypeFromHandle(fieldType)); } @@ -285,7 +285,7 @@ public static object LoadValueTypeFieldValueFromValueType(TypedReference typedRe Debug.Assert(TypedReference.TargetTypeToken(typedReference).ToMethodTable()->IsValueType); Debug.Assert(fieldTypeHandle.ToMethodTable()->IsValueType); - return RuntimeImports.RhBox(fieldTypeHandle.ToMethodTable(), ref Unsafe.Add(ref typedReference.Value, fieldOffset)); + return RuntimeExports.RhBox(fieldTypeHandle.ToMethodTable(), ref Unsafe.Add(ref typedReference.Value, fieldOffset)); } [CLSCompliant(false)] @@ -391,7 +391,7 @@ public static bool IsInterface(RuntimeTypeHandle type) public static unsafe object Box(RuntimeTypeHandle type, IntPtr address) { - return RuntimeImports.RhBox(type.ToMethodTable(), ref *(byte*)address); + return RuntimeExports.RhBox(type.ToMethodTable(), ref *(byte*)address); } //============================================================================================== diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs index dc30aaecd1f06f..09439509355e88 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs @@ -330,7 +330,7 @@ private static unsafe void CopyImplGcRefArray(Array sourceArray, int sourceIndex for (int i = 0; i < length; i++) { object? value = Unsafe.Add(ref refSourceArray, sourceIndex - i); - if (mustCastCheckEachElement && value != null && RuntimeImports.IsInstanceOf(destinationElementEEType, value) == null) + if (mustCastCheckEachElement && value != null && TypeCast.IsInstanceOfAny(destinationElementEEType, value) == null) throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); Unsafe.Add(ref refDestinationArray, destinationIndex - i) = value; } @@ -340,7 +340,7 @@ private static unsafe void CopyImplGcRefArray(Array sourceArray, int sourceIndex for (int i = 0; i < length; i++) { object? value = Unsafe.Add(ref refSourceArray, sourceIndex + i); - if (mustCastCheckEachElement && value != null && RuntimeImports.IsInstanceOf(destinationElementEEType, value) == null) + if (mustCastCheckEachElement && value != null && TypeCast.IsInstanceOfAny(destinationElementEEType, value) == null) throw new InvalidCastException(SR.InvalidCast_DownCastArrayElement); Unsafe.Add(ref refDestinationArray, destinationIndex + i) = value; } @@ -370,7 +370,7 @@ private static unsafe void CopyImplValueTypeArrayToReferenceArray(Array sourceAr ref object refDestinationArray = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(destinationArray)); for (int i = 0; i < length; i++) { - object boxedValue = RuntimeImports.RhBox(sourceElementEEType, ref *pElement); + object boxedValue = RuntimeExports.RhBox(sourceElementEEType, ref *pElement); Unsafe.Add(ref refDestinationArray, destinationIndex + i) = boxedValue; pElement += sourceElementSize; } @@ -457,7 +457,7 @@ private static unsafe void CopyImplValueTypeArrayWithInnerGcRefs(Array sourceArr pDestinationElement -= cbElementSize; } - object boxedValue = RuntimeImports.RhBox(sourceElementEEType, ref *pSourceElement); + object boxedValue = RuntimeExports.RhBox(sourceElementEEType, ref *pSourceElement); if (boxedElements != null) boxedElements[i] = boxedValue; else @@ -768,7 +768,7 @@ internal unsafe nint GetFlattenedIndex(ReadOnlySpan indices) MethodTable* pElementEEType = ElementMethodTable; if (pElementEEType->IsValueType) { - return RuntimeImports.RhBox(pElementEEType, ref element); + return RuntimeExports.RhBox(pElementEEType, ref element); } else { diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/InvokeUtils.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/InvokeUtils.cs index b6fc499829d173..61a2d5367c7cb3 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/InvokeUtils.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/InvokeUtils.cs @@ -133,7 +133,7 @@ private static Exception ConvertOrWidenPrimitivesEnumsAndPointersIfPossible(obje if (dstElementType == srcElementType) { // Rebox the value if the EETypeElementTypes match - dstObject = RuntimeImports.RhBox(dstEEType, ref srcObject.GetRawData()); + dstObject = RuntimeExports.RhBox(dstEEType, ref srcObject.GetRawData()); } else { diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs index 45bc2f9e19978a..d7837bdc649e18 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs @@ -783,7 +783,7 @@ private unsafe void CopyBackToArray(ref object? src, object?[] dest) } else { - obj = RuntimeImports.RhBox( + obj = RuntimeExports.RhBox( (transform & Transform.FunctionPointer) != 0 ? MethodTable.Of() : argumentInfo.Type, ref obj.GetRawData()); } @@ -818,7 +818,7 @@ private unsafe void CopyBackToSpan(Span src, Span dest) } else { - obj = RuntimeImports.RhBox( + obj = RuntimeExports.RhBox( (transform & Transform.FunctionPointer) != 0 ? MethodTable.Of() : argumentInfo.Type, ref obj.GetRawData()); } @@ -849,7 +849,7 @@ private unsafe object ReturnTransform(ref byte byref, bool wrapInTargetInvocatio else if ((_returnTransform & Transform.FunctionPointer) != 0) { Debug.Assert(Type.GetTypeFromMethodTable(_returnType).IsFunctionPointer); - obj = RuntimeImports.RhBox(MethodTable.Of(), ref byref); + obj = RuntimeExports.RhBox(MethodTable.Of(), ref byref); } else if ((_returnTransform & Transform.Reference) != 0) { @@ -859,7 +859,7 @@ private unsafe object ReturnTransform(ref byte byref, bool wrapInTargetInvocatio else { Debug.Assert((_returnTransform & (Transform.ByRef | Transform.Nullable)) != 0); - obj = RuntimeImports.RhBox(_returnType, ref byref); + obj = RuntimeExports.RhBox(_returnType, ref byref); } return obj; } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs index 8488a99f12a26f..b30bfd88ef0822 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs @@ -380,7 +380,7 @@ public static unsafe object GetUninitializedObject( if (mt->IsByRefLike) throw new NotSupportedException(SR.NotSupported_ByRefLike); - return RuntimeImports.RhBox(mt, ref target); + return RuntimeExports.RhBox(mt, ref target); } /// diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs index 15587b47467210..454c5a1e0c4be0 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs @@ -379,10 +379,6 @@ internal static IntPtr RhHandleAllocDependent(object primary, object secondary) [RuntimeImport(RuntimeLibrary, "RhTypeCast_CheckArrayStore")] internal static extern void RhCheckArrayStore(object array, object? obj); - [MethodImpl(MethodImplOptions.InternalCall)] - [RuntimeImport(RuntimeLibrary, "RhTypeCast_IsInstanceOfAny")] - internal static extern unsafe object IsInstanceOf(MethodTable* pTargetType, object obj); - // // calls to runtime for allocation // These calls are needed in types which cannot use "new" to allocate and need to do it manually @@ -405,10 +401,6 @@ internal static IntPtr RhHandleAllocDependent(object primary, object secondary) [RuntimeImport(RuntimeLibrary, "RhNewString")] internal static extern unsafe string RhNewString(MethodTable* pEEType, int length); - [MethodImpl(MethodImplOptions.InternalCall)] - [RuntimeImport(RuntimeLibrary, "RhBox")] - internal static extern unsafe object RhBox(MethodTable* pEEType, ref byte data); - [MethodImpl(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhUnbox")] internal static extern unsafe void RhUnbox(object? obj, ref byte data, MethodTable* pUnboxToEEType); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeType.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeType.cs index 1ccb983b64bc32..1854e6ddeb210d 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeType.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeType.cs @@ -630,7 +630,7 @@ public override bool IsInstanceOfType([NotNullWhen(true)] object? o) return false; if (pEEType->IsNullable) pEEType = pEEType->NullableType; - return RuntimeImports.IsInstanceOf(pEEType, o) != null; + return TypeCast.IsInstanceOfAny(pEEType, o) != null; } // diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/ValueType.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/ValueType.cs index 968e97c425cf81..c19988c9bd6782 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/ValueType.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/ValueType.cs @@ -146,7 +146,7 @@ private unsafe void RegularGetValueTypeHashCode(ref HashCode hashCode, ref byte // of __GetFieldHelper, decodes the unboxing stub pointed to by the slot to the real target // (we already have that part), and calls the entrypoint that expects a byref `this`, and use the // data to decide between calling fast or regular hashcode helper. - var fieldValue = (ValueType)RuntimeImports.RhBox(fieldType, ref fieldData); + var fieldValue = (ValueType)RuntimeExports.RhBox(fieldType, ref fieldData); if (fieldValue != null) { hashCode.Add(fieldValue); diff --git a/src/coreclr/nativeaot/Test.CoreLib/src/System/Buffer.cs b/src/coreclr/nativeaot/Test.CoreLib/src/System/Buffer.cs new file mode 100644 index 00000000000000..9549be1ae49c7b --- /dev/null +++ b/src/coreclr/nativeaot/Test.CoreLib/src/System/Buffer.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime; +using System.Runtime.CompilerServices; + +namespace System +{ + public static partial class Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void BulkMoveWithWriteBarrier(ref byte destination, ref byte source, nuint byteCount) => + RuntimeImports.RhBulkMoveWithWriteBarrier(ref destination, ref source, byteCount); + } +} diff --git a/src/coreclr/nativeaot/Test.CoreLib/src/System/SpanHelpers.cs b/src/coreclr/nativeaot/Test.CoreLib/src/System/SpanHelpers.cs new file mode 100644 index 00000000000000..77c20f716a4d6f --- /dev/null +++ b/src/coreclr/nativeaot/Test.CoreLib/src/System/SpanHelpers.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System +{ + internal static partial class SpanHelpers + { + [Intrinsic] + public static unsafe void ClearWithoutReferences(ref byte dest, nuint len) + { + Fill(ref dest, 0, len); + } + + [Intrinsic] + internal static unsafe void Memmove(ref byte dest, ref byte src, nuint len) + { + if ((nuint)(nint)Unsafe.ByteOffset(ref src, ref dest) >= len) + for (nuint i = 0; i < len; i++) + Unsafe.Add(ref dest, (nint)i) = Unsafe.Add(ref src, (nint)i); + else + for (nuint i = len; i > 0; i--) + Unsafe.Add(ref dest, (nint)(i - 1)) = Unsafe.Add(ref src, (nint)(i - 1)); + } + + + internal static void Fill(ref byte dest, byte value, nuint len) + { + for (nuint i = 0; i < len; i++) + Unsafe.Add(ref dest, (nint)i) = value; + } + } +} diff --git a/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj b/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj index 9d606a6b757115..b838c2ed7c14b7 100644 --- a/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj +++ b/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj @@ -232,6 +232,8 @@ + + diff --git a/src/coreclr/tools/Common/TypeSystem/IL/HelperExtensions.cs b/src/coreclr/tools/Common/TypeSystem/IL/HelperExtensions.cs index 4ccaff2d6dd9f0..a02c9bbf9da982 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/HelperExtensions.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/HelperExtensions.cs @@ -33,6 +33,12 @@ public static MethodDesc GetHelperEntryPoint(this TypeSystemContext context, str return helperMethod; } + public static MethodDesc GetCoreLibEntryPoint(this TypeSystemContext context, string namespaceName, string typeName, string methodName, MethodSignature signature) + { + MetadataType owningType = context.SystemModule.GetKnownType(namespaceName, typeName); + return owningType.GetKnownMethod(methodName, signature); + } + public static MethodDesc GetOptionalHelperEntryPoint(this TypeSystemContext context, string typeName, string methodName) { MetadataType helperType = context.GetOptionalHelperType(typeName); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs index d1fc6c593eef81..4d9328f1a34eef 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs @@ -137,6 +137,19 @@ private void CompileSingleMethod(ScannedMethodNode methodCodeNodeNeedingCode) ILScanResults IILScanner.Scan() { + _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.BulkWriteBarrier), "Not tracked by scanner"); + _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.MemCpy), "Not tracked by scanner"); + _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.MemSet), "Not tracked by scanner"); + _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.MemZero), "Not tracked by scanner"); + _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.CheckCastAny), "Not tracked by scanner"); + _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.CheckCastInterface), "Not tracked by scanner"); + _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.CheckCastClass), "Not tracked by scanner"); + _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.CheckCastClassSpecial), "Not tracked by scanner"); + _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.CheckInstanceAny), "Not tracked by scanner"); + _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.CheckInstanceInterface), "Not tracked by scanner"); + _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.CheckInstanceClass), "Not tracked by scanner"); + _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.IsInstanceOfException), "Not tracked by scanner"); + _dependencyGraph.ComputeMarkedNodes(); _nodeFactory.SetMarkingComplete(); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs index 8861535467c8ca..1d78df875c125b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs @@ -73,7 +73,7 @@ public static void GetEntryPoint(TypeSystemContext context, ReadyToRunHelper id, mangledName = context.Target.Architecture == TargetArchitecture.ARM64 ? "RhpCheckedAssignRefArm64" : "RhpCheckedAssignRef"; break; case ReadyToRunHelper.BulkWriteBarrier: - mangledName = "RhBuffer_BulkMoveWithWriteBarrier"; + methodDesc = context.GetCoreLibEntryPoint("System", "Buffer", "BulkMoveWithWriteBarrier", null); break; case ReadyToRunHelper.ByRefWriteBarrier: mangledName = context.Target.Architecture == TargetArchitecture.ARM64 ? "RhpByRefAssignRefArm64" : "RhpByRefAssignRef"; @@ -116,16 +116,16 @@ public static void GetEntryPoint(TypeSystemContext context, ReadyToRunHelper id, break; case ReadyToRunHelper.Box: case ReadyToRunHelper.Box_Nullable: - mangledName = "RhBox"; + methodDesc = context.GetCoreLibEntryPoint("System.Runtime", "RuntimeExports", "RhBox", null); break; case ReadyToRunHelper.Unbox: - mangledName = "RhUnbox2"; + methodDesc = context.GetCoreLibEntryPoint("System.Runtime", "RuntimeExports", "RhUnbox2", null); break; case ReadyToRunHelper.Unbox_Nullable: - mangledName = "RhUnboxNullable"; + methodDesc = context.GetCoreLibEntryPoint("System.Runtime", "RuntimeExports", "RhUnboxNullable", null); break; case ReadyToRunHelper.Unbox_TypeTest: - mangledName = "RhUnboxTypeTest"; + methodDesc = context.GetCoreLibEntryPoint("System.Runtime", "RuntimeExports", "RhUnboxTypeTest", null); break; case ReadyToRunHelper.NewMultiDimArr: @@ -143,20 +143,20 @@ public static void GetEntryPoint(TypeSystemContext context, ReadyToRunHelper id, break; case ReadyToRunHelper.Stelem_Ref: - mangledName = "RhpStelemRef"; + methodDesc = context.GetCoreLibEntryPoint("System.Runtime", "TypeCast", "StelemRef", null); break; case ReadyToRunHelper.Ldelema_Ref: - mangledName = "RhpLdelemaRef"; + methodDesc = context.GetCoreLibEntryPoint("System.Runtime", "TypeCast", "LdelemaRef", null); break; case ReadyToRunHelper.MemCpy: - mangledName = "RhSpanHelpers_MemCopy"; + methodDesc = context.GetCoreLibEntryPoint("System", "SpanHelpers", "Memmove", null); break; case ReadyToRunHelper.MemSet: - mangledName = "RhSpanHelpers_MemSet"; + methodDesc = context.GetCoreLibEntryPoint("System", "SpanHelpers", "Fill", null); break; case ReadyToRunHelper.MemZero: - mangledName = "RhSpanHelpers_MemZero"; + methodDesc = context.GetCoreLibEntryPoint("System", "SpanHelpers", "ClearWithoutReferences", null); break; case ReadyToRunHelper.NativeMemSet: mangledName = "memset"; @@ -284,29 +284,29 @@ public static void GetEntryPoint(TypeSystemContext context, ReadyToRunHelper id, break; case ReadyToRunHelper.CheckCastAny: - mangledName = "RhTypeCast_CheckCastAny"; + methodDesc = context.GetCoreLibEntryPoint("System.Runtime", "TypeCast", "CheckCastAny", null); break; case ReadyToRunHelper.CheckCastInterface: - mangledName = "RhTypeCast_CheckCastInterface"; + methodDesc = context.GetCoreLibEntryPoint("System.Runtime", "TypeCast", "CheckCastInterface", null); break; case ReadyToRunHelper.CheckCastClass: - mangledName = "RhTypeCast_CheckCastClass"; + methodDesc = context.GetCoreLibEntryPoint("System.Runtime", "TypeCast", "CheckCastClass", null); break; case ReadyToRunHelper.CheckCastClassSpecial: - mangledName = "RhTypeCast_CheckCastClassSpecial"; + methodDesc = context.GetCoreLibEntryPoint("System.Runtime", "TypeCast", "CheckCastClassSpecial", null); break; case ReadyToRunHelper.CheckInstanceAny: - mangledName = "RhTypeCast_IsInstanceOfAny"; + methodDesc = context.GetCoreLibEntryPoint("System.Runtime", "TypeCast", "IsInstanceOfAny", null); break; case ReadyToRunHelper.CheckInstanceInterface: - mangledName = "RhTypeCast_IsInstanceOfInterface"; + methodDesc = context.GetCoreLibEntryPoint("System.Runtime", "TypeCast", "IsInstanceOfInterface", null); break; case ReadyToRunHelper.CheckInstanceClass: - mangledName = "RhTypeCast_IsInstanceOfClass"; + methodDesc = context.GetCoreLibEntryPoint("System.Runtime", "TypeCast", "IsInstanceOfClass", null); break; case ReadyToRunHelper.IsInstanceOfException: - mangledName = "RhTypeCast_IsInstanceOfException"; + methodDesc = context.GetCoreLibEntryPoint("System.Runtime", "TypeCast", "IsInstanceOfException", null); break; case ReadyToRunHelper.MonitorEnter: diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs index 611d11e02edd1a..00a0d56d48150f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -897,6 +897,7 @@ private void ImportUnbox(int token, ILOpcode opCode) if (opCode == ILOpcode.unbox) { helper = ReadyToRunHelper.Unbox; + _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Unbox_TypeTest), "Unbox"); } else { @@ -1237,11 +1238,16 @@ private void ImportLoadElement(TypeDesc elementType) private void ImportStoreElement(int token) { - _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.RngChkFail), "stelem"); + ImportStoreElement((TypeDesc)_methodIL.GetObject(token)); } private void ImportStoreElement(TypeDesc elementType) { + if (elementType == null || elementType.IsGCPointer) + { + _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Stelem_Ref), "stelem"); + } + _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.RngChkFail), "stelem"); } @@ -1254,6 +1260,8 @@ private void ImportAddressOfElement(int token) _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, elementType), "ldelema"); else _dependencies.Add(_factory.NecessaryTypeSymbol(elementType), "ldelema"); + + _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Ldelema_Ref), "ldelema"); } _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.RngChkFail), "ldelema"); diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffer.cs b/src/libraries/System.Private.CoreLib/src/System/Buffer.cs index 2a1e2a02999509..30ee85041633d6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffer.cs @@ -175,9 +175,6 @@ ref Unsafe.As(ref source), private const uint BulkMoveWithWriteBarrierChunk = 0x4000; #endif -#if NATIVEAOT - [System.Runtime.RuntimeExport("RhBuffer_BulkMoveWithWriteBarrier")] -#endif internal static void BulkMoveWithWriteBarrier(ref byte destination, ref byte source, nuint byteCount) { if (byteCount <= BulkMoveWithWriteBarrierChunk) diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.ByteMemOps.cs b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.ByteMemOps.cs index c4831969ccb0c5..e2b33b5d4227f4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.ByteMemOps.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.ByteMemOps.cs @@ -33,9 +33,6 @@ private struct Block16 {} private struct Block64 {} #endif // HAS_CUSTOM_BLOCKS -#if NATIVEAOT - [System.Runtime.RuntimeExport("RhSpanHelpers_MemCopy")] -#endif [Intrinsic] // Unrolled for small constant lengths internal static unsafe void Memmove(ref byte dest, ref byte src, nuint len) { @@ -245,9 +242,6 @@ internal static unsafe void Memmove(ref byte dest, ref byte src, nuint len) Buffer._Memmove(ref dest, ref src, len); } -#if NATIVEAOT - [System.Runtime.RuntimeExport("RhSpanHelpers_MemZero")] -#endif [Intrinsic] // Unrolled for small sizes public static unsafe void ClearWithoutReferences(ref byte dest, nuint len) { @@ -434,9 +428,6 @@ public static unsafe void ClearWithoutReferences(ref byte dest, nuint len) Buffer._ZeroMemory(ref dest, len); } -#if NATIVEAOT - [System.Runtime.RuntimeExport("RhSpanHelpers_MemSet")] -#endif internal static void Fill(ref byte dest, byte value, nuint len) { if (!Vector.IsHardwareAccelerated) From 9df306fd2f23b22e3a50c75b6a171dea5da99376 Mon Sep 17 00:00:00 2001 From: Adeel Mujahid <3840695+am11@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:47:06 +0200 Subject: [PATCH 04/70] Fix linux-armel build (#110514) This syncs the gcc version in toolchian file with what's in the newest container build. Fixes #110517 --- eng/common/cross/toolchain.cmake | 67 +++++++++++++++----------------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake index 9a4e285a5ae3f0..9a7ecfbd42c5f3 100644 --- a/eng/common/cross/toolchain.cmake +++ b/eng/common/cross/toolchain.cmake @@ -40,7 +40,7 @@ if(TARGET_ARCH_NAME STREQUAL "arm") set(TOOLCHAIN "arm-linux-gnueabihf") endif() if(TIZEN) - set(TIZEN_TOOLCHAIN "armv7hl-tizen-linux-gnueabihf/9.2.0") + set(TIZEN_TOOLCHAIN "armv7hl-tizen-linux-gnueabihf") endif() elseif(TARGET_ARCH_NAME STREQUAL "arm64") set(CMAKE_SYSTEM_PROCESSOR aarch64) @@ -49,7 +49,7 @@ elseif(TARGET_ARCH_NAME STREQUAL "arm64") elseif(LINUX) set(TOOLCHAIN "aarch64-linux-gnu") if(TIZEN) - set(TIZEN_TOOLCHAIN "aarch64-tizen-linux-gnu/9.2.0") + set(TIZEN_TOOLCHAIN "aarch64-tizen-linux-gnu") endif() elseif(FREEBSD) set(triple "aarch64-unknown-freebsd12") @@ -58,7 +58,7 @@ elseif(TARGET_ARCH_NAME STREQUAL "armel") set(CMAKE_SYSTEM_PROCESSOR armv7l) set(TOOLCHAIN "arm-linux-gnueabi") if(TIZEN) - set(TIZEN_TOOLCHAIN "armv7l-tizen-linux-gnueabi/9.2.0") + set(TIZEN_TOOLCHAIN "armv7l-tizen-linux-gnueabi") endif() elseif(TARGET_ARCH_NAME STREQUAL "armv6") set(CMAKE_SYSTEM_PROCESSOR armv6l) @@ -81,7 +81,7 @@ elseif(TARGET_ARCH_NAME STREQUAL "riscv64") else() set(TOOLCHAIN "riscv64-linux-gnu") if(TIZEN) - set(TIZEN_TOOLCHAIN "riscv64-tizen-linux-gnu/13.1.0") + set(TIZEN_TOOLCHAIN "riscv64-tizen-linux-gnu") endif() endif() elseif(TARGET_ARCH_NAME STREQUAL "s390x") @@ -98,7 +98,7 @@ elseif(TARGET_ARCH_NAME STREQUAL "x64") elseif(LINUX) set(TOOLCHAIN "x86_64-linux-gnu") if(TIZEN) - set(TIZEN_TOOLCHAIN "x86_64-tizen-linux-gnu/9.2.0") + set(TIZEN_TOOLCHAIN "x86_64-tizen-linux-gnu") endif() elseif(FREEBSD) set(triple "x86_64-unknown-freebsd12") @@ -115,7 +115,7 @@ elseif(TARGET_ARCH_NAME STREQUAL "x86") set(TOOLCHAIN "i686-linux-gnu") endif() if(TIZEN) - set(TIZEN_TOOLCHAIN "i586-tizen-linux-gnu/9.2.0") + set(TIZEN_TOOLCHAIN "i586-tizen-linux-gnu") endif() else() message(FATAL_ERROR "Arch is ${TARGET_ARCH_NAME}. Only arm, arm64, armel, armv6, ppc64le, riscv64, s390x, x64 and x86 are supported!") @@ -127,30 +127,25 @@ endif() # Specify include paths if(TIZEN) - if(TARGET_ARCH_NAME STREQUAL "arm") - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/) - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/armv7hl-tizen-linux-gnueabihf) - endif() - if(TARGET_ARCH_NAME STREQUAL "armel") - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/) - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/armv7l-tizen-linux-gnueabi) - endif() - if(TARGET_ARCH_NAME STREQUAL "arm64") - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/) - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/aarch64-tizen-linux-gnu) - endif() - if(TARGET_ARCH_NAME STREQUAL "x86") - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/) - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/i586-tizen-linux-gnu) - endif() - if(TARGET_ARCH_NAME STREQUAL "x64") - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/) - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/x86_64-tizen-linux-gnu) - endif() - if(TARGET_ARCH_NAME STREQUAL "riscv64") - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/) - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/riscv64-tizen-linux-gnu) + function(find_toolchain_dir prefix) + # Dynamically find the version subdirectory + file(GLOB DIRECTORIES "${prefix}/*") + list(GET DIRECTORIES 0 FIRST_MATCH) + get_filename_component(TOOLCHAIN_VERSION ${FIRST_MATCH} NAME) + + set(TIZEN_TOOLCHAIN_PATH "${prefix}/${TOOLCHAIN_VERSION}" PARENT_SCOPE) + endfunction() + + if(TARGET_ARCH_NAME MATCHES "^(arm|armel|x86)$") + find_toolchain_dir("${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + else() + find_toolchain_dir("${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") endif() + + message(STATUS "TIZEN_TOOLCHAIN_PATH set to: ${TIZEN_TOOLCHAIN_PATH}") + + include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++) + include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++/${TIZEN_TOOLCHAIN}) endif() if(ANDROID) @@ -272,21 +267,21 @@ endif() if(TARGET_ARCH_NAME MATCHES "^(arm|armel)$") if(TIZEN) - add_toolchain_linker_flag("-B${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib") - add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}") endif() elseif(TARGET_ARCH_NAME MATCHES "^(arm64|x64|riscv64)$") if(TIZEN) - add_toolchain_linker_flag("-B${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib64") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib64") - add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}") add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/lib64") add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib64") - add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-Wl,--rpath-link=${TIZEN_TOOLCHAIN_PATH}") endif() elseif(TARGET_ARCH_NAME STREQUAL "s390x") add_toolchain_linker_flag("--target=${TOOLCHAIN}") @@ -297,10 +292,10 @@ elseif(TARGET_ARCH_NAME STREQUAL "x86") endif() add_toolchain_linker_flag(-m32) if(TIZEN) - add_toolchain_linker_flag("-B${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib") - add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}") endif() elseif(ILLUMOS) add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib/amd64") From d9f5ee47990061c5697242792a38e78f186fbd36 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Mon, 9 Dec 2024 15:43:18 +0100 Subject: [PATCH 05/70] [wasi] bump wasmtime to 27 (#110524) --- src/mono/wasi/wasmtime-version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasi/wasmtime-version.txt b/src/mono/wasi/wasmtime-version.txt index 2c0a9e33605740..008c39a438806b 100644 --- a/src/mono/wasi/wasmtime-version.txt +++ b/src/mono/wasi/wasmtime-version.txt @@ -1 +1 @@ -25.0.0 +27.0.0 From 45256197135d69b5f0ccd056d575a9c5ec5e5d30 Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Mon, 9 Dec 2024 11:48:56 -0300 Subject: [PATCH 06/70] [debugger] Fix a step that becomes a go (#110484) * Fixing step becomes a go * Trying to avoid step that becomes a go * Adding comments and more protections. * fixing comment * Checking if removing this comments, CI failures are gone. * Adding part of the changes to understand the failures on CI * Update src/coreclr/debug/ee/controller.cpp Co-authored-by: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> * Fixing wrong fix. --------- Co-authored-by: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> --- src/coreclr/debug/ee/controller.cpp | 10 +++++++++- src/coreclr/vm/threadsuspend.cpp | 12 +++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/coreclr/debug/ee/controller.cpp b/src/coreclr/debug/ee/controller.cpp index 47b6e0b5a81349..544bc824aa0e0e 100644 --- a/src/coreclr/debug/ee/controller.cpp +++ b/src/coreclr/debug/ee/controller.cpp @@ -7585,7 +7585,15 @@ bool DebuggerStepper::TriggerSingleStep(Thread *thread, const BYTE *ip) if (!g_pEEInterface->IsManagedNativeCode(ip)) { LOG((LF_CORDB,LL_INFO10000, "DS::TSS: not in managed code, Returning false (case 0)!\n")); - DisableSingleStep(); + // Sometimes we can get here with a callstack that is coming from an APC + // this will disable the single stepping and incorrectly resume an app that the user + // is stepping through. +#ifdef FEATURE_THREAD_ACTIVATION + if ((thread->m_State & Thread::TS_DebugWillSync) == 0) +#endif + { + DisableSingleStep(); + } return false; } diff --git a/src/coreclr/vm/threadsuspend.cpp b/src/coreclr/vm/threadsuspend.cpp index 6bc046e2b40bd4..505ceb174684f3 100644 --- a/src/coreclr/vm/threadsuspend.cpp +++ b/src/coreclr/vm/threadsuspend.cpp @@ -5732,8 +5732,9 @@ BOOL CheckActivationSafePoint(SIZE_T ip) Thread *pThread = GetThreadNULLOk(); // The criteria for safe activation is to be running managed code. - // Also we are not interested in handling interruption if we are already in preemptive mode. - BOOL isActivationSafePoint = pThread != NULL && + // Also we are not interested in handling interruption if we are already in preemptive mode nor if we are single stepping + BOOL isActivationSafePoint = pThread != NULL && + (pThread->m_StateNC & Thread::TSNC_DebuggerIsStepping) == 0 && pThread->PreemptiveGCDisabled() && ExecutionManager::IsManagedCode(ip); @@ -5918,7 +5919,12 @@ bool Thread::InjectActivation(ActivationReason reason) { return true; } - + // Avoid APC calls when the thread is in single step state to avoid any + // wrong resume because it's running a native code. + if ((m_StateNC & Thread::TSNC_DebuggerIsStepping) != 0) + { + return false; + } #ifdef FEATURE_SPECIAL_USER_MODE_APC _ASSERTE(UseSpecialUserModeApc()); From df70878a52ea59657e3223f285ccd19e18595b5b Mon Sep 17 00:00:00 2001 From: "bj.shou" Date: Tue, 10 Dec 2024 00:05:31 +0800 Subject: [PATCH 07/70] fix profiling env var names in profiling.md (#109764) Fix #109722 Co-authored-by: loadingcn --- docs/design/coreclr/botr/profiling.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/design/coreclr/botr/profiling.md b/docs/design/coreclr/botr/profiling.md index 3100666ac25769..35d57b37c1c166 100644 --- a/docs/design/coreclr/botr/profiling.md +++ b/docs/design/coreclr/botr/profiling.md @@ -68,10 +68,10 @@ The other interface involved for profiling is _ICorProfilerInfo_. The profiler The picture so far describes what happens once the application and profiler are running. But how are the two connected together when an application is started? The CLR makes the connection during its initialization in each process. It decides whether to connect to a profiler, and which profiler that should be, depending upon the value for two environment variables, checked one after the other: -- Cor\_Enable\_Profiling - only connect with a profiler if this environment variable exists and is set to a non-zero value. -- Cor\_Profiler - connect with the profiler with this CLSID or ProgID (which must have been stored previously in the Registry). The Cor\_Profiler environment variable is defined as a string: - - set Cor\_Profiler={32E2F4DA-1BEA-47ea-88F9-C5DAF691C94A}, or - - set Cor\_Profiler="MyProfiler" +- CORECLR\_ENABLE\_PROFILING - only connect with a profiler if this environment variable exists and is set to a non-zero value. +- CORECLR\_PROFILER - connect with the profiler with this CLSID or ProgID (which must have been stored previously in the Registry). The CORECLR\_PROFILER environment variable is defined as a string: + - set CORECLR\_PROFILER={32E2F4DA-1BEA-47ea-88F9-C5DAF691C94A}, or + - set CORECLR\_PROFILER="MyProfiler" - The profiler class is the one that implements _ICorProfilerCallback/ICorProfilerCallback2_. It is required that a profiler implement ICorProfilerCallback2; if it does not, it will not be loaded. When both checks above pass, the CLR creates an instance of the profiler in a similar fashion to _CoCreateInstance_. The profiler is not loaded through a direct call to _CoCreateInstance_ so that a call to _CoInitialize_ may be avoided, which requires setting the threading model. It then calls the _ICorProfilerCallback::Initialize_ method in the profiler. The signature of this method is: @@ -235,7 +235,7 @@ Profiling is enabled through environment variables, and since NT Services are st MyComputer -> Properties -> Advanced -> EnvironmentVariables -> System Variables -Both **Cor\_Enable\_Profiling** and **COR\_PROFILER have to be set** , and the user must ensure that the Profiler DLL is registered. Then, the target machine should be re-booted so that the NT Services pick up those changes. Note that this will enable profiling on a system-wide basis. So, to prevent every managed application that is run subsequently from being profiled, the user should delete those system environment variables after the re-boot. +Both **CORECLR\_ENABLE\_PROFILING** and **CORECLR\_PROFILER have to be set** , and the user must ensure that the Profiler DLL is registered. Then, the target machine should be re-booted so that the NT Services pick up those changes. Note that this will enable profiling on a system-wide basis. So, to prevent every managed application that is run subsequently from being profiled, the user should delete those system environment variables after the re-boot. Profiling API – High-Level Description ====================================== From 6ade56a3719c762f629b4d4f7c1f607bb9883b16 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 9 Dec 2024 18:03:01 +0100 Subject: [PATCH 08/70] Change assertion in IPGlobalProperties_DomainName_ReturnsEmptyStringWhenNotSet (#110070) Accept "localdomain" as a valid DomainName on Android. --- .../tests/FunctionalTests/IPGlobalPropertiesTest.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Net.NetworkInformation/tests/FunctionalTests/IPGlobalPropertiesTest.cs b/src/libraries/System.Net.NetworkInformation/tests/FunctionalTests/IPGlobalPropertiesTest.cs index ea8fe35b96efe8..011bb87b92b92c 100644 --- a/src/libraries/System.Net.NetworkInformation/tests/FunctionalTests/IPGlobalPropertiesTest.cs +++ b/src/libraries/System.Net.NetworkInformation/tests/FunctionalTests/IPGlobalPropertiesTest.cs @@ -212,7 +212,10 @@ public async Task GetUnicastAddresses_NotEmpty() public void IPGlobalProperties_DomainName_ReturnsEmptyStringWhenNotSet() { IPGlobalProperties gp = IPGlobalProperties.GetIPGlobalProperties(); - Assert.Equal(string.Empty, gp.DomainName); + + // [ActiveIssue("https://github.com/dotnet/runtime/issues/109280")] + string expectedDomainName = PlatformDetection.IsAndroid ? "localdomain" : string.Empty; + Assert.Equal(expectedDomainName, gp.DomainName); } } } From 1c960dde5b1af83d7363c9ba54fd3d2cfe6998e8 Mon Sep 17 00:00:00 2001 From: "Mukund Raghav Sharma (Moko)" <68247673+mrsharm@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:00:43 -0800 Subject: [PATCH 09/70] Added a fix for the build failure when STRESS_DYNAMIC_HEAP_COUNT is defined. (#110506) * Fix build break for DATAS Stress Mode * Removed stress heap dynamic --- src/coreclr/gc/gc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index bf2276f8411f59..3b717a6e239157 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -39921,7 +39921,7 @@ void gc_heap::bgc_thread_function() { dprintf (6666, ("h%d no concurrent GC needed, exiting", heap_number)); -#ifdef STRESS_DYNAMIC_HEAP_COUNT +#if defined(TRACE_GC) && defined(SIMPLE_DPRINTF) && defined(STRESS_DYNAMIC_HEAP_COUNT) flush_gc_log (true); GCToOSInterface::DebugBreak(); #endif From 6fdfb784d4aff9979bf97dfc70b60ddbd2710531 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 9 Dec 2024 10:56:41 -0800 Subject: [PATCH 10/70] [cdac] Handle no method def token when trying to get the IL version state (#110449) Some methods have a nil token - for example, special runtime methods like array functions. When we tried to look up their IL version state, we were throwing an exception. Methods like this will have no versioning state, so check for a nil token and skip the lookup. --- .../Contracts/IRuntimeTypeSystem.cs | 3 +- .../Contracts/CodeVersions_1.cs | 23 ++++++---- .../Contracts/RuntimeTypeSystem_1.cs | 4 +- .../EcmaMetadataUtils.cs | 14 +++++++ .../cdacreader/tests/CodeVersionsTests.cs | 42 ++++++++++++------- 5 files changed, 58 insertions(+), 28 deletions(-) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index d0ad8db342b0d9..31131c4475422d 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -80,6 +80,7 @@ internal interface IRuntimeTypeSystem : IContract #region TypeHandle inspection APIs public virtual TypeHandle GetTypeHandle(TargetPointer address) => throw new NotImplementedException(); public virtual TargetPointer GetModule(TypeHandle typeHandle) => throw new NotImplementedException(); + // A canonical method table is either the MethodTable itself, or in the case of a generic instantiation, it is the // MethodTable of the prototypical instance. public virtual TargetPointer GetCanonicalMethodTable(TypeHandle typeHandle) => throw new NotImplementedException(); @@ -130,7 +131,7 @@ internal interface IRuntimeTypeSystem : IContract public virtual bool IsGenericMethodDefinition(MethodDescHandle methodDesc) => throw new NotImplementedException(); public virtual ReadOnlySpan GetGenericMethodInstantiation(MethodDescHandle methodDesc) => throw new NotImplementedException(); - // Return mdTokenNil (0x06000000) if the method doesn't have a token, otherwise return the token of the method + // Return mdtMethodDef (0x06000000) if the method doesn't have a token, otherwise return the token of the method public virtual uint GetMethodToken(MethodDescHandle methodDesc) => throw new NotImplementedException(); // Return true if a MethodDesc represents an array method diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs index a0d06e79349c37..e6b1755004c710 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs @@ -12,7 +12,6 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; { private readonly Target _target; - public CodeVersions_1(Target target) { _target = target; @@ -23,9 +22,7 @@ ILCodeVersionHandle ICodeVersions.GetActiveILCodeVersion(TargetPointer methodDes // CodeVersionManager::GetActiveILCodeVersion GetModuleAndMethodDesc(methodDesc, out TargetPointer module, out uint methodDefToken); - ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module); - TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState; - TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefToken, out var _); + TargetPointer ilVersionStateAddress = GetILVersionStateAddress(module, methodDefToken); if (ilVersionStateAddress == TargetPointer.Null) { return ILCodeVersionHandle.CreateSynthetic(module, methodDefToken); @@ -73,14 +70,11 @@ IEnumerable ICodeVersions.GetILCodeVersions(TargetPointer m // CodeVersionManager::GetILCodeVersions GetModuleAndMethodDesc(methodDesc, out TargetPointer module, out uint methodDefToken); - ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module); - TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState; - TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefToken, out var _); - // always add the synthetic version yield return ILCodeVersionHandle.CreateSynthetic(module, methodDefToken); // if explicit versions exist, iterate linked list and return them + TargetPointer ilVersionStateAddress = GetILVersionStateAddress(module, methodDefToken); if (ilVersionStateAddress != TargetPointer.Null) { Data.ILCodeVersioningState ilState = _target.ProcessedData.GetOrAdd(ilVersionStateAddress); @@ -187,7 +181,6 @@ NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersionForILCodeVersion return (ilVersionId == codeVersion.ILVersionId) && ((NativeCodeVersionNodeFlags)codeVersion.Flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild); }); - } [Flags] @@ -301,6 +294,18 @@ private void GetModuleAndMethodDesc(TargetPointer methodDesc, out TargetPointer methodDefToken = rts.GetMethodToken(md); } + private TargetPointer GetILVersionStateAddress(TargetPointer module, uint methodDefToken) + { + // No token - for example, special runtime methods like array methods + if (methodDefToken == (uint)EcmaMetadataUtils.TokenType.mdtMethodDef) + return TargetPointer.Null; + + ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module); + TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState; + TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefToken, out var _); + return ilVersionStateAddress; + } + private ILCodeVersionNode AsNode(ILCodeVersionHandle handle) { if (handle.ILCodeVersionNode == TargetPointer.Null) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 65f161804948e1..423a2ef2ed9d0e 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -23,7 +23,6 @@ internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem private readonly Dictionary _methodTables = new(); private readonly Dictionary _methodDescs = new(); - internal struct MethodTable { internal MethodTableFlags_1 Flags { get; } @@ -126,8 +125,7 @@ private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.Metho uint tokenRemainder = (uint)(desc.Flags3AndTokenRemainder & tokenRemainderMask); uint tokenRange = ((uint)(chunk.FlagsAndTokenRange & tokenRangeMask)) << tokenRemainderBitCount; - - return 0x06000000 | tokenRange | tokenRemainder; + return EcmaMetadataUtils.CreateMethodDef(tokenRange | tokenRemainder); } public MethodClassification Classification => (MethodClassification)((int)_desc.Flags & (int)MethodDescFlags_1.MethodDescFlags.ClassificationMask); diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs index e0fbee71811581..f542da0e956ec0 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; + namespace Microsoft.Diagnostics.DataContractReader; internal static class EcmaMetadataUtils @@ -12,4 +14,16 @@ internal static class EcmaMetadataUtils internal static uint MakeToken(uint rid, uint table) => rid | (table << RowIdBitCount); + // ECMA-335 II.22 + // Metadata table index is the most significant byte of the 4-byte token + public enum TokenType : uint + { + mdtMethodDef = 0x06 << 24 + } + + public static uint CreateMethodDef(uint tokenParts) + { + Debug.Assert((tokenParts & 0xff000000) == 0, $"Token type should not be set in {nameof(tokenParts)}"); + return (uint)TokenType.mdtMethodDef | tokenParts; + } } diff --git a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs index 26e88593527875..bdd6cacdd3a106 100644 --- a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs +++ b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs @@ -33,7 +33,7 @@ internal class MockMethodDesc public bool IsVersionable { get; private set; } public uint RowId { get; set; } - public uint MethodToken => 0x06000000 | RowId; + public uint MethodToken => EcmaMetadataUtils.CreateMethodDef(RowId); // n.b. in the real RuntimeTypeSystem_1 this is more complex public TargetCodePointer NativeCode { get; private set; } @@ -342,9 +342,10 @@ public void GetActiveNativeCodeVersion_DefaultCase(MockTarget.Architecture arch) { uint methodRowId = 0x25; // arbitrary TargetCodePointer expectedNativeCodePointer = new TargetCodePointer(0x0700_abc0); - uint methodDefToken = 0x06000000 | methodRowId; + uint methodDefToken = EcmaMetadataUtils.CreateMethodDef(methodRowId); var builder = new MockCodeVersions(arch); var methodDescAddress = new TargetPointer(0x00aa_aa00); + var methodDescNilTokenAddress = new TargetPointer(0x00aa_bb00); var moduleAddress = new TargetPointer(0x00ca_ca00); TargetPointer versioningState = builder.AddILCodeVersioningState( @@ -353,34 +354,45 @@ public void GetActiveNativeCodeVersion_DefaultCase(MockTarget.Architecture arch) activeVersionModule: moduleAddress, activeVersionMethodDef: methodDefToken, firstVersionNode: TargetPointer.Null); - var oneModule = new MockModule() { + var module = new MockModule() { Address = moduleAddress, MethodDefToILCodeVersioningStateAddress = new TargetPointer(0x00da_da00), MethodDefToILCodeVersioningStateTable = new Dictionary() { { methodRowId, versioningState} }, }; - var oneMethodTable = new MockMethodTable() { + var methodTable = new MockMethodTable() { Address = new TargetPointer(0x00ba_ba00), - Module = oneModule, + Module = module, }; - var oneMethod = MockMethodDesc.CreateVersionable(selfAddress: methodDescAddress, methodDescVersioningState: TargetPointer.Null, nativeCode: expectedNativeCodePointer); - oneMethod.MethodTable = oneMethodTable; - oneMethod.RowId = methodRowId; + var method = MockMethodDesc.CreateVersionable(selfAddress: methodDescAddress, methodDescVersioningState: TargetPointer.Null, nativeCode: expectedNativeCodePointer); + method.MethodTable = methodTable; + method.RowId = methodRowId; + + var methodNilToken = MockMethodDesc.CreateVersionable(selfAddress: methodDescNilTokenAddress, methodDescVersioningState: TargetPointer.Null, nativeCode: expectedNativeCodePointer); + methodNilToken.MethodTable = methodTable; - var target = CreateTarget(arch, [oneMethod], [oneMethodTable], [], [oneModule], builder); + var target = CreateTarget(arch, [method, methodNilToken], [methodTable], [], [module], builder); // TEST var codeVersions = target.Contracts.CodeVersions; - Assert.NotNull(codeVersions); - NativeCodeVersionHandle handle = codeVersions.GetActiveNativeCodeVersion(methodDescAddress); - Assert.True(handle.Valid); - Assert.Equal(methodDescAddress, handle.MethodDescAddress); - var actualCodeAddress = codeVersions.GetNativeCode(handle); - Assert.Equal(expectedNativeCodePointer, actualCodeAddress); + { + NativeCodeVersionHandle handle = codeVersions.GetActiveNativeCodeVersion(methodDescAddress); + Assert.True(handle.Valid); + Assert.Equal(methodDescAddress, handle.MethodDescAddress); + var actualCodeAddress = codeVersions.GetNativeCode(handle); + Assert.Equal(expectedNativeCodePointer, actualCodeAddress); + } + { + NativeCodeVersionHandle handle = codeVersions.GetActiveNativeCodeVersion(methodDescNilTokenAddress); + Assert.True(handle.Valid); + Assert.Equal(methodDescNilTokenAddress, handle.MethodDescAddress); + var actualCodeAddress = codeVersions.GetNativeCode(handle); + Assert.Equal(expectedNativeCodePointer, actualCodeAddress); + } } [Theory] From a08711cdeaf2a120d8d687615dbac6a0d69cdbbd Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed <10833894+tarekgh@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:31:45 -0800 Subject: [PATCH 11/70] Normalization APIs using the spans (#110465) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Normalization APIs using the spans * Address the feedback * Update src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.Icu.cs Co-authored-by: Günther Foidl * Fix comment indent --------- Co-authored-by: Günther Foidl Co-authored-by: Eric StJohn --- .../src/Resources/Strings.resx | 57 ++++--- .../System/Globalization/Normalization.Icu.cs | 105 ++++++++++-- .../System/Globalization/Normalization.Nls.cs | 158 +++++++++++++----- .../src/System/Globalization/Normalization.cs | 73 +++++++- .../System/StringNormalizationExtensions.cs | 57 +++++++ .../System.Runtime/ref/System.Runtime.cs | 3 + .../Normalization/NormalizationAll.cs | 44 +++++ .../Normalization/StringNormalizationTests.cs | 53 +++++- 8 files changed, 461 insertions(+), 89 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index e7dadba40a5395..3e91b3ecb8340f 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -1,17 +1,17 @@  - @@ -1315,6 +1315,9 @@ Invalid or unsupported normalization form. + + `NormalizationForm.FormKC` and `NormalizationForm.FormKD` are not supported in browser environments or WebAssembly. + An undefined NumberStyles value is being used. diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.Icu.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.Icu.cs index 6ef9df95aa79d4..076629fa32b841 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.Icu.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.Icu.cs @@ -10,31 +10,33 @@ namespace System.Globalization { internal static partial class Normalization { - private static unsafe bool IcuIsNormalized(string strInput, NormalizationForm normalizationForm) + private static unsafe bool IcuIsNormalized(ReadOnlySpan source, NormalizationForm normalizationForm) { Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(!GlobalizationMode.UseNls); + Debug.Assert(!source.IsEmpty); + Debug.Assert(normalizationForm is NormalizationForm.FormC or NormalizationForm.FormD or NormalizationForm.FormKC or NormalizationForm.FormKD); - ValidateArguments(strInput, normalizationForm); + ValidateArguments(source, normalizationForm, nameof(source)); int ret; - fixed (char* pInput = strInput) + fixed (char* pInput = source) { #if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS if (GlobalizationMode.Hybrid) { - ret = Interop.Globalization.IsNormalizedNative(normalizationForm, pInput, strInput.Length); + ret = Interop.Globalization.IsNormalizedNative(normalizationForm, pInput, source.Length); } else #endif { - ret = Interop.Globalization.IsNormalized(normalizationForm, pInput, strInput.Length); + ret = Interop.Globalization.IsNormalized(normalizationForm, pInput, source.Length); } } if (ret == -1) { - throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(strInput)); + throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(source)); } return ret == 1; @@ -44,6 +46,7 @@ private static unsafe string IcuNormalize(string strInput, NormalizationForm nor { Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(!GlobalizationMode.UseNls); + Debug.Assert(normalizationForm == NormalizationForm.FormC || normalizationForm == NormalizationForm.FormD || normalizationForm == NormalizationForm.FormKC || normalizationForm == NormalizationForm.FormKD); ValidateArguments(strInput, normalizationForm); @@ -114,25 +117,95 @@ private static unsafe string IcuNormalize(string strInput, NormalizationForm nor } } - private static void ValidateArguments(string strInput, NormalizationForm normalizationForm) + private static unsafe bool IcuTryNormalize(ReadOnlySpan source, Span destination, out int charsWritten, NormalizationForm normalizationForm = NormalizationForm.FormC) { - Debug.Assert(strInput != null); + Debug.Assert(!GlobalizationMode.Invariant); + Debug.Assert(!GlobalizationMode.UseNls); + Debug.Assert(!source.IsEmpty); + Debug.Assert(normalizationForm == NormalizationForm.FormC || normalizationForm == NormalizationForm.FormD || normalizationForm == NormalizationForm.FormKC || normalizationForm == NormalizationForm.FormKD); - if ((OperatingSystem.IsBrowser() || OperatingSystem.IsWasi())&& (normalizationForm == NormalizationForm.FormKC || normalizationForm == NormalizationForm.FormKD)) + if (destination.IsEmpty) { - // Browser's ICU doesn't contain data needed for FormKC and FormKD - throw new PlatformNotSupportedException(); + charsWritten = 0; + return false; + } + + ValidateArguments(source, normalizationForm, nameof(source)); + + int realLen; + fixed (char* pInput = source) + fixed (char* pDest = destination) + { +#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + if (GlobalizationMode.Hybrid) + { + realLen = Interop.Globalization.NormalizeStringNative(normalizationForm, pInput, source.Length, pDest, destination.Length); + } + else +#endif + { + realLen = Interop.Globalization.NormalizeString(normalizationForm, pInput, source.Length, pDest, destination.Length); + } + } + + if (realLen < 0) + { + throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(source)); + } + + if (realLen <= destination.Length) + { + charsWritten = realLen; + return true; + } + + charsWritten = 0; + return false; + } + + private static unsafe int IcuGetNormalizedLength(ReadOnlySpan source, NormalizationForm normalizationForm) + { + Debug.Assert(!GlobalizationMode.Invariant); + Debug.Assert(!GlobalizationMode.UseNls); + Debug.Assert(!source.IsEmpty); + Debug.Assert(normalizationForm == NormalizationForm.FormC || normalizationForm == NormalizationForm.FormD || normalizationForm == NormalizationForm.FormKC || normalizationForm == NormalizationForm.FormKD); + + ValidateArguments(source, normalizationForm, nameof(source)); + + int realLen; + fixed (char* pInput = source) + { +#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + if (GlobalizationMode.Hybrid) + { + realLen = Interop.Globalization.NormalizeStringNative(normalizationForm, pInput, source.Length, null, 0); + } + else +#endif + { + realLen = Interop.Globalization.NormalizeString(normalizationForm, pInput, source.Length, null, 0); + } + } + + if (realLen < 0) + { + throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(source)); } - if (normalizationForm != NormalizationForm.FormC && normalizationForm != NormalizationForm.FormD && - normalizationForm != NormalizationForm.FormKC && normalizationForm != NormalizationForm.FormKD) + return realLen; + } + + private static void ValidateArguments(ReadOnlySpan strInput, NormalizationForm normalizationForm, string paramName = "strInput") + { + if ((OperatingSystem.IsBrowser() || OperatingSystem.IsWasi()) && (normalizationForm == NormalizationForm.FormKC || normalizationForm == NormalizationForm.FormKD)) { - throw new ArgumentException(SR.Argument_InvalidNormalizationForm, nameof(normalizationForm)); + // Browser's ICU doesn't contain data needed for FormKC and FormKD + throw new PlatformNotSupportedException(SR.Argument_UnsupportedNormalizationFormInBrowser); } if (HasInvalidUnicodeSequence(strInput)) { - throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(strInput)); + throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, paramName); } } @@ -143,7 +216,7 @@ private static void ValidateArguments(string strInput, NormalizationForm normali /// We walk the string ourselves looking for these bad sequences so we can continue to throw /// ArgumentException in these cases. /// - private static bool HasInvalidUnicodeSequence(string s) + private static bool HasInvalidUnicodeSequence(ReadOnlySpan s) { for (int i = 0; i < s.Length; i++) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.Nls.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.Nls.cs index 3abea572529896..2e63cd5daa5b81 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.Nls.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.Nls.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -10,45 +11,23 @@ namespace System.Globalization { internal static partial class Normalization { - private static unsafe bool NlsIsNormalized(string strInput, NormalizationForm normalizationForm) + private static unsafe bool NlsIsNormalized(ReadOnlySpan source, NormalizationForm normalizationForm) { Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(GlobalizationMode.UseNls); - Debug.Assert(strInput != null); + Debug.Assert(!source.IsEmpty); + Debug.Assert(normalizationForm == NormalizationForm.FormC || normalizationForm == NormalizationForm.FormD || normalizationForm == NormalizationForm.FormKC || normalizationForm == NormalizationForm.FormKD); - // The only way to know if IsNormalizedString failed is through checking the Win32 last error - // IsNormalizedString pinvoke has SetLastError attribute property which will set the last error - // to 0 (ERROR_SUCCESS) before executing the calls. Interop.BOOL result; - fixed (char* pInput = strInput) + fixed (char* pInput = source) { - result = Interop.Normaliz.IsNormalizedString(normalizationForm, pInput, strInput.Length); + result = Interop.Normaliz.IsNormalizedString(normalizationForm, pInput, source.Length); } - int lastError = Marshal.GetLastPInvokeError(); - switch (lastError) - { - case Interop.Errors.ERROR_SUCCESS: - break; - - case Interop.Errors.ERROR_INVALID_PARAMETER: - case Interop.Errors.ERROR_NO_UNICODE_TRANSLATION: - if (normalizationForm != NormalizationForm.FormC && - normalizationForm != NormalizationForm.FormD && - normalizationForm != NormalizationForm.FormKC && - normalizationForm != NormalizationForm.FormKD) - { - throw new ArgumentException(SR.Argument_InvalidNormalizationForm, nameof(normalizationForm)); - } - - throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(strInput)); - - case Interop.Errors.ERROR_NOT_ENOUGH_MEMORY: - throw new OutOfMemoryException(); - - default: - throw new InvalidOperationException(SR.Format(SR.UnknownError_Num, lastError)); - } + // The only way to know if IsNormalizedString failed is through checking the Win32 last error + // IsNormalizedString pinvoke has SetLastError attribute property which will set the last error + // to 0 (ERROR_SUCCESS) before executing the calls. + CheckLastErrorAndThrowIfFailed(nameof(source)); return result != Interop.BOOL.FALSE; } @@ -58,6 +37,7 @@ private static unsafe string NlsNormalize(string strInput, NormalizationForm nor Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(GlobalizationMode.UseNls); Debug.Assert(strInput != null); + Debug.Assert(normalizationForm == NormalizationForm.FormC || normalizationForm == NormalizationForm.FormD || normalizationForm == NormalizationForm.FormKC || normalizationForm == NormalizationForm.FormKD); if (strInput.Length == 0) { @@ -111,14 +91,6 @@ private static unsafe string NlsNormalize(string strInput, NormalizationForm nor case Interop.Errors.ERROR_INVALID_PARAMETER: case Interop.Errors.ERROR_NO_UNICODE_TRANSLATION: - if (normalizationForm != NormalizationForm.FormC && - normalizationForm != NormalizationForm.FormD && - normalizationForm != NormalizationForm.FormKC && - normalizationForm != NormalizationForm.FormKD) - { - throw new ArgumentException(SR.Argument_InvalidNormalizationForm, nameof(normalizationForm)); - } - // Illegal code point or order found. Ie: FFFE or D800 D800, etc. throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(strInput)); @@ -139,5 +111,113 @@ private static unsafe string NlsNormalize(string strInput, NormalizationForm nor } } } + + private static unsafe bool NlsTryNormalize(ReadOnlySpan source, Span destination, out int charsWritten, NormalizationForm normalizationForm = NormalizationForm.FormC) + { + Debug.Assert(!GlobalizationMode.Invariant); + Debug.Assert(GlobalizationMode.UseNls); + Debug.Assert(!source.IsEmpty); + Debug.Assert(normalizationForm == NormalizationForm.FormC || normalizationForm == NormalizationForm.FormD || normalizationForm == NormalizationForm.FormKC || normalizationForm == NormalizationForm.FormKD); + + if (destination.IsEmpty) + { + charsWritten = 0; + return false; + } + + // we depend on Win32 last error when calling NormalizeString + // NormalizeString pinvoke has SetLastError attribute property which will set the last error + // to 0 (ERROR_SUCCESS) before executing the calls. + + int realLength; + fixed (char* pInput = source) + fixed (char* pDest = destination) + { + realLength = Interop.Normaliz.NormalizeString(normalizationForm, pInput, source.Length, pDest, destination.Length); + } + + int lastError = Marshal.GetLastPInvokeError(); + switch (lastError) + { + case Interop.Errors.ERROR_SUCCESS: + charsWritten = realLength; + return true; + + // Do appropriate stuff for the individual errors: + case Interop.Errors.ERROR_INSUFFICIENT_BUFFER: + charsWritten = 0; + return false; + + case Interop.Errors.ERROR_INVALID_PARAMETER: + case Interop.Errors.ERROR_NO_UNICODE_TRANSLATION: + // Illegal code point or order found. Ie: FFFE or D800 D800, etc. + throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(source)); + + case Interop.Errors.ERROR_NOT_ENOUGH_MEMORY: + throw new OutOfMemoryException(); + + default: + // We shouldn't get here... + throw new InvalidOperationException(SR.Format(SR.UnknownError_Num, lastError)); + } + } + + private static unsafe int NlsGetNormalizedLength(ReadOnlySpan source, NormalizationForm normalizationForm) + { + Debug.Assert(!GlobalizationMode.Invariant); + Debug.Assert(GlobalizationMode.UseNls); + Debug.Assert(!source.IsEmpty); + Debug.Assert(normalizationForm == NormalizationForm.FormC || normalizationForm == NormalizationForm.FormD || normalizationForm == NormalizationForm.FormKC || normalizationForm == NormalizationForm.FormKD); + + // we depend on Win32 last error when calling NormalizeString + // NormalizeString pinvoke has SetLastError attribute property which will set the last error + // to 0 (ERROR_SUCCESS) before executing the calls. + + int realLength; + fixed (char* pInput = source) + { + realLength = Interop.Normaliz.NormalizeString(normalizationForm, pInput, source.Length, null, 0); + } + + int lastError = Marshal.GetLastPInvokeError(); + switch (lastError) + { + case Interop.Errors.ERROR_SUCCESS: + return realLength; + + case Interop.Errors.ERROR_INVALID_PARAMETER: + case Interop.Errors.ERROR_NO_UNICODE_TRANSLATION: + // Illegal code point or order found. Ie: FFFE or D800 D800, etc. + throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(source)); + + case Interop.Errors.ERROR_NOT_ENOUGH_MEMORY: + throw new OutOfMemoryException(); + + default: + // We shouldn't get here... + throw new InvalidOperationException(SR.Format(SR.UnknownError_Num, lastError)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void CheckLastErrorAndThrowIfFailed(string inputName) + { + int lastError = Marshal.GetLastPInvokeError(); + switch (lastError) + { + case Interop.Errors.ERROR_SUCCESS: + break; + + case Interop.Errors.ERROR_INVALID_PARAMETER: + case Interop.Errors.ERROR_NO_UNICODE_TRANSLATION: + throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, inputName); + + case Interop.Errors.ERROR_NOT_ENOUGH_MEMORY: + throw new OutOfMemoryException(); + + default: + throw new InvalidOperationException(SR.Format(SR.UnknownError_Num, lastError)); + } + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.cs index d120302a8aa8e7..647e097601cd71 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.cs @@ -2,28 +2,33 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Text; namespace System.Globalization { internal static partial class Normalization { - internal static bool IsNormalized(string strInput, NormalizationForm normalizationForm) + internal static bool IsNormalized(ReadOnlySpan source, NormalizationForm normalizationForm = NormalizationForm.FormC) { - if (GlobalizationMode.Invariant) + CheckNormalizationForm(normalizationForm); + + // In Invariant mode we assume all characters are normalized. + if (GlobalizationMode.Invariant || source.IsEmpty || Ascii.IsValid(source)) { - // In Invariant mode we assume all characters are normalized. // This is because we don't support any linguistic operation on the strings return true; } return GlobalizationMode.UseNls ? - NlsIsNormalized(strInput, normalizationForm) : - IcuIsNormalized(strInput, normalizationForm); + NlsIsNormalized(source, normalizationForm) : + IcuIsNormalized(source, normalizationForm); } internal static string Normalize(string strInput, NormalizationForm normalizationForm) { + CheckNormalizationForm(normalizationForm); + if (GlobalizationMode.Invariant) { // In Invariant mode we assume all characters are normalized. @@ -35,5 +40,63 @@ internal static string Normalize(string strInput, NormalizationForm normalizatio NlsNormalize(strInput, normalizationForm) : IcuNormalize(strInput, normalizationForm); } + + internal static bool TryNormalize(ReadOnlySpan source, Span destination, out int charsWritten, NormalizationForm normalizationForm = NormalizationForm.FormC) + { + CheckNormalizationForm(normalizationForm); + + if (source.IsEmpty) + { + charsWritten = 0; + return true; + } + + if (GlobalizationMode.Invariant || Ascii.IsValid(source)) + { + // In Invariant mode we assume all characters are normalized. + // This is because we don't support any linguistic operation on the strings + + if (source.TryCopyTo(destination)) + { + charsWritten = source.Length; + return true; + } + + charsWritten = 0; + return false; + } + + return GlobalizationMode.UseNls ? + NlsTryNormalize(source, destination, out charsWritten, normalizationForm) : + IcuTryNormalize(source, destination, out charsWritten, normalizationForm); + } + + internal static int GetNormalizedLength(this ReadOnlySpan source, NormalizationForm normalizationForm = NormalizationForm.FormC) + { + CheckNormalizationForm(normalizationForm); + + if (GlobalizationMode.Invariant || source.IsEmpty || Ascii.IsValid(source)) + { + // In Invariant mode we assume all characters are normalized. + // This is because we don't support any linguistic operation on the strings + return source.Length; + } + + return GlobalizationMode.UseNls ? + NlsGetNormalizedLength(source, normalizationForm) : + IcuGetNormalizedLength(source, normalizationForm); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void CheckNormalizationForm(NormalizationForm normalizationForm) + { + if (normalizationForm != NormalizationForm.FormC && + normalizationForm != NormalizationForm.FormD && + normalizationForm != NormalizationForm.FormKC && + normalizationForm != NormalizationForm.FormKD) + { + throw new ArgumentException(SR.Argument_InvalidNormalizationForm, nameof(normalizationForm)); + } + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/StringNormalizationExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/StringNormalizationExtensions.cs index 712752626fe549..7a94a853581aa2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/StringNormalizationExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/StringNormalizationExtensions.cs @@ -5,13 +5,27 @@ namespace System { + /// + /// Extensions for string normalization. + /// public static partial class StringNormalizationExtensions { + /// + /// Determines whether the specified string is in a normalized . + /// + /// The string to check. + /// if the specified string is in a normalized form; otherwise, . public static bool IsNormalized(this string strInput) { return IsNormalized(strInput, NormalizationForm.FormC); } + /// + /// Determines whether the specified string is in a normalized form. + /// + /// The string to check. + /// The normalization form to use. + /// if the specified string is in a normalized form; otherwise, . public static bool IsNormalized(this string strInput, NormalizationForm normalizationForm) { ArgumentNullException.ThrowIfNull(strInput); @@ -19,17 +33,60 @@ public static bool IsNormalized(this string strInput, NormalizationForm normaliz return strInput.IsNormalized(normalizationForm); } + /// + /// Determines whether the specified span of characters is in a normalized form. + /// + /// The span of characters to check. + /// The normalization form to use. + /// if the specified span of characters is in a normalized form; otherwise, . + /// The specified character span contains an invalid code point or the normalization form is invalid. + public static bool IsNormalized(this ReadOnlySpan source, NormalizationForm normalizationForm = NormalizationForm.FormC) => + System.Globalization.Normalization.IsNormalized(source, normalizationForm); + + /// + /// Normalizes the specified string to the . + /// + /// The string to normalize. + /// The normalized string in . public static string Normalize(this string strInput) { // Default to Form C return Normalize(strInput, NormalizationForm.FormC); } + /// + /// Normalizes the specified string to the specified normalization form. + /// + /// The string to normalize. + /// The normalization form to use. + /// The normalized string in the specified normalization form. public static string Normalize(this string strInput, NormalizationForm normalizationForm) { ArgumentNullException.ThrowIfNull(strInput); return strInput.Normalize(normalizationForm); } + + /// + /// Normalizes the specified span of characters to the specified normalization form. + /// + /// The span of characters to normalize. + /// The buffer to write the normalized characters to. + /// When this method returns, contains the number of characters written to . + /// The normalization form to use. + /// if the specified span of characters was successfully normalized; otherwise, . + /// The specified character span contains an invalid code point or the normalization form is invalid. + public static bool TryNormalize(this ReadOnlySpan source, Span destination, out int charsWritten, NormalizationForm normalizationForm = NormalizationForm.FormC) => + System.Globalization.Normalization.TryNormalize(source, destination, out charsWritten, normalizationForm); + + /// + /// Gets the estimated length of the normalized form of the specified string in the . + /// + /// The character span to get the estimated length of the normalized form. + /// The normalization form to use. + /// The estimated length of the normalized form of the specified string. + /// The specified character span contains an invalid code point or the normalization form is invalid. + public static int GetNormalizedLength(this ReadOnlySpan source, NormalizationForm normalizationForm = NormalizationForm.FormC) => + System.Globalization.Normalization.GetNormalizedLength(source, normalizationForm); } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 0d79bc4c54e23b..da44791d9607cf 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -5819,8 +5819,11 @@ public static partial class StringNormalizationExtensions { public static bool IsNormalized(this string strInput) { throw null; } public static bool IsNormalized(this string strInput, System.Text.NormalizationForm normalizationForm) { throw null; } + public static bool IsNormalized(this ReadOnlySpan source, System.Text.NormalizationForm normalizationForm = System.Text.NormalizationForm.FormC) { throw null; } public static string Normalize(this string strInput) { throw null; } + public static bool TryNormalize(this ReadOnlySpan source, Span destination, out int charsWritten, System.Text.NormalizationForm normalizationForm = System.Text.NormalizationForm.FormC) { throw null; } public static string Normalize(this string strInput, System.Text.NormalizationForm normalizationForm) { throw null; } + public static int GetNormalizedLength(this ReadOnlySpan source, System.Text.NormalizationForm normalizationForm = System.Text.NormalizationForm.FormC) { throw null; } } [System.FlagsAttribute] public enum StringSplitOptions diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Extensions.Tests/Normalization/NormalizationAll.cs b/src/libraries/System.Runtime/tests/System.Globalization.Extensions.Tests/Normalization/NormalizationAll.cs index 703988c1cffb39..bedd07baf4637a 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Extensions.Tests/Normalization/NormalizationAll.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Extensions.Tests/Normalization/NormalizationAll.cs @@ -93,6 +93,18 @@ private static void VerifyConformanceInvariant(NormalizationForm normForm, strin string normalized4 = c4.Normalize(normForm); string normalized5 = c5.Normalize(normForm); + Span normalizedSpan1 = new char[normalized1.Length]; + Span normalizedSpan2 = new char[normalized2.Length]; + Span normalizedSpan3 = new char[normalized3.Length]; + Span normalizedSpan4 = new char[normalized4.Length]; + Span normalizedSpan5 = new char[normalized5.Length]; + + Assert.True(c1.AsSpan().TryNormalize(normalizedSpan1, out int charsWritten1, normForm), $"'{c1}' is not normalized with form {normForm}"); + Assert.True(c2.AsSpan().TryNormalize(normalizedSpan2, out int charsWritten2, normForm), $"'{c2}' is not normalized with form {normForm}"); + Assert.True(c3.AsSpan().TryNormalize(normalizedSpan3, out int charsWritten3, normForm), $"'{c3}' is not normalized with form {normForm}"); + Assert.True(c4.AsSpan().TryNormalize(normalizedSpan4, out int charsWritten4, normForm), $"'{c4}' is not normalized with form {normForm}"); + Assert.True(c5.AsSpan().TryNormalize(normalizedSpan5, out int charsWritten5, normForm), $"'{c5}' is not normalized with form {normForm}"); + switch (normForm) { case NormalizationForm.FormC: @@ -101,15 +113,24 @@ private static void VerifyConformanceInvariant(NormalizationForm normForm, strin AssertEqualsForm(c2, normalized2); AssertEqualsForm(c2, normalized3); + AssertEqualsForm(c2, normalizedSpan1.Slice(0, charsWritten1).ToString()); + AssertEqualsForm(c2, normalizedSpan2.Slice(0, charsWritten2).ToString()); + AssertEqualsForm(c2, normalizedSpan3.Slice(0, charsWritten3).ToString()); + // c4 == NFC(c4) == NFC(c5) AssertEqualsForm(c4, normalized4); AssertEqualsForm(c4, normalized5); + AssertEqualsForm(c4, normalizedSpan4.Slice(0, charsWritten4).ToString()); + AssertEqualsForm(c4, normalizedSpan5.Slice(0, charsWritten5).ToString()); + // c2 is normalized to Form C Assert.True(c2.IsNormalized(normForm), $"'{c2}' is marked as not normalized with form {normForm}"); + Assert.True(c2.AsSpan().IsNormalized(normForm), $"'{c2}' span is marked as not normalized with form {normForm}"); // c4 is normalized to Form C Assert.True(c4.IsNormalized(normForm), $"'{c4}' is marked as not normalized with form {normForm}"); + Assert.True(c4.AsSpan().IsNormalized(normForm), $"'{c4}' span is marked as not normalized with form {normForm}"); break; case NormalizationForm.FormD: @@ -118,15 +139,24 @@ private static void VerifyConformanceInvariant(NormalizationForm normForm, strin AssertEqualsForm(c3, normalized2); AssertEqualsForm(c3, normalized3); + AssertEqualsForm(c3, normalizedSpan1.Slice(0, charsWritten1).ToString()); + AssertEqualsForm(c3, normalizedSpan2.Slice(0, charsWritten2).ToString()); + AssertEqualsForm(c3, normalizedSpan3.Slice(0, charsWritten3).ToString()); + // c5 == NFD(c4) == NFD(c5) AssertEqualsForm(c5, normalized4); AssertEqualsForm(c5, normalized5); + AssertEqualsForm(c5, normalizedSpan4.Slice(0, charsWritten4).ToString()); + AssertEqualsForm(c5, normalizedSpan5.Slice(0, charsWritten5).ToString()); + // c3 is normalized to Form D Assert.True(c3.IsNormalized(normForm), $"'{c3}' is marked as not normalized with form {normForm}"); + Assert.True(c3.AsSpan().IsNormalized(normForm), $"'{c3}' span is marked as not normalized with form {normForm}"); // c5 is normalized to Form D Assert.True(c5.IsNormalized(normForm), $"'{c5}' is marked as not normalized with form {normForm}"); + Assert.True(c5.AsSpan().IsNormalized(normForm), $"'{c5}' span is marked as not normalized with form {normForm}"); break; case NormalizationForm.FormKC: @@ -138,8 +168,15 @@ private static void VerifyConformanceInvariant(NormalizationForm normForm, strin AssertEqualsForm(c4, normalized4); AssertEqualsForm(c4, normalized5); + AssertEqualsForm(c4, normalizedSpan1.Slice(0, charsWritten1).ToString()); + AssertEqualsForm(c4, normalizedSpan2.Slice(0, charsWritten2).ToString()); + AssertEqualsForm(c4, normalizedSpan3.Slice(0, charsWritten3).ToString()); + AssertEqualsForm(c4, normalizedSpan4.Slice(0, charsWritten4).ToString()); + AssertEqualsForm(c4, normalizedSpan5.Slice(0, charsWritten5).ToString()); + // c4 is normalized to Form KC Assert.True(c4.IsNormalized(normForm), $"'{c4}' is marked as not normalized with form {normForm}"); + Assert.True(c4.AsSpan().IsNormalized(normForm), $"'{c4}' span is marked as not normalized with form {normForm}"); break; case NormalizationForm.FormKD: @@ -151,8 +188,15 @@ private static void VerifyConformanceInvariant(NormalizationForm normForm, strin AssertEqualsForm(c5, normalized4); AssertEqualsForm(c5, normalized5); + AssertEqualsForm(c5, normalizedSpan1.Slice(0, charsWritten1).ToString()); + AssertEqualsForm(c5, normalizedSpan2.Slice(0, charsWritten2).ToString()); + AssertEqualsForm(c5, normalizedSpan3.Slice(0, charsWritten3).ToString()); + AssertEqualsForm(c5, normalizedSpan4.Slice(0, charsWritten4).ToString()); + AssertEqualsForm(c5, normalizedSpan5.Slice(0, charsWritten5).ToString()); + // c5 is normalized to Form KD Assert.True(c5.IsNormalized(normForm), $"'{c5}' is marked as not normalized with form {normForm}"); + Assert.True(c5.AsSpan().IsNormalized(normForm), $"'{c5}' span is marked as not normalized with form {normForm}"); break; } } diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Extensions.Tests/Normalization/StringNormalizationTests.cs b/src/libraries/System.Runtime/tests/System.Globalization.Extensions.Tests/Normalization/StringNormalizationTests.cs index 1b70a79b6ae6de..bba0ddb088d462 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Extensions.Tests/Normalization/StringNormalizationTests.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Extensions.Tests/Normalization/StringNormalizationTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Linq; using System.Text; using Xunit; using System.Collections.Generic; @@ -21,16 +22,23 @@ public void IsNormalized(string value, NormalizationForm normalizationForm, bool if (normalizationForm == NormalizationForm.FormC) { Assert.Equal(expected, value.IsNormalized()); + Assert.Equal(expected, value.AsSpan().IsNormalized()); } Assert.Equal(expected, value.IsNormalized(normalizationForm)); + Assert.Equal(expected, value.AsSpan().IsNormalized(normalizationForm)); } [Fact] public void IsNormalized_Invalid() { Assert.Throws(() => "\uFB01".IsNormalized((NormalizationForm)10)); - AssertExtensions.Throws("strInput", () => "\uFFFE".IsNormalized()); // Invalid codepoint - AssertExtensions.Throws("strInput", () => "\uD800\uD800".IsNormalized()); // Invalid surrogate pair + Assert.Throws(() => "\uFB01".AsSpan().IsNormalized((NormalizationForm)10)); + + AssertExtensions.Throws("source", () => "\uFFFE".IsNormalized()); // Invalid codepoint + AssertExtensions.Throws("source", () => "\uFFFE".AsSpan().IsNormalized()); // Invalid codepoint + + AssertExtensions.Throws("source", () => "\uD800\uD800".IsNormalized()); // Invalid surrogate pair + AssertExtensions.Throws("source", () => "\uD800\uD800".AsSpan().IsNormalized()); // Invalid surrogate pair } [Fact] @@ -63,20 +71,61 @@ public static IEnumerable NormalizeTestData() [MemberData(nameof(NormalizeTestData))] public void Normalize(string value, NormalizationForm normalizationForm, string expected) { + Span destination = new char[expected.Length + 1]; // NLS sometimes need extra character in the buffer mostly if need to insert the null terminator + int charsWritten; + if (normalizationForm == NormalizationForm.FormC) { Assert.Equal(expected, value.Normalize()); + + Assert.True(value.AsSpan().TryNormalize(destination, out charsWritten)); + Assert.Equal(expected, destination.Slice(0, charsWritten).ToString()); + + if (PlatformDetection.IsNlsGlobalization) + { + // NLS return estimated normalized length that is enough to hold the result but doesn't return the exact length + Assert.True(expected.Length <= value.GetNormalizedLength(), $"Expected: {expected.Length}, Actual: {value.GetNormalizedLength()}"); + } + else + { + // ICU returns the exact normalized length + Assert.Equal(expected.Length, value.AsSpan().GetNormalizedLength()); + } } + Assert.Equal(expected, value.Normalize(normalizationForm)); + + if (expected.Length > 0) + { + Assert.False(value.AsSpan().TryNormalize(destination.Slice(0, expected.Length - 1), out charsWritten, normalizationForm), $"Trying to normalize '{value}' to a buffer of length {expected.Length - 1} succeeded!"); + } + + Assert.True(value.AsSpan().TryNormalize(destination, out charsWritten, normalizationForm), $"Failed to normalize '{value}' to a buffer of length {destination.Length}"); + Assert.Equal(expected, destination.Slice(0, charsWritten).ToString()); + if (PlatformDetection.IsNlsGlobalization) + { + // NLS return estimated normalized length that is enough to hold the result but doesn't return the exact length + Assert.True(expected.Length <= value.AsSpan().GetNormalizedLength(normalizationForm), $"Expected: {expected.Length}, Actual: {value.AsSpan().GetNormalizedLength(normalizationForm)}"); + } + else + { + // ICU returns the exact normalized length + Assert.Equal(expected.Length, value.AsSpan().GetNormalizedLength(normalizationForm)); + } } [Fact] public void Normalize_Invalid() { + char[] destination = new char[100]; Assert.Throws(() => "\uFB01".Normalize((NormalizationForm)7)); + Assert.Throws(() => "\uFB01".AsSpan().TryNormalize(destination.AsSpan(), out int charsWritten, (NormalizationForm)7)); AssertExtensions.Throws("strInput", () => "\uFFFE".Normalize()); // Invalid codepoint + AssertExtensions.Throws("source", () => "\uFFFE".AsSpan().TryNormalize(destination.AsSpan(), out int charsWritten)); // Invalid codepoint + AssertExtensions.Throws("strInput", () => "\uD800\uD800".Normalize()); // Invalid surrogate pair + AssertExtensions.Throws("source", () => "\uD800\uD800".AsSpan().TryNormalize(destination, out int charsWritten)); // Invalid surrogate pair } [Fact] From 511b2e0505dbf99d2827217752de0c1e2babc50a Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Mon, 9 Dec 2024 17:18:09 -0800 Subject: [PATCH 12/70] Fix test under JIT stress (#110538) --- .../Common/tests/System/Reflection/InvokeInterpretedTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libraries/Common/tests/System/Reflection/InvokeInterpretedTests.cs b/src/libraries/Common/tests/System/Reflection/InvokeInterpretedTests.cs index 6370e83d41c8f6..03a876131f4787 100644 --- a/src/libraries/Common/tests/System/Reflection/InvokeInterpretedTests.cs +++ b/src/libraries/Common/tests/System/Reflection/InvokeInterpretedTests.cs @@ -16,7 +16,6 @@ public static void VerifyInvokeIsUsingInterpreter_Method() Exception exInner = ex.InnerException; Assert.Contains("Here", exInner.ToString()); - Assert.Contains("InterpretedInvoke_Method", exInner.ToString()); Assert.DoesNotContain("InvokeStub_TestClassThatThrows", exInner.ToString()); } @@ -29,7 +28,6 @@ public static void VerifyInvokeIsUsingInterpreter_Constructor() Exception exInner = ex.InnerException; Assert.Contains("Here", exInner.ToString()); - Assert.Contains("InterpretedInvoke_Constructor", exInner.ToString()); Assert.DoesNotContain("InvokeStub_TestClassThatThrows", exInner.ToString()); } From 810bad5ac951419217981a79a4d7d94e15cfe47b Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 9 Dec 2024 17:53:32 -0800 Subject: [PATCH 13/70] JIT: extract BBJ_COND to BBJ_ALWAYS profile repair as utility (#110494) JIT: extract BBJ_COND to BBJ_ALWAYS profile repair as utility and call it from a few places. Co-authored-by: Aman Khalid --- src/coreclr/jit/compiler.h | 1 + src/coreclr/jit/fginline.cpp | 96 ++++----------------------- src/coreclr/jit/fgprofile.cpp | 112 ++++++++++++++++++++++++++++++++ src/coreclr/jit/importer.cpp | 60 +---------------- src/coreclr/jit/objectalloc.cpp | 9 +-- 5 files changed, 129 insertions(+), 149 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index c4cce85e431ded..344f36a6e75858 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6545,6 +6545,7 @@ class Compiler } void fgRemoveProfileData(const char* reason); + void fgRepairProfileCondToUncond(BasicBlock* block, FlowEdge* retainedEdge, FlowEdge* removedEdge, int* metric = nullptr); //-------- Insert a statement at the start or end of a basic block -------- diff --git a/src/coreclr/jit/fginline.cpp b/src/coreclr/jit/fginline.cpp index d7abf54dfc3946..ee75f8b13cd774 100644 --- a/src/coreclr/jit/fginline.cpp +++ b/src/coreclr/jit/fginline.cpp @@ -644,98 +644,28 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitorgtUpdateNodeSideEffects(tree); assert((tree->gtFlags & GTF_SIDE_EFFECT) == 0); tree->gtBashToNOP(); - m_madeChanges = true; - FlowEdge* removedEdge = nullptr; + m_madeChanges = true; + FlowEdge* removedEdge = nullptr; + FlowEdge* retainedEdge = nullptr; if (condTree->IsIntegralConst(0)) { - removedEdge = block->GetTrueEdge(); - m_compiler->fgRemoveRefPred(removedEdge); - block->SetKindAndTargetEdge(BBJ_ALWAYS, block->GetFalseEdge()); + removedEdge = block->GetTrueEdge(); + retainedEdge = block->GetFalseEdge(); } else { - removedEdge = block->GetFalseEdge(); - m_compiler->fgRemoveRefPred(removedEdge); - block->SetKindAndTargetEdge(BBJ_ALWAYS, block->GetTrueEdge()); + removedEdge = block->GetFalseEdge(); + retainedEdge = block->GetTrueEdge(); } - // Update profile; make it consistent if possible - // - if (block->hasProfileWeight()) - { - bool repairWasComplete = true; - bool missingProfileData = false; - weight_t const weight = removedEdge->getLikelyWeight(); - - if (weight > 0) - { - // Target block weight will increase. - // - BasicBlock* const target = block->GetTarget(); - - // We may have a profiled inlinee in an unprofiled method - // - if (target->hasProfileWeight()) - { - target->setBBProfileWeight(target->bbWeight + weight); - missingProfileData = true; - } - - // Alternate weight will decrease - // - BasicBlock* const alternate = removedEdge->getDestinationBlock(); - - if (alternate->hasProfileWeight()) - { - weight_t const alternateNewWeight = alternate->bbWeight - weight; - - // If profile weights are consistent, expect at worst a slight underflow. - // - if (m_compiler->fgPgoConsistent && (alternateNewWeight < 0)) - { - assert(m_compiler->fgProfileWeightsEqual(alternateNewWeight, 0)); - } - alternate->setBBProfileWeight(max(0.0, alternateNewWeight)); - } - else - { - missingProfileData = true; - } - - // This will affect profile transitively, so in general - // the profile will become inconsistent. - // - repairWasComplete = false; + m_compiler->fgRemoveRefPred(removedEdge); + block->SetKindAndTargetEdge(BBJ_ALWAYS, retainedEdge); - // But we can check for the special case where the - // block's postdominator is target's target (simple - // if/then/else/join). - // - if (!missingProfileData && target->KindIs(BBJ_ALWAYS)) - { - repairWasComplete = - alternate->KindIs(BBJ_ALWAYS) && alternate->TargetIs(target->GetTarget()); - } - } - - if (missingProfileData) - { - JITDUMP("Profile data could not be locally repaired. Data was missing.\n"); - } - - if (!repairWasComplete) - { - JITDUMP("Profile data could not be locally repaired. Data %s inconsistent.\n", - m_compiler->fgPgoConsistent ? "is now" : "was already"); - - if (m_compiler->fgPgoConsistent) - { - m_compiler->Metrics.ProfileInconsistentInlinerBranchFold++; - m_compiler->fgPgoConsistent = false; - } - } - } + // Update profile, make it consistent if possible. + // + m_compiler->fgRepairProfileCondToUncond(block, retainedEdge, removedEdge, + &m_compiler->Metrics.ProfileInconsistentInlinerBranchFold); } } else diff --git a/src/coreclr/jit/fgprofile.cpp b/src/coreclr/jit/fgprofile.cpp index 4bbcb6ea9b7aa4..18d96b3bac19a8 100644 --- a/src/coreclr/jit/fgprofile.cpp +++ b/src/coreclr/jit/fgprofile.cpp @@ -5063,3 +5063,115 @@ bool Compiler::fgDebugCheckOutgoingProfileData(BasicBlock* block, ProfileChecks } #endif // DEBUG + +//------------------------------------------------------------------------ +// fgRepairProfileCondToUncond: attempt to repair profile after modifying +// a conditinal branch to an unconditional branch. +// +// Arguments: +// block - block that was just altered +// retainedEdge - flow edge that remains +// removedEdge - flow edge that was removed +// metric - [in/out, optional] metric to update if profile becomes inconsistent +// +void Compiler::fgRepairProfileCondToUncond(BasicBlock* block, + FlowEdge* retainedEdge, + FlowEdge* removedEdge, + int* metric /* = nullptr */) +{ + assert(block->KindIs(BBJ_ALWAYS)); + assert(block->GetTargetEdge() == retainedEdge); + + // If block does not have profile data, there's nothing to do. + // + if (!block->hasProfileWeight()) + { + return; + } + + // If the removed edge was not carrying away any profile, there's nothing to do. + // + weight_t const weight = removedEdge->getLikelyWeight(); + + if (weight == 0.0) + { + return; + } + + // If the branch was degenerate, there is nothing to do + // + if (retainedEdge == removedEdge) + { + return; + } + + // This flow graph change will affect profile transitively, so in general + // the profile will become inconsistent. + // + bool repairWasComplete = false; + bool missingProfileData = false; + + // Target block weight will increase. + // + BasicBlock* const target = block->GetTarget(); + + if (target->hasProfileWeight()) + { + target->setBBProfileWeight(target->bbWeight + weight); + } + else + { + missingProfileData = true; + } + + // Alternate weight will decrease + // + BasicBlock* const alternate = removedEdge->getDestinationBlock(); + + if (alternate->hasProfileWeight()) + { + weight_t const alternateNewWeight = alternate->bbWeight - weight; + + // If profile weights are consistent, expect at worst a slight underflow. + // + if (fgPgoConsistent && (alternateNewWeight < 0.0)) + { + assert(fgProfileWeightsEqual(alternateNewWeight, 0.0)); + } + alternate->setBBProfileWeight(max(0.0, alternateNewWeight)); + } + else + { + missingProfileData = true; + } + + // Check for the special case where the block's postdominator + // is target's target (simple if/then/else/join). + // + // TODO: try a bit harder to find a postdominator, if it's "nearby" + // + if (!missingProfileData && target->KindIs(BBJ_ALWAYS)) + { + repairWasComplete = alternate->KindIs(BBJ_ALWAYS) && (alternate->GetTarget() == target->GetTarget()); + } + + if (missingProfileData) + { + JITDUMP("Profile data could not be locally repaired. Data was missing.\n"); + } + + if (!repairWasComplete) + { + JITDUMP("Profile data could not be locally repaired. Data %s inconsistent.\n", + fgPgoConsistent ? "is now" : "was already"); + + if (fgPgoConsistent) + { + if (metric != nullptr) + { + *metric++; + } + fgPgoConsistent = false; + } + } +} diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 564d3560109859..334cf5e411aabe 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -7613,64 +7613,8 @@ void Compiler::impImportBlockCode(BasicBlock* block) fgRemoveRefPred(removedEdge); block->SetKindAndTargetEdge(BBJ_ALWAYS, retainedEdge); Metrics.ImporterBranchFold++; - - // If we removed an edge carrying profile, try to do a local repair. - // - if (block->hasProfileWeight()) - { - bool repairWasComplete = true; - weight_t const weight = removedEdge->getLikelyWeight(); - - if (weight > 0) - { - // Target block weight will increase. - // - BasicBlock* const target = block->GetTarget(); - assert(target->hasProfileWeight()); - target->setBBProfileWeight(target->bbWeight + weight); - - // Alternate weight will decrease - // - BasicBlock* const alternate = removedEdge->getDestinationBlock(); - assert(alternate->hasProfileWeight()); - weight_t const alternateNewWeight = alternate->bbWeight - weight; - - // If profile weights are consistent, expect at worst a slight underflow. - // - if (fgPgoConsistent && (alternateNewWeight < 0)) - { - assert(fgProfileWeightsEqual(alternateNewWeight, 0)); - } - alternate->setBBProfileWeight(max(0.0, alternateNewWeight)); - - // This will affect profile transitively, so in general - // the profile will become inconsistent. - // - repairWasComplete = false; - - // But we can check for the special case where the - // block's postdominator is target's target (simple - // if/then/else/join). - // - if (target->KindIs(BBJ_ALWAYS)) - { - repairWasComplete = alternate->KindIs(BBJ_ALWAYS) && - (alternate->GetTarget() == target->GetTarget()); - } - } - - if (!repairWasComplete) - { - JITDUMP("Profile data could not be locally repaired. Data %s inconsistent.\n", - fgPgoConsistent ? "is now" : "was already"); - - if (fgPgoConsistent) - { - Metrics.ProfileInconsistentImporterBranchFold++; - fgPgoConsistent = false; - } - } - } + fgRepairProfileCondToUncond(block, retainedEdge, removedEdge, + &Metrics.ProfileInconsistentImporterBranchFold); } if (!op1->OperIs(GT_CNS_INT)) diff --git a/src/coreclr/jit/objectalloc.cpp b/src/coreclr/jit/objectalloc.cpp index e8c85d7ca48bcc..29fa2bce242768 100644 --- a/src/coreclr/jit/objectalloc.cpp +++ b/src/coreclr/jit/objectalloc.cpp @@ -669,14 +669,7 @@ unsigned int ObjectAllocator::MorphAllocObjNodeIntoStackAlloc( BasicBlock* removedBlock = removedEdge->getDestinationBlock(); comp->fgRemoveRefPred(removedEdge); predBlock->SetKindAndTargetEdge(BBJ_ALWAYS, keptEdge); - - if (predBlock->hasProfileWeight()) - { - block->setBBProfileWeight(predBlock->bbWeight); - JITDUMP("Profile weight into " FMT_BB " needs to be propagated to successors. Profile %s inconsistent.\n", - block->bbNum, comp->fgPgoConsistent ? "is now" : "was already"); - comp->fgPgoConsistent = false; - } + comp->fgRepairProfileCondToUncond(predBlock, keptEdge, removedEdge); // Just lop off the JTRUE, the rest can clean up later // (eg may have side effects) From cf92a649d01549a9128c3365f81d4e2294d3926d Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Tue, 10 Dec 2024 02:58:05 +0100 Subject: [PATCH 14/70] Speed up surrogate validation in HttpUtility (#110478) * Speed up surrogate validation in HttpUtility --- .../src/System.Web.HttpUtility.csproj | 10 +-- .../src/System/Web/Util/HttpEncoder.cs | 34 ++++++++- .../System/Web/Util/Utf16StringValidator.cs | 72 ------------------- 3 files changed, 34 insertions(+), 82 deletions(-) delete mode 100644 src/libraries/System.Web.HttpUtility/src/System/Web/Util/Utf16StringValidator.cs diff --git a/src/libraries/System.Web.HttpUtility/src/System.Web.HttpUtility.csproj b/src/libraries/System.Web.HttpUtility/src/System.Web.HttpUtility.csproj index 73aa6d15930f72..6795e3db21c61b 100644 --- a/src/libraries/System.Web.HttpUtility/src/System.Web.HttpUtility.csproj +++ b/src/libraries/System.Web.HttpUtility/src/System.Web.HttpUtility.csproj @@ -11,13 +11,9 @@ - - - - + + + diff --git a/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoder.cs b/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoder.cs index 7bdb43b1e91cbc..46227356574632 100644 --- a/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoder.cs +++ b/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoder.cs @@ -299,7 +299,7 @@ internal static byte[] UrlDecode(ReadOnlySpan bytes) helper.AddByte(b); } - return Utf16StringValidator.ValidateString(helper.GetString()); + return helper.GetString(); } [return: NotNullIfNotNull(nameof(value))] @@ -383,7 +383,7 @@ internal static string UrlDecode(ReadOnlySpan value, Encoding encoding) } } - return Utf16StringValidator.ValidateString(helper.GetString()); + return helper.GetString(); } [return: NotNullIfNotNull(nameof(bytes))] @@ -674,7 +674,35 @@ internal string GetString() FlushBytes(); } - return _charBuffer.Slice(0, _numChars).ToString(); + Span chars = _charBuffer.Slice(0, _numChars); + + const char HIGH_SURROGATE_START = '\ud800'; + const char LOW_SURROGATE_END = '\udfff'; + + // Replace any invalid surrogate chars. + int idxOfFirstSurrogate = chars.IndexOfAnyInRange(HIGH_SURROGATE_START, LOW_SURROGATE_END); + for (int i = idxOfFirstSurrogate; (uint)i < (uint)chars.Length; i++) + { + if (char.IsHighSurrogate(chars[i])) + { + if ((uint)(i + 1) >= (uint)chars.Length || !char.IsLowSurrogate(chars[i + 1])) + { + // High surrogate not followed by a low surrogate. + chars[i] = (char)Rune.ReplacementChar.Value; + } + else + { + i++; + } + } + else if (char.IsLowSurrogate(chars[i])) + { + // Low surrogate not preceded by a high surrogate. + chars[i] = (char)Rune.ReplacementChar.Value; + } + } + + return chars.ToString(); } } } diff --git a/src/libraries/System.Web.HttpUtility/src/System/Web/Util/Utf16StringValidator.cs b/src/libraries/System.Web.HttpUtility/src/System/Web/Util/Utf16StringValidator.cs deleted file mode 100644 index a9882aa14767ce..00000000000000 --- a/src/libraries/System.Web.HttpUtility/src/System/Web/Util/Utf16StringValidator.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Web.Util -{ - internal static class Utf16StringValidator - { - private const char UnicodeReplacementChar = '\uFFFD'; - - internal static string ValidateString(string input) - { - if (string.IsNullOrEmpty(input)) - { - return input; - } - - // locate the first surrogate character - int idxOfFirstSurrogate = -1; - for (int i = 0; i < input.Length; i++) - { - if (char.IsSurrogate(input[i])) - { - idxOfFirstSurrogate = i; - break; - } - } - - // fast case: no surrogates = return input string - if (idxOfFirstSurrogate < 0) - { - return input; - } - - // slow case: surrogates exist, so we need to validate them - return string.Create(input.Length, (input, idxOfFirstSurrogate), (chars, state) => - { - state.input.CopyTo(chars); - for (int i = state.idxOfFirstSurrogate; i < chars.Length; i++) - { - char thisChar = chars[i]; - - // If this character is a low surrogate, then it was not preceded by - // a high surrogate, so we'll replace it. - if (char.IsLowSurrogate(thisChar)) - { - chars[i] = UnicodeReplacementChar; - continue; - } - - if (char.IsHighSurrogate(thisChar)) - { - // If this character is a high surrogate and it is followed by a - // low surrogate, allow both to remain. - if (i + 1 < chars.Length && char.IsLowSurrogate(chars[i + 1])) - { - i++; // skip the low surrogate also - continue; - } - - // If this character is a high surrogate and it is not followed - // by a low surrogate, replace it. - chars[i] = UnicodeReplacementChar; - continue; - } - - // Otherwise, this is a non-surrogate character and just move to the - // next character. - } - }); - } - } -} From 69229cde8966a6a4be3c7243c514f8a3c35e94f8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 9 Dec 2024 18:00:40 -0800 Subject: [PATCH 15/70] Don't wait for finalizers in 'IReferenceTrackerHost::ReleaseDisconnectedReferenceSources' (#110551) This PR updates the IReferenceTrackerHost::ReleaseDisconnectedReferenceSources implementation for CoreCLR and NativeAOT to match what .NET Native was doing, and not wait for finalizers, to avoid deadlocks in ASTA scenarios (UWP). Finalizers will just continue running normally, and if the process is suspended (which can only happen on UWP anyway), they'll resume when the process is resumed later on. Worst case scenario this would only cause a non-optimal memory use cleanup before suspension (which is better than a deadlock anyway). Can be revisited and improved for .NET 10 if needed. Contributes to #109538 --- src/coreclr/interop/trackerobjectmanager.cpp | 5 ++++- .../InteropServices/ComWrappers.NativeAot.cs | 13 ++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/coreclr/interop/trackerobjectmanager.cpp b/src/coreclr/interop/trackerobjectmanager.cpp index d4302054baedba..0df78164906dcc 100644 --- a/src/coreclr/interop/trackerobjectmanager.cpp +++ b/src/coreclr/interop/trackerobjectmanager.cpp @@ -84,7 +84,10 @@ namespace STDMETHODIMP HostServices::ReleaseDisconnectedReferenceSources() { - return InteropLibImports::WaitForRuntimeFinalizerForExternal(); + // We'd like to call InteropLibImports::WaitForRuntimeFinalizerForExternal() here, but this could + // lead to deadlock if the finalizer thread is trying to get back to this thread, because we are + // not pumping anymore. Disable this for now. See: https://github.com/dotnet/runtime/issues/109538. + return S_OK; } STDMETHODIMP HostServices::NotifyEndOfReferenceTrackingOnThread() diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.NativeAot.cs index cc33e85d73917c..b4d70b66c2a937 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.NativeAot.cs @@ -1418,15 +1418,10 @@ internal static unsafe int IReferenceTrackerHost_DisconnectUnusedReferenceSource [UnmanagedCallersOnly] internal static unsafe int IReferenceTrackerHost_ReleaseDisconnectedReferenceSources(IntPtr pThis) { - try - { - GC.WaitForPendingFinalizers(); - return HResults.S_OK; - } - catch (Exception e) - { - return Marshal.GetHRForException(e); - } + // We'd like to call GC.WaitForPendingFinalizers() here, but this could lead to deadlock + // if the finalizer thread is trying to get back to this thread, because we are not pumping + // anymore. Disable this for now. See: https://github.com/dotnet/runtime/issues/109538. + return HResults.S_OK; } [UnmanagedCallersOnly] From dede4f9256526e4bf7733d208f9ad25e7c656bce Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 9 Dec 2024 20:36:11 -0800 Subject: [PATCH 16/70] [cdac] Fix calculation of `MethodDesc` optional slot addresses (#110491) The cDAC was assuming that the additional data after a MethodDesc were each just pointers. That is only true for the non-vtable slot and native code slot, not method impl. This change includes the size of those additional slots in the data descriptor and uses them when computing the addresses those optional slots. As part of this, I pulled out shared helpers around dealing with optional slots for use by `MethodValidation` and `RuntimeTypeSystem_1`. --- .../debug/runtimeinfo/datadescriptor.h | 12 ++ src/coreclr/vm/method.hpp | 4 +- .../DataType.cs | 3 + .../Contracts/RuntimeTypeSystem_1.cs | 93 ++-------- .../MethodDescFlags_1.cs | 9 +- .../MethodDescOptionalSlots.cs | 92 ++++++++++ .../MethodValidation.cs | 96 +++-------- .../cdacreader/tests/MethodDescTests.cs | 159 ++++++++++++------ .../MockDescriptors.MethodDescriptors.cs | 38 +++-- 9 files changed, 271 insertions(+), 235 deletions(-) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 1fa37c6ba50d2e..b3894f4efa71d1 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -342,6 +342,18 @@ CDAC_TYPE_FIELD(MethodDescChunk, /*uint8*/, Count, cdac_data::C CDAC_TYPE_FIELD(MethodDescChunk, /*uint16*/, FlagsAndTokenRange, cdac_data::FlagsAndTokenRange) CDAC_TYPE_END(MethodDescChunk) +CDAC_TYPE_BEGIN(NonVtableSlot) +CDAC_TYPE_SIZE(sizeof(MethodDesc::NonVtableSlot)) +CDAC_TYPE_END(NonVtableSlot) + +CDAC_TYPE_BEGIN(MethodImpl) +CDAC_TYPE_SIZE(sizeof(MethodImpl)) +CDAC_TYPE_END(MethodImpl) + +CDAC_TYPE_BEGIN(NativeCodeSlot) +CDAC_TYPE_SIZE(sizeof(MethodDesc::NativeCodeSlot)) +CDAC_TYPE_END(NativeCodeSlot) + CDAC_TYPE_BEGIN(InstantiatedMethodDesc) CDAC_TYPE_SIZE(sizeof(InstantiatedMethodDesc)) CDAC_TYPE_FIELD(InstantiatedMethodDesc, /*pointer*/, PerInstInfo, cdac_data::PerInstInfo) diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index c4e390130cc348..44d03a2ffd3c16 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1853,10 +1853,8 @@ class MethodDesc // // Optional MethodDesc slots appear after the end of base MethodDesc in this order: // - - // class MethodImpl; // Present if HasMethodImplSlot() is true - typedef PCODE NonVtableSlot; // Present if HasNonVtableSlot() is true + // class MethodImpl; // Present if HasMethodImplSlot() is true typedef PCODE NativeCodeSlot; // Present if HasNativeCodeSlot() is true // Stub Dispatch code diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 98190f5308a8fa..ddcdfbac5789a9 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -73,4 +73,7 @@ public enum DataType HashMap, Bucket, UnwindInfo, + NonVtableSlot, + MethodImpl, + NativeCodeSlot, } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 423a2ef2ed9d0e..c29930d49fced7 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -143,45 +143,14 @@ private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.Metho public TargetPointer CodeData => _desc.CodeData; public bool IsIL => Classification == MethodClassification.IL || Classification == MethodClassification.Instantiated; - public bool HasNativeCodeSlot => HasFlags(MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot); - internal bool HasNonVtableSlot => HasFlags(MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot); - internal bool HasMethodImpl => HasFlags(MethodDescFlags_1.MethodDescFlags.HasMethodImpl); + internal bool HasNonVtableSlot => MethodDescOptionalSlots.HasNonVtableSlot(_desc.Flags); + internal bool HasNativeCodeSlot => MethodDescOptionalSlots.HasNativeCodeSlot(_desc.Flags); internal bool HasStableEntryPoint => HasFlags(MethodDescFlags_1.MethodDescFlags3.HasStableEntryPoint); internal bool HasPrecode => HasFlags(MethodDescFlags_1.MethodDescFlags3.HasPrecode); - #region Additional Pointers - private int AdditionalPointersHelper(MethodDescFlags_1.MethodDescFlags extraFlags) - => int.PopCount(_desc.Flags & (ushort)extraFlags); - - // non-vtable slot, native code slot and MethodImpl slots are stored after the MethodDesc itself, packed tightly - // in the order: [non-vtable; methhod impl; native code]. - internal int NonVtableSlotIndex => HasNonVtableSlot ? 0 : throw new InvalidOperationException("no non-vtable slot"); - internal int MethodImplIndex - { - get - { - if (!HasMethodImpl) - { - throw new InvalidOperationException("no method impl slot"); - } - return AdditionalPointersHelper(MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot); - } - } - internal int NativeCodeSlotIndex - { - get - { - if (!HasNativeCodeSlot) - { - throw new InvalidOperationException("no native code slot"); - } - return AdditionalPointersHelper(MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot | MethodDescFlags_1.MethodDescFlags.HasMethodImpl); - } - } - - internal int AdditionalPointersCount => AdditionalPointersHelper(MethodDescFlags_1.MethodDescFlags.MethodDescAdditionalPointersMask); - #endregion Additional Pointers + internal TargetPointer GetAddressOfNonVtableSlot() => MethodDescOptionalSlots.GetAddressOfNonVtableSlot(Address, Classification, _desc.Flags, _target); + internal TargetPointer GetAddressOfNativeCodeSlot() => MethodDescOptionalSlots.GetAddressOfNativeCodeSlot(Address, Classification, _desc.Flags, _target); internal bool IsLoaderModuleAttachedToChunk => HasFlags(MethodDescChunkFlags.LoaderModuleAttachedToChunk); @@ -994,48 +963,10 @@ bool IRuntimeTypeSystem.HasNativeCodeSlot(MethodDescHandle methodDesc) return md.HasNativeCodeSlot; } - internal static DataType GetMethodClassificationDataType(MethodClassification classification) - => classification switch - { - MethodClassification.IL => DataType.MethodDesc, - MethodClassification.FCall => throw new NotImplementedException(), //TODO[cdac]: - MethodClassification.PInvoke => throw new NotImplementedException(), //TODO[cdac]: - MethodClassification.EEImpl => throw new NotImplementedException(), //TODO[cdac]: - MethodClassification.Array => throw new NotImplementedException(), //TODO[cdac]: - MethodClassification.Instantiated => DataType.InstantiatedMethodDesc, - MethodClassification.ComInterop => throw new NotImplementedException(), //TODO[cdac]: - MethodClassification.Dynamic => DataType.DynamicMethodDesc, - _ => throw new InvalidOperationException($"Unexpected method classification 0x{classification:x2} for MethodDesc") - }; - - private uint MethodDescAdditionalPointersOffset(MethodDesc md) - { - // See MethodDesc::GetBaseSize and s_ClassificationSizeTable - // sizeof(MethodDesc), mcIL - // sizeof(FCallMethodDesc), mcFCall - // sizeof(NDirectMethodDesc), mcPInvoke - // sizeof(EEImplMethodDesc), mcEEImpl - // sizeof(ArrayMethodDesc), mcArray - // sizeof(InstantiatedMethodDesc), mcInstantiated - // sizeof(CLRToCOMCallMethodDesc), mcComInterOp - // sizeof(DynamicMethodDesc) mcDynamic - MethodClassification cls = md.Classification; - DataType type = GetMethodClassificationDataType(cls); - return _target.GetTypeInfo(type).Size ?? throw new InvalidOperationException($"size of MethodDesc not known"); - } - TargetPointer IRuntimeTypeSystem.GetAddressOfNativeCodeSlot(MethodDescHandle methodDesc) { MethodDesc md = _methodDescs[methodDesc.Address]; - uint offset = MethodDescAdditionalPointersOffset(md); - offset += (uint)(_target.PointerSize * md.NativeCodeSlotIndex); - return methodDesc.Address + offset; - } - private TargetPointer GetAddressOfNonVtableSlot(TargetPointer methodDescPointer, MethodDesc md) - { - uint offset = MethodDescAdditionalPointersOffset(md); - offset += (uint)(_target.PointerSize * md.NonVtableSlotIndex); - return methodDescPointer.Value + offset; + return md.GetAddressOfNativeCodeSlot(); } TargetCodePointer IRuntimeTypeSystem.GetNativeCode(MethodDescHandle methodDescHandle) @@ -1047,7 +978,7 @@ TargetCodePointer IRuntimeTypeSystem.GetNativeCode(MethodDescHandle methodDescHa // When profiler is enabled, profiler may ask to rejit a code even though we // we have ngen code for this MethodDesc. (See MethodDesc::DoPrestub). // This means that *ppCode is not stable. It can turn from non-zero to zero. - TargetPointer ppCode = ((IRuntimeTypeSystem)this).GetAddressOfNativeCodeSlot(methodDescHandle); + TargetPointer ppCode = md.GetAddressOfNativeCodeSlot(); TargetCodePointer pCode = _target.ReadCodePointer(ppCode); return CodePointerUtils.CodePointerFromAddress(pCode.AsTargetPointer, _target); } @@ -1055,22 +986,22 @@ TargetCodePointer IRuntimeTypeSystem.GetNativeCode(MethodDescHandle methodDescHa if (!md.HasStableEntryPoint || md.HasPrecode) return TargetCodePointer.Null; - return GetStableEntryPoint(methodDescHandle.Address, md); + return GetStableEntryPoint(md); } - private TargetCodePointer GetStableEntryPoint(TargetPointer methodDescAddress, MethodDesc md) + private TargetCodePointer GetStableEntryPoint(MethodDesc md) { - // TODO(cdac): _ASSERTE(HasStableEntryPoint()); // TODO(cdac): _ASSERTE(!IsVersionableWithVtableSlotBackpatch()); + Debug.Assert(md.HasStableEntryPoint); - return GetMethodEntryPointIfExists(methodDescAddress, md); + return GetMethodEntryPointIfExists(md); } - private TargetCodePointer GetMethodEntryPointIfExists(TargetPointer methodDescAddress, MethodDesc md) + private TargetCodePointer GetMethodEntryPointIfExists(MethodDesc md) { if (md.HasNonVtableSlot) { - TargetPointer pSlot = GetAddressOfNonVtableSlot(methodDescAddress, md); + TargetPointer pSlot = md.GetAddressOfNonVtableSlot(); return _target.ReadCodePointer(pSlot); } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs index b4e33e29f38fb7..89988f355a39ad 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs @@ -10,14 +10,12 @@ internal static class MethodDescFlags_1 internal enum MethodDescFlags : ushort { ClassificationMask = 0x7, - #region Additional pointers - // The below flags each imply that there's an extra pointer-sized piece of data after the MethodDesc in the MethodDescChunk + #region Optional slots + // The below flags each imply that there's an extra pointer-size-aligned piece of data after the MethodDesc in the MethodDescChunk HasNonVtableSlot = 0x0008, HasMethodImpl = 0x0010, HasNativeCodeSlot = 0x0020, - // Mask for the above flags - MethodDescAdditionalPointersMask = 0x0038, - #endregion Additional pointers + #endregion Optional slots } [Flags] @@ -35,5 +33,4 @@ internal enum MethodDescEntryPointFlags : byte { TemporaryEntryPointAssigned = 0x04, } - } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs new file mode 100644 index 00000000000000..4405ce8fc0ff71 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; + +namespace Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; + +// Non-vtable slot, native code slot, and MethodImpl slots are stored after the MethodDesc itself, packed tightly +// in the order: [non-vtable; method impl; native code]. +internal static class MethodDescOptionalSlots +{ + internal static bool HasNonVtableSlot(ushort flags) + => (flags & (ushort)MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot) != 0; + + internal static bool HasMethodImpl(ushort flags) + => (flags & (ushort)MethodDescFlags_1.MethodDescFlags.HasMethodImpl) != 0; + + internal static bool HasNativeCodeSlot(ushort flags) + => (flags & (ushort)MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot) != 0; + + internal static TargetPointer GetAddressOfNonVtableSlot(TargetPointer methodDesc, MethodClassification classification, ushort flags, Target target) + { + uint offset = StartOffset(classification, target); + offset += NonVtableSlotOffset(flags); + return methodDesc + offset; + } + + internal static TargetPointer GetAddressOfNativeCodeSlot(TargetPointer methodDesc, MethodClassification classification, ushort flags, Target target) + { + uint offset = StartOffset(classification, target); + offset += NativeCodeSlotOffset(flags, target); + return methodDesc + offset; + } + + // Offset from the MethodDesc address to the start of its optional slots + private static uint StartOffset(MethodClassification classification, Target target) + { + // See MethodDesc::GetBaseSize and s_ClassificationSizeTable + // sizeof(MethodDesc), mcIL + // sizeof(FCallMethodDesc), mcFCall + // sizeof(NDirectMethodDesc), mcPInvoke + // sizeof(EEImplMethodDesc), mcEEImpl + // sizeof(ArrayMethodDesc), mcArray + // sizeof(InstantiatedMethodDesc), mcInstantiated + // sizeof(CLRToCOMCallMethodDesc), mcComInterOp + // sizeof(DynamicMethodDesc) mcDynamic + DataType type = classification switch + { + MethodClassification.IL => DataType.MethodDesc, + MethodClassification.FCall => throw new NotImplementedException(), //TODO[cdac]: + MethodClassification.PInvoke => throw new NotImplementedException(), //TODO[cdac]: + MethodClassification.EEImpl => throw new NotImplementedException(), //TODO[cdac]: + MethodClassification.Array => throw new NotImplementedException(), //TODO[cdac]: + MethodClassification.Instantiated => DataType.InstantiatedMethodDesc, + MethodClassification.ComInterop => throw new NotImplementedException(), //TODO[cdac]: + MethodClassification.Dynamic => DataType.DynamicMethodDesc, + _ => throw new InvalidOperationException($"Unexpected method classification 0x{classification:x2} for MethodDesc") + }; + return target.GetTypeInfo(type).Size ?? throw new InvalidOperationException($"size of MethodDesc not known"); + } + + // Offsets are from the start of optional slots data (so right after the MethodDesc), obtained via StartOffset + private static uint NonVtableSlotOffset(ushort flags) + { + if (!HasNonVtableSlot(flags)) + throw new InvalidOperationException("no non-vtable slot"); + + return 0u; + } + + private static uint MethodImplOffset(ushort flags, Target target) + { + if (!HasMethodImpl(flags)) + throw new InvalidOperationException("no method impl slot"); + + return HasNonVtableSlot(flags) ? (uint)target.PointerSize : 0; + } + + private static uint NativeCodeSlotOffset(ushort flags, Target target) + { + if (!HasNativeCodeSlot(flags)) + throw new InvalidOperationException("no native code slot"); + + uint offset = 0; + if (HasNonVtableSlot(flags)) + offset += target.GetTypeInfo(DataType.NonVtableSlot).Size!.Value; + + if (HasMethodImpl(flags)) + offset += target.GetTypeInfo(DataType.MethodImpl).Size!.Value; + + return offset; + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodValidation.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodValidation.cs index b167270158feb5..e0a463d9eb78dd 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodValidation.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodValidation.cs @@ -51,11 +51,16 @@ internal struct NonValidatedMethodDesc private readonly Target _target; private readonly Data.MethodDesc _desc; private readonly Data.MethodDescChunk _chunk; - internal NonValidatedMethodDesc(Target target, Data.MethodDesc desc, Data.MethodDescChunk chunk) + + internal TargetPointer Address { get; init; } + + internal NonValidatedMethodDesc(Target target, TargetPointer methodDescAddr, Data.MethodDesc desc, Data.MethodDescChunk chunk) { _target = target; _desc = desc; _chunk = chunk; + + Address = methodDescAddr; } private bool HasFlags(MethodDescFlags_1.MethodDescFlags flag) => (_desc.Flags & (ushort)flag) != 0; @@ -66,9 +71,8 @@ internal NonValidatedMethodDesc(Target target, Data.MethodDesc desc, Data.Method internal byte ChunkIndex => _desc.ChunkIndex; internal TargetPointer MethodTable => _chunk.MethodTable; internal ushort Slot => _desc.Slot; - internal bool HasNonVtableSlot => HasFlags(MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot); - internal bool HasMethodImpl => HasFlags(MethodDescFlags_1.MethodDescFlags.HasMethodImpl); - internal bool HasNativeCodeSlot => HasFlags(MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot); + internal bool HasNonVtableSlot => MethodDescOptionalSlots.HasNonVtableSlot(_desc.Flags); + internal bool HasNativeCodeSlot => MethodDescOptionalSlots.HasNativeCodeSlot(_desc.Flags); internal bool TemporaryEntryPointAssigned => HasFlags(MethodDescFlags_1.MethodDescEntryPointFlags.TemporaryEntryPointAssigned); @@ -77,38 +81,8 @@ internal NonValidatedMethodDesc(Target target, Data.MethodDesc desc, Data.Method internal MethodClassification Classification => (MethodClassification)(_desc.Flags & (ushort)MethodDescFlags_1.MethodDescFlags.ClassificationMask); internal bool IsFCall => Classification == MethodClassification.FCall; - #region Additional Pointers - private int AdditionalPointersHelper(MethodDescFlags_1.MethodDescFlags extraFlags) - => int.PopCount(_desc.Flags & (ushort)extraFlags); - - // non-vtable slot, native code slot and MethodImpl slots are stored after the MethodDesc itself, packed tightly - // in the order: [non-vtable; methhod impl; native code]. - internal int NonVtableSlotIndex => HasNonVtableSlot ? 0 : throw new InvalidOperationException("no non-vtable slot"); - internal int MethodImplIndex - { - get - { - if (!HasMethodImpl) - { - throw new InvalidOperationException("no method impl slot"); - } - return AdditionalPointersHelper(MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot); - } - } - internal int NativeCodeSlotIndex - { - get - { - if (!HasNativeCodeSlot) - { - throw new InvalidOperationException("no native code slot"); - } - return AdditionalPointersHelper(MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot | MethodDescFlags_1.MethodDescFlags.HasMethodImpl); - } - } - - internal int AdditionalPointersCount => AdditionalPointersHelper(MethodDescFlags_1.MethodDescFlags.MethodDescAdditionalPointersMask); - #endregion Additional Pointers + internal TargetPointer GetAddressOfNonVtableSlot() => MethodDescOptionalSlots.GetAddressOfNonVtableSlot(Address, Classification, _desc.Flags, _target); + internal TargetPointer GetAddressOfNativeCodeSlot() => MethodDescOptionalSlots.GetAddressOfNativeCodeSlot(Address, Classification, _desc.Flags, _target); internal bool HasStableEntryPoint => HasFlags(MethodDescFlags_1.MethodDescFlags3.HasStableEntryPoint); internal bool HasPrecode => HasFlags(MethodDescFlags_1.MethodDescFlags3.HasPrecode); @@ -140,7 +114,7 @@ private NonValidatedMethodDesc GetMethodDescThrowing(TargetPointer methodDescPoi // we bypass the target data cache here because we don't want to cache non-validated data Data.MethodDesc desc = new Data.MethodDesc(_target, methodDescPointer); Data.MethodDescChunk chunk = GetMethodDescChunkThrowing(methodDescPointer, desc, out methodDescChunkPointer); - return new NonValidatedMethodDesc(_target, desc, chunk); + return new NonValidatedMethodDesc(_target, methodDescPointer, desc, chunk); } private TargetCodePointer GetTemporaryEntryPointIfExists(NonValidatedMethodDesc umd) @@ -153,21 +127,7 @@ private TargetCodePointer GetTemporaryEntryPointIfExists(NonValidatedMethodDesc return codeData.TemporaryEntryPoint; } - private TargetPointer GetAddrOfNativeCodeSlot(TargetPointer methodDescPointer, NonValidatedMethodDesc umd) - { - uint offset = MethodDescAdditionalPointersOffset(umd); - offset += (uint)(_target.PointerSize * umd.NativeCodeSlotIndex); - return methodDescPointer.Value + offset; - } - - private TargetPointer GetAddressOfNonVtableSlot(TargetPointer methodDescPointer, NonValidatedMethodDesc umd) - { - uint offset = MethodDescAdditionalPointersOffset(umd); - offset += (uint)(_target.PointerSize * umd.NonVtableSlotIndex); - return methodDescPointer.Value + offset; - } - - private TargetCodePointer GetCodePointer(TargetPointer methodDescPointer, NonValidatedMethodDesc umd) + private TargetCodePointer GetCodePointer(NonValidatedMethodDesc umd) { // TODO(cdac): _ASSERTE(!IsDefaultInterfaceMethod() || HasNativeCodeSlot()); if (umd.HasNativeCodeSlot) @@ -175,7 +135,7 @@ private TargetCodePointer GetCodePointer(TargetPointer methodDescPointer, NonVal // When profiler is enabled, profiler may ask to rejit a code even though we // we have ngen code for this MethodDesc. (See MethodDesc::DoPrestub). // This means that *ppCode is not stable. It can turn from non-zero to zero. - TargetPointer ppCode = GetAddrOfNativeCodeSlot(methodDescPointer, umd); + TargetPointer ppCode = umd.GetAddressOfNativeCodeSlot(); TargetCodePointer pCode = _target.ReadCodePointer(ppCode); return CodePointerUtils.CodePointerFromAddress(pCode.AsTargetPointer, _target); @@ -184,23 +144,23 @@ private TargetCodePointer GetCodePointer(TargetPointer methodDescPointer, NonVal if (!umd.HasStableEntryPoint || umd.HasPrecode) return TargetCodePointer.Null; - return GetStableEntryPoint(methodDescPointer, umd); + return GetStableEntryPoint(umd); } - private TargetCodePointer GetStableEntryPoint(TargetPointer methodDescPointer, NonValidatedMethodDesc umd) + private TargetCodePointer GetStableEntryPoint(NonValidatedMethodDesc umd) { Debug.Assert(umd.HasStableEntryPoint); // TODO(cdac): _ASSERTE(!IsVersionableWithVtableSlotBackpatch()); - return GetMethodEntryPointIfExists(methodDescPointer, umd); + return GetMethodEntryPointIfExists(umd); } - private TargetCodePointer GetMethodEntryPointIfExists(TargetPointer methodDescAddress, NonValidatedMethodDesc umd) + private TargetCodePointer GetMethodEntryPointIfExists(NonValidatedMethodDesc umd) { if (umd.HasNonVtableSlot) { - TargetPointer pSlot = GetAddressOfNonVtableSlot(methodDescAddress, umd); + TargetPointer pSlot = umd.GetAddressOfNonVtableSlot(); return _target.ReadCodePointer(pSlot); } @@ -211,21 +171,7 @@ private TargetCodePointer GetMethodEntryPointIfExists(TargetPointer methodDescAd return _target.ReadCodePointer(addrOfSlot); } - private uint MethodDescAdditionalPointersOffset(NonValidatedMethodDesc umd) - { - MethodClassification cls = umd.Classification; - DataType type = RuntimeTypeSystem_1.GetMethodClassificationDataType(cls); - return _target.GetTypeInfo(type).Size ?? throw new InvalidOperationException("size of MethodDesc not known"); - } - - internal uint GetMethodDescBaseSize(NonValidatedMethodDesc umd) - { - uint baseSize = MethodDescAdditionalPointersOffset(umd); - baseSize += (uint)(_target.PointerSize * umd.AdditionalPointersCount); - return baseSize; - } - - private bool HasNativeCode(TargetPointer methodDescPointer, NonValidatedMethodDesc umd) => GetCodePointer(methodDescPointer, umd) != TargetCodePointer.Null; + private bool HasNativeCode(NonValidatedMethodDesc umd) => GetCodePointer(umd) != TargetCodePointer.Null; internal bool ValidateMethodDescPointer(TargetPointer methodDescPointer, [NotNullWhen(true)] out TargetPointer methodDescChunkPointer) { @@ -257,9 +203,9 @@ internal bool ValidateMethodDescPointer(TargetPointer methodDescPointer, [NotNul } } - if (HasNativeCode(methodDescPointer, umd) && !umd.IsFCall) + if (HasNativeCode(umd) && !umd.IsFCall) { - TargetCodePointer jitCodeAddr = GetCodePointer(methodDescPointer, umd); + TargetCodePointer jitCodeAddr = GetCodePointer(umd); Contracts.IExecutionManager executionManager = _target.Contracts.ExecutionManager; CodeBlockHandle? codeInfo = executionManager.GetCodeBlockHandle(jitCodeAddr); if (!codeInfo.HasValue) diff --git a/src/native/managed/cdacreader/tests/MethodDescTests.cs b/src/native/managed/cdacreader/tests/MethodDescTests.cs index 7499eefce1019e..3157dcfaa535b6 100644 --- a/src/native/managed/cdacreader/tests/MethodDescTests.cs +++ b/src/native/managed/cdacreader/tests/MethodDescTests.cs @@ -1,8 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; +using System.Collections.Generic; using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; using Moq; using Xunit; @@ -10,80 +11,128 @@ namespace Microsoft.Diagnostics.DataContractReader.Tests; public class MethodDescTests { - private static void MethodDescHelper(MockTarget.Architecture arch, Action configure, Action testCase) + private static Target CreateTarget(MockDescriptors.MethodDescriptors methodDescBuilder) { - TargetTestHelpers targetTestHelpers = new(arch); - - MockMemorySpace.Builder builder = new(targetTestHelpers); - MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder); - MockDescriptors.Loader loaderBuilder = new(builder); - - var methodDescChunkAllocator = builder.CreateAllocator(start: 0x00000000_20002000, end: 0x00000000_20003000); - var methodDescBuilder = new MockDescriptors.MethodDescriptors(rtsBuilder, loaderBuilder) - { - MethodDescChunkAllocator = methodDescChunkAllocator, - }; - - configure?.Invoke(methodDescBuilder); - - var target = new TestPlaceholderTarget(arch, builder.GetReadContext().ReadFromTarget, methodDescBuilder.Types, methodDescBuilder.Globals); + MockMemorySpace.Builder builder = methodDescBuilder.Builder; + var target = new TestPlaceholderTarget(builder.TargetTestHelpers.Arch, builder.GetReadContext().ReadFromTarget, methodDescBuilder.Types, methodDescBuilder.Globals); target.SetContracts(Mock.Of( c => c.RuntimeTypeSystem == ((IContractFactory)new RuntimeTypeSystemFactory()).CreateContract(target, 1) - && c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); - - testCase(target); + && c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1) + && c.PlatformMetadata == new Mock().Object)); + return target; } [Theory] [ClassData(typeof(MockTarget.StdArch))] public void MethodDescGetMethodDescTokenOk(MockTarget.Architecture arch) { - TargetPointer testMethodDescAddress = default; - TargetPointer objectMethodTable = default; + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder); + MockDescriptors.Loader loaderBuilder = new(builder); + MockDescriptors.MethodDescriptors methodDescBuilder = new(rtsBuilder, loaderBuilder); + const int MethodDefToken = 0x06 << 24; const ushort expectedRidRangeStart = 0x2000; // arbitrary (larger than 1<< TokenRemainderBitCount) + Assert.True(expectedRidRangeStart > (1 << MockDescriptors.MethodDescriptors.TokenRemainderBitCount)); const ushort expectedRidRemainder = 0x10; // arbitrary const uint expectedRid = expectedRidRangeStart | expectedRidRemainder; // arbitrary uint expectedToken = MethodDefToken | expectedRid; ushort expectedSlotNum = 0x0002; // arbitrary, but must be less than number of vtable slots in the method table - MethodDescHelper(arch, - (builder) => + TargetPointer objectMethodTable = MethodTableTests.AddSystemObjectMethodTable(methodDescBuilder.RTSBuilder).MethodTable; + // add a loader module so that we can do the "IsCollectible" check + TargetPointer module = methodDescBuilder.LoaderBuilder.AddModule("testModule"); + methodDescBuilder.RTSBuilder.SetMethodTableAuxData(objectMethodTable, loaderModule: module); + + byte count = 10; // arbitrary + byte methodDescSize = (byte)(methodDescBuilder.Types[DataType.MethodDesc].Size.Value / methodDescBuilder.MethodDescAlignment); + byte chunkSize = (byte)(count * methodDescSize); + var chunk = methodDescBuilder.AddMethodDescChunk(objectMethodTable, "testMethod", count, chunkSize, tokenRange: expectedRidRangeStart); + + byte methodDescNum = 3; // abitrary, less than "count" + byte methodDescIndex = (byte)(methodDescNum * methodDescSize); + TargetPointer testMethodDescAddress = methodDescBuilder.SetMethodDesc(chunk, methodDescIndex, slotNum: expectedSlotNum, flags: 0, tokenRemainder: expectedRidRemainder); + + Target target = CreateTarget(methodDescBuilder); + IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; + + var handle = rts.GetMethodDescHandle(testMethodDescAddress); + Assert.NotEqual(TargetPointer.Null, handle.Address); + + uint token = rts.GetMethodToken(handle); + Assert.Equal(expectedToken, token); + ushort slotNum = rts.GetSlotNumber(handle); + Assert.Equal(expectedSlotNum, slotNum); + TargetPointer mt = rts.GetMethodTable(handle); + Assert.Equal(objectMethodTable, mt); + bool isCollectible = rts.IsCollectibleMethod(handle); + Assert.False(isCollectible); + TargetPointer versioning = rts.GetMethodDescVersioningState(handle); + Assert.Equal(TargetPointer.Null, versioning); + } + + public static IEnumerable StdArchOptionalSlotsData() + { + foreach (object[] arr in new MockTarget.StdArch()) { - objectMethodTable = MethodTableTests.AddSystemObjectMethodTable(builder.RTSBuilder).MethodTable; - // add a loader module so that we can do the "IsCollectible" check - TargetPointer module = builder.LoaderBuilder.AddModule("testModule"); - builder.RTSBuilder.SetMethodTableAuxData(objectMethodTable, loaderModule: module); + MockTarget.Architecture arch = (MockTarget.Architecture)arr[0]; + yield return new object[] { arch, 0 }; + yield return new object[] { arch, MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot }; + yield return new object[] { arch, MethodDescFlags_1.MethodDescFlags.HasMethodImpl }; + yield return new object[] { arch, MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot }; + yield return new object[] { arch, MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot | MethodDescFlags_1.MethodDescFlags.HasMethodImpl }; + yield return new object[] { arch, MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot | MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot }; + yield return new object[] { arch, MethodDescFlags_1.MethodDescFlags.HasMethodImpl | MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot }; + yield return new object[] { arch, MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot | MethodDescFlags_1.MethodDescFlags.HasMethodImpl | MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot }; + } + } + + [Theory] + [MemberData(nameof(StdArchOptionalSlotsData))] + public void GetAddressOfNativeCodeSlot_OptionalSlots(MockTarget.Architecture arch, ushort flagsValue) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder); + MockDescriptors.Loader loaderBuilder = new(builder); + MockDescriptors.MethodDescriptors methodDescBuilder = new(rtsBuilder, loaderBuilder); + + MethodDescFlags_1.MethodDescFlags flags = (MethodDescFlags_1.MethodDescFlags)flagsValue; + ushort numVirtuals = 1; + TargetPointer eeClass = rtsBuilder.AddEEClass(string.Empty, 0, 2, 1); + TargetPointer methodTable = rtsBuilder.AddMethodTable(string.Empty, + mtflags: default, mtflags2: default, baseSize: helpers.ObjectBaseSize, + module: TargetPointer.Null, parentMethodTable: TargetPointer.Null, numInterfaces: 0, numVirtuals: numVirtuals); + rtsBuilder.SetEEClassAndCanonMTRefs(eeClass, methodTable); + + uint methodDescSize = methodDescBuilder.Types[DataType.MethodDesc].Size.Value; + if (flags.HasFlag(MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot)) + methodDescSize += methodDescBuilder.Types[DataType.NonVtableSlot].Size!.Value; + + if (flags.HasFlag(MethodDescFlags_1.MethodDescFlags.HasMethodImpl)) + methodDescSize += methodDescBuilder.Types[DataType.MethodImpl].Size!.Value; + + if (flags.HasFlag(MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot)) + methodDescSize += methodDescBuilder.Types[DataType.NativeCodeSlot].Size!.Value; - byte count = 10; // arbitrary - byte methodDescSize = (byte)(builder.Types[DataType.MethodDesc].Size.Value / builder.MethodDescAlignment); - byte chunkSize = (byte)(count * methodDescSize); - var chunk = builder.AddMethodDescChunk(objectMethodTable, "testMethod", count, chunkSize, tokenRange: expectedRidRangeStart); + byte chunkSize = (byte)(methodDescSize / methodDescBuilder.MethodDescAlignment); + TargetPointer chunk = methodDescBuilder.AddMethodDescChunk(methodTable, string.Empty, count: 1, chunkSize, tokenRange: 0); + TargetPointer methodDescAddress = methodDescBuilder.SetMethodDesc(chunk, index: 0, slotNum: 0, flags: (ushort)flags, tokenRemainder: 0); - byte methodDescNum = 3; // abitrary, less than "count" - byte methodDescIndex = (byte)(methodDescNum * methodDescSize); - Span dest = builder.BorrowMethodDesc(chunk, methodDescIndex); - builder.SetMethodDesc(dest, methodDescIndex, slotNum: expectedSlotNum, tokenRemainder: expectedRidRemainder); + Target target = CreateTarget(methodDescBuilder); + IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; - testMethodDescAddress = builder.GetMethodDescAddress(chunk, methodDescIndex); + var handle = rts.GetMethodDescHandle(methodDescAddress); + Assert.NotEqual(TargetPointer.Null, handle.Address); - }, - (target) => + bool hasNativeCodeSlot = rts.HasNativeCodeSlot(handle); + Assert.Equal(flags.HasFlag(MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot), hasNativeCodeSlot); + if (hasNativeCodeSlot) { - IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; - - var handle = rts.GetMethodDescHandle(testMethodDescAddress); - Assert.NotEqual(TargetPointer.Null, handle.Address); - - uint token = rts.GetMethodToken(handle); - Assert.Equal(expectedToken, token); - ushort slotNum = rts.GetSlotNumber(handle); - Assert.Equal(expectedSlotNum, slotNum); - TargetPointer mt = rts.GetMethodTable(handle); - Assert.Equal(objectMethodTable, mt); - bool isCollectible = rts.IsCollectibleMethod(handle); - Assert.False(isCollectible); - TargetPointer versioning = rts.GetMethodDescVersioningState(handle); - Assert.Equal(TargetPointer.Null, versioning); - }); + // Native code slot is last optional slot + TargetPointer expectedCodeSlotAddr = methodDescAddress + methodDescSize - (uint)helpers.PointerSize; + TargetPointer actualNativeCodeSlotAddr = rts.GetAddressOfNativeCodeSlot(handle); + Assert.Equal(expectedCodeSlotAddr, actualNativeCodeSlotAddr); + } } } diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs index e36010a0683acc..2e77d0238e0efb 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs @@ -11,7 +11,7 @@ internal partial class MockDescriptors { public class MethodDescriptors { - internal const uint TokenRemainderBitCount = 12u; /* see METHOD_TOKEN_REMAINDER_BIT_COUNT*/ + internal const byte TokenRemainderBitCount = 12; /* see METHOD_TOKEN_REMAINDER_BIT_COUNT*/ private static readonly TypeFields MethodDescFields = new TypeFields() { @@ -40,22 +40,30 @@ public class MethodDescriptors ] }; + private const ulong DefaultAllocationRangeStart = 0x2000_2000; + private const ulong DefaultAllocationRangeEnd = 0x2000_3000; + internal readonly RuntimeTypeSystem RTSBuilder; internal readonly Loader LoaderBuilder; internal Dictionary Types { get; } internal (string Name, ulong Value)[] Globals { get; } - internal MockMemorySpace.BumpAllocator MethodDescChunkAllocator { get; set; } + private readonly MockMemorySpace.BumpAllocator _allocator; internal TargetTestHelpers TargetTestHelpers => RTSBuilder.Builder.TargetTestHelpers; internal MockMemorySpace.Builder Builder => RTSBuilder.Builder; internal uint MethodDescAlignment => RuntimeTypeSystem.GetMethodDescAlignment(TargetTestHelpers); internal MethodDescriptors(RuntimeTypeSystem rtsBuilder, Loader loaderBuilder) + : this(rtsBuilder, loaderBuilder, (DefaultAllocationRangeStart, DefaultAllocationRangeEnd)) + { } + + internal MethodDescriptors(RuntimeTypeSystem rtsBuilder, Loader loaderBuilder, (ulong Start, ulong End) allocationRange) { RTSBuilder = rtsBuilder; LoaderBuilder = loaderBuilder; + _allocator = Builder.CreateAllocator(allocationRange.Start, allocationRange.End); Types = GetTypes(); Globals = rtsBuilder.Globals.Concat( [ @@ -71,6 +79,9 @@ internal MethodDescriptors(RuntimeTypeSystem rtsBuilder, Loader loaderBuilder) MethodDescFields, MethodDescChunkFields, ]); + types[DataType.NonVtableSlot] = new Target.TypeInfo() { Size = (uint)TargetTestHelpers.PointerSize }; + types[DataType.MethodImpl] = new Target.TypeInfo() { Size = (uint)TargetTestHelpers.PointerSize * 2 }; + types[DataType.NativeCodeSlot] = new Target.TypeInfo() { Size = (uint)TargetTestHelpers.PointerSize }; types = types .Concat(RTSBuilder.Types) .Concat(LoaderBuilder.Types) @@ -83,7 +94,7 @@ internal TargetPointer AddMethodDescChunk(TargetPointer methodTable, string name uint totalAllocSize = Types[DataType.MethodDescChunk].Size.Value; totalAllocSize += (uint)(size * MethodDescAlignment); - MockMemorySpace.HeapFragment methodDescChunk = MethodDescChunkAllocator.Allocate(totalAllocSize, $"MethodDescChunk {name}"); + MockMemorySpace.HeapFragment methodDescChunk = _allocator.Allocate(totalAllocSize, $"MethodDescChunk {name}"); Span dest = methodDescChunk.Data; TargetTestHelpers.WritePointer(dest.Slice(Types[DataType.MethodDescChunk].Fields[nameof(Data.MethodDescChunk.MethodTable)].Offset), methodTable); TargetTestHelpers.Write(dest.Slice(Types[DataType.MethodDescChunk].Fields[nameof(Data.MethodDescChunk.Size)].Offset), size); @@ -93,25 +104,22 @@ internal TargetPointer AddMethodDescChunk(TargetPointer methodTable, string name return methodDescChunk.Address; } - internal TargetPointer GetMethodDescAddress(TargetPointer chunkAddress, byte index) + private TargetPointer GetMethodDescAddress(TargetPointer chunkAddress, byte index) { Target.TypeInfo methodDescChunkTypeInfo = Types[DataType.MethodDescChunk]; return chunkAddress + methodDescChunkTypeInfo.Size.Value + index * MethodDescAlignment; } - internal Span BorrowMethodDesc(TargetPointer methodDescChunk, byte index) - { - TargetPointer methodDescAddress = GetMethodDescAddress(methodDescChunk, index); - Target.TypeInfo methodDescTypeInfo = Types[DataType.MethodDesc]; - return Builder.BorrowAddressRange(methodDescAddress, (int)methodDescTypeInfo.Size.Value); - } - internal void SetMethodDesc(scoped Span dest, byte index, ushort slotNum, ushort tokenRemainder) + internal TargetPointer SetMethodDesc(TargetPointer methodDescChunk, byte index, ushort slotNum, ushort flags, ushort tokenRemainder) { + TargetPointer methodDesc = GetMethodDescAddress(methodDescChunk, index); Target.TypeInfo methodDescTypeInfo = Types[DataType.MethodDesc]; - TargetTestHelpers.Write(dest.Slice(methodDescTypeInfo.Fields[nameof(Data.MethodDesc.ChunkIndex)].Offset), (byte)index); - TargetTestHelpers.Write(dest.Slice(methodDescTypeInfo.Fields[nameof(Data.MethodDesc.Flags3AndTokenRemainder)].Offset), tokenRemainder); - TargetTestHelpers.Write(dest.Slice(methodDescTypeInfo.Fields[nameof(Data.MethodDesc.Slot)].Offset), slotNum); - // TODO: write more fields + Span data = Builder.BorrowAddressRange(methodDesc, (int)methodDescTypeInfo.Size.Value); + TargetTestHelpers.Write(data.Slice(methodDescTypeInfo.Fields[nameof(Data.MethodDesc.ChunkIndex)].Offset), (byte)index); + TargetTestHelpers.Write(data.Slice(methodDescTypeInfo.Fields[nameof(Data.MethodDesc.Flags)].Offset), flags); + TargetTestHelpers.Write(data.Slice(methodDescTypeInfo.Fields[nameof(Data.MethodDesc.Flags3AndTokenRemainder)].Offset), tokenRemainder); + TargetTestHelpers.Write(data.Slice(methodDescTypeInfo.Fields[nameof(Data.MethodDesc.Slot)].Offset), slotNum); + return methodDesc; } } } From c3e071cb87a4d8c3112a247517ea2f62f26a9a73 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 9 Dec 2024 20:54:53 -0800 Subject: [PATCH 17/70] [dac] Make `GetObjectStringData` return the needed buffer element count instead of byte count (#110540) `GetObjectStringData` is returning the byte count instead of the buffer element count. This is inconsistent with all the other APIs that (optionally) return a needed count which return the buffer element count, not the byte count - for example, for strings specifically: `GetAssemblyName`, `GetMethodDescName`, `GetObjectClassName`, `GetMethodTableName`, `GetFrameName`, `GetPEFileName`. --- src/coreclr/debug/daccess/request.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 34ce7f2aa8827d..fee1cadacbe8a1 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -1571,12 +1571,18 @@ ClrDataAccess::GetObjectStringData(CLRDATA_ADDRESS obj, unsigned int count, _Ino count = needed; TADDR pszStr = TO_TADDR(obj)+offsetof(StringObject, m_FirstChar); - hr = m_pTarget->ReadVirtual(pszStr, (PBYTE)stringData, count * sizeof(WCHAR), &needed); + ULONG32 bytesRead; + hr = m_pTarget->ReadVirtual(pszStr, (PBYTE)stringData, count * sizeof(WCHAR), &bytesRead); + needed = bytesRead / sizeof(WCHAR); if (SUCCEEDED(hr)) - stringData[count - 1] = W('\0'); + { + stringData[needed - 1] = W('\0'); + } else + { stringData[0] = W('\0'); + } } else { From 7b1214bbd7ae704f6e6de0c13c20729a966887fa Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Mon, 9 Dec 2024 22:04:17 -0800 Subject: [PATCH 18/70] Remove ld_classic in 16+ (#110542) * Remove ld_class in 16+ * Update src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets Co-authored-by: Jan Kotas --------- Co-authored-by: Jan Kotas --- .../BuildIntegration/Microsoft.NETCore.Native.Unix.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets index af68ed744c9d21..970b1debbf46e1 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets @@ -284,7 +284,7 @@ The .NET Foundation licenses this file to you under the MIT license. - + From 43c5facc31065b4663b10338b8a7bd180f32a96b Mon Sep 17 00:00:00 2001 From: Mike McLaughlin Date: Mon, 9 Dec 2024 23:27:07 -0800 Subject: [PATCH 19/70] Fix AV error in DAC on Linux/MacOS - issue #109877 (#110557) --- src/coreclr/debug/daccess/request.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index fee1cadacbe8a1..7a07a8c0f6c3c1 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -1049,6 +1049,11 @@ HRESULT ClrDataAccess::GetMethodDescData( { ILCodeVersion activeILCodeVersion = pCodeVersionManager->GetActiveILCodeVersion(pMD); activeNativeCodeVersion = activeILCodeVersion.GetActiveNativeCodeVersion(pMD); + if (activeNativeCodeVersion.IsNull()) + { + // This is caught below and S_OK is returned + DacError(E_ACCESSDENIED); + } } CopyNativeCodeVersionToReJitData( activeNativeCodeVersion, From 40014b6c4246f6c1af7d9c5a1806c3dc34c88d10 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Tue, 10 Dec 2024 10:42:09 +0100 Subject: [PATCH 20/70] [browser] fix code gen overflow (#110539) --- .../gen/JSImportGenerator/JSSignatureContext.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSSignatureContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSSignatureContext.cs index 3f058a5c7eeb41..f59bb25bca0f51 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSSignatureContext.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSSignatureContext.cs @@ -48,6 +48,7 @@ public static JSSignatureContext Create( // there could be multiple method signatures with the same name, get unique signature name uint hash = 17; + int typesHash; unchecked { foreach (var param in sigContext.ElementTypeInformation) @@ -57,8 +58,8 @@ public static JSSignatureContext Create( foreach (char c in param.ManagedType.FullTypeName) hash = hash * 31 + c; } + typesHash = (int)(hash & int.MaxValue); }; - int typesHash = Math.Abs((int)hash); var fullName = $"{method.ContainingType.ToDisplayString()}.{method.Name}"; string qualifiedName = GetFullyQualifiedMethodName(env, method); From 5d539a2f65343cd11430ec43f60fdd277437bc2f Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Tue, 10 Dec 2024 10:51:35 +0100 Subject: [PATCH 21/70] Disable `HybridGlobalization` tests for WASM on CI (#110526) --- eng/pipelines/coreclr/perf-wasm-jobs.yml | 23 -------------- .../runtime-extra-platforms-wasm.yml | 31 ------------------- eng/testing/performance/blazor_perf.proj | 23 ++++++-------- 3 files changed, 9 insertions(+), 68 deletions(-) diff --git a/eng/pipelines/coreclr/perf-wasm-jobs.yml b/eng/pipelines/coreclr/perf-wasm-jobs.yml index 93ffdfc5283821..8323bbc448e464 100644 --- a/eng/pipelines/coreclr/perf-wasm-jobs.yml +++ b/eng/pipelines/coreclr/perf-wasm-jobs.yml @@ -171,26 +171,3 @@ jobs: logicalmachine: 'perftiger' downloadSpecificBuild: ${{ parameters.downloadSpecificBuild }} perfBranch: ${{ parameters.perfBranch }} - -- ${{if or(and(ne(variables['System.TeamProject'], 'public'), in(variables['Build.Reason'], 'Schedule')), in(variables['Build.DefinitionName'], 'runtime-wasm-perf')) }}: - # run mono wasm blazor perf job - - template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/coreclr/templates/perf-job.yml - buildConfig: release - runtimeFlavor: wasm - platforms: - - linux_x64 - jobParameters: - testGroup: perf - liveLibrariesBuildConfig: Release - runtimeType: wasm - projectFile: $(Build.SourcesDirectory)/eng/testing/performance/blazor_perf.proj - runKind: blazor_scenarios - runJobTemplate: /eng/pipelines/coreclr/templates/run-scenarios-job.yml - # For working with a newer sdk, and previous tfm (eg. 9.0 sdk, and net8.0 tfm) - #additionalSetupParameters: '--dotnetversions 8.0.0' # passed to run-performance-job.py - logicalmachine: 'perftiger' - downloadSpecificBuild: ${{ parameters.downloadSpecificBuild }} - hybridGlobalization: True - perfBranch: ${{ parameters.perfBranch }} diff --git a/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml b/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml index f2c2f84e8730af..06b65b209ba500 100644 --- a/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml +++ b/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml @@ -148,37 +148,6 @@ jobs: scenarios: - WasmTestOnWasmtime - # Hybrid Globalization tests - - template: /eng/pipelines/common/templates/wasm-library-tests.yml - parameters: - platforms: - - browser_wasm - - browser_wasm_win - nameSuffix: _HybridGlobalization - extraBuildArgs: /p:HybridGlobalization=true - isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }} - isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }} - alwaysRun: true - scenarios: - - WasmTestOnChrome - - WasmTestOnFirefox - - # # Hybrid Globalization AOT tests - # # ActiveIssue: https://github.com/dotnet/runtime/issues/51746 - # - template: /eng/pipelines/common/templates/wasm-library-aot-tests.yml - # parameters: - # platforms: - # - browser_wasm - # - browser_wasm_win - # nameSuffix: _HybridGlobalization_AOT - # extraBuildArgs: /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) /p:HybridGlobalization=true - # runAOT: true - # isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }} - # isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }} - # alwaysRun: true - # scenarios: - # - WasmTestOnChrome - - ${{ if and(ne(parameters.isRollingBuild, true), ne(parameters.excludeNonLibTests, true), ne(parameters.debuggerTestsOnly, true)) }}: # Builds only - template: /eng/pipelines/common/templates/wasm-build-only.yml diff --git a/eng/testing/performance/blazor_perf.proj b/eng/testing/performance/blazor_perf.proj index 08aea69800b67b..bcad56a4be50b8 100644 --- a/eng/testing/performance/blazor_perf.proj +++ b/eng/testing/performance/blazor_perf.proj @@ -9,14 +9,9 @@ python3 $(HelixPreCommands);chmod +x $HELIX_WORKITEM_PAYLOAD/SOD/SizeOnDisk - <_MSBuildArgs>/p:_TrimmerDumpDependencies=true;/p:HybridGlobalization=$(hybridGlobalization);/warnaserror:NU1602,NU1604 + <_MSBuildArgs>/p:_TrimmerDumpDependencies=true;/warnaserror:NU1602,NU1604 --has-workload --readonly-dotnet --msbuild "$(_MSBuildArgs)" --msbuild-static AdditionalMonoLinkerOptions=%27"%24(AdditionalMonoLinkerOptions) --dump-dependencies"%27 --binlog $(LogDirectory)blazor_publish.binlog $(EnvVars) $(Python) pre.py publish $(PublishArgs) - - - - HybridGlobalization - $(HybridGlobalizationPath) @@ -51,48 +46,48 @@ - + $(WorkItemDirectory) cd $(BlazorMinDirectory) && $(PublishCommand) && $(Python) test.py sod --scenario-name "%(Identity)" $(ScenarioArgs) - + $(WorkItemDirectory) cd $(BlazorMinAOTDirectory) && $(PublishCommand) && $(Python) test.py sod --scenario-name "%(Identity)" $(ScenarioArgs) 00:30 - + $(WorkItemDirectory) cd $(BlazorDirectory) && $(PublishCommand) && $(Python) test.py sod --scenario-name "%(Identity)" $(ScenarioArgs) $(Python) post.py --readonly-dotnet - + $(WorkItemDirectory) cd $(BlazorAOTDirectory) && $(PublishCommand) && $(Python) test.py sod --scenario-name "%(Identity)" $(ScenarioArgs) $(Python) post.py --readonly-dotnet 00:30 - + $(WorkItemDirectory) cd $(BlazorPizzaDirectory) && $(PublishCommand) -f $(PerflabTargetFrameworks) && $(Python) test.py sod --scenario-name "%(Identity)" --dirs $(PizzaAppPubLocation) $(ScenarioArgs) $(Python) post.py --readonly-dotnet - + $(WorkItemDirectory) cd $(BlazorPizzaAOTDirectory) && $(PublishCommand) -f $(PerflabTargetFrameworks) && $(Python) test.py sod --scenario-name "%(Identity)" --dirs $(PizzaAppPubLocation) $(ScenarioArgs) $(Python) post.py --readonly-dotnet 1:00 - + $(WorkItemDirectory) cd $(BlazorLocalizedDirectory) && $(PublishCommand) -f $(PerflabTargetFrameworks) && $(Python) test.py sod --scenario-name "%(Identity)" --dirs $(PizzaAppPubLocation) $(ScenarioArgs) $(Python) post.py --readonly-dotnet 1:00 - + <_PublishArgsWithAOT>--msbuild "$(_MSBuildArgs);/p:RunAOTCompilation=true" $(WorkItemDirectory) cd $(BlazorLocalizedDirectory) && $(PublishCommand) $(_PublishArgsWithAOT) -f $(PerflabTargetFrameworks) && $(Python) test.py sod --scenario-name "%(Identity)" --dirs $(PizzaAppPubLocation) $(ScenarioArgs) From 0bb743292c5ea80c13ac43c46edcf9a20e37fd44 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 10 Dec 2024 11:38:15 +0100 Subject: [PATCH 22/70] JIT: Include more edges in `BlockDominancePreds` (#110531) Because of spurious flow it is possible that the preds of the try-begin block are not the only blocks that can dominate a handler. We handled this possibility, but only for finally/fault blocks that can directly have these edges. However, even other handler blocks can be reachable through spurious paths that involves finally/fault blocks, and in these cases returning the preds of the try-begin block is not enough to compute the right dominator statically. --- src/coreclr/jit/block.cpp | 50 +++++---------- .../JitBlue/Runtime_109981/Runtime_109981.cs | 63 +++++++++++++++++++ .../Runtime_109981/Runtime_109981.csproj | 8 +++ 3 files changed, 86 insertions(+), 35 deletions(-) create mode 100644 src/tests/JIT/Regression/JitBlue/Runtime_109981/Runtime_109981.cs create mode 100644 src/tests/JIT/Regression/JitBlue/Runtime_109981/Runtime_109981.csproj diff --git a/src/coreclr/jit/block.cpp b/src/coreclr/jit/block.cpp index b630e18c9e7b97..f032ce7748c754 100644 --- a/src/coreclr/jit/block.cpp +++ b/src/coreclr/jit/block.cpp @@ -267,15 +267,21 @@ FlowEdge* Compiler::BlockPredsWithEH(BasicBlock* blk) // 'blk'. // // Arguments: -// blk - Block to get dominance predecessors for. +// blk - Block to get dominance predecessors for. // // Returns: -// List of edges. +// List of edges. // // Remarks: -// Differs from BlockPredsWithEH only in the treatment of handler blocks; -// enclosed blocks are never dominance preds, while all predecessors of -// blocks in the 'try' are (currently only the first try block expected). +// Differs from BlockPredsWithEH only in the treatment of handler blocks; +// enclosed blocks are never dominance preds, while all predecessors of +// blocks in the 'try' are (currently only the first try block expected). +// +// There are additional complications due to spurious flow because of +// two-pass EH. In the flow graph with EH edges we can see entries into the +// try from filters outside the try, to blocks other than the "try-begin" +// block. Hence we need to consider the full set of blocks in the try region +// when considering the block dominance preds. // FlowEdge* Compiler::BlockDominancePreds(BasicBlock* blk) { @@ -284,14 +290,6 @@ FlowEdge* Compiler::BlockDominancePreds(BasicBlock* blk) return blk->bbPreds; } - EHblkDsc* ehblk = ehGetBlockHndDsc(blk); - if (!ehblk->HasFinallyOrFaultHandler() || (ehblk->ebdHndBeg != blk)) - { - return ehblk->ebdTryBeg->bbPreds; - } - - // Finally/fault handlers can be preceded by enclosing filters due to 2 - // pass EH, so add those and keep them cached. BlockToFlowEdgeMap* domPreds = GetDominancePreds(); FlowEdge* res; if (domPreds->Lookup(blk, &res)) @@ -299,29 +297,11 @@ FlowEdge* Compiler::BlockDominancePreds(BasicBlock* blk) return res; } - res = ehblk->ebdTryBeg->bbPreds; - if (ehblk->HasFinallyOrFaultHandler() && (ehblk->ebdHndBeg == blk)) + EHblkDsc* ehblk = ehGetBlockHndDsc(blk); + res = BlockPredsWithEH(blk); + for (BasicBlock* predBlk : ehblk->ebdTryBeg->PredBlocks()) { - // block is a finally or fault handler; all enclosing filters are predecessors - unsigned enclosing = ehblk->ebdEnclosingTryIndex; - while (enclosing != EHblkDsc::NO_ENCLOSING_INDEX) - { - EHblkDsc* enclosingDsc = ehGetDsc(enclosing); - if (enclosingDsc->HasFilter()) - { - for (BasicBlock* filterBlk = enclosingDsc->ebdFilter; filterBlk != enclosingDsc->ebdHndBeg; - filterBlk = filterBlk->Next()) - { - res = new (this, CMK_FlowEdge) FlowEdge(filterBlk, blk, res); - - assert(filterBlk->VisitEHEnclosedHandlerSecondPassSuccs(this, [blk](BasicBlock* succ) { - return succ == blk ? BasicBlockVisit::Abort : BasicBlockVisit::Continue; - }) == BasicBlockVisit::Abort); - } - } - - enclosing = enclosingDsc->ebdEnclosingTryIndex; - } + res = new (this, CMK_FlowEdge) FlowEdge(predBlk, blk, res); } domPreds->Set(blk, res); diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_109981/Runtime_109981.cs b/src/tests/JIT/Regression/JitBlue/Runtime_109981/Runtime_109981.cs new file mode 100644 index 00000000000000..a7c8bcd92d81aa --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_109981/Runtime_109981.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using Xunit; + +public class Runtime_109981 +{ + [Fact] + public static int TestEntryPoint() => Foo(14); + + public static int Foo(int x) + { + if (x == 123) + return 0; + + int sum = 9; + for (int i = 0; i < x; i++) + { + sum += i; + } + + try + { + if (x != 123) + return sum; + + try + { + try + { + Bar(); + } + finally + { + sum += 1000; + } + } + catch (ArgumentException) + { + sum += 10000; + } + } + catch when (Filter()) + { + sum += 100000; + } + + return sum; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Bar() + { + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool Filter() + { + return true; + } +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_109981/Runtime_109981.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_109981/Runtime_109981.csproj new file mode 100644 index 00000000000000..de6d5e08882e86 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_109981/Runtime_109981.csproj @@ -0,0 +1,8 @@ + + + True + + + + + From e0f70cc6e7d18fd1cfd79e13bb32f6bd0aea677d Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Tue, 10 Dec 2024 14:04:42 +0200 Subject: [PATCH 23/70] [mono][interp] Remove no_inlining functionality for dead bblocks (#110468) Many methods in the BCL, especially hwintrins related, contain a lot of code that is detected as dead during compilation. On mono, inlining happens during IL import and a lot of optimizations are run as later passes. This exposed the issue where we have a lot of dead code bloat from inlining, with optimizations later running on it. A simple solution for this problem was tracking jump counts for each bblock (https://github.com/dotnet/runtime/pull/97514), which are initialized when bblocks are first created, before IL import stage. Then a small set of IL import level optimizations were added, in order to reduce the jump targets of each bblock. As we were further importing IL, if we reached a bblock with 0 jump targets, we would disable inlining into it, in order to reduce code bloat. Disabling code emit altogether was too challenging. Another limitation of this approach was that we would fail to detect dead code if it was part of a loop. The results were good however, by reducing mem usage in `System.Numerics.Tensor.Tests` from 6GB to 600MB. For an unrelated issue, the order in which we generate bblocks was redesigned in order to account for bblock stack state initialization in weird control flow scenarios (https://github.com/dotnet/runtime/pull/108731). This was achieved by deferring IL import into bblocks that were not yet reached from other live bblocks. A side effect of this is that we no longer generate code at all in unreachable bblocks, completely superseding the previous approach while addressing both the problems of inlining into loops or generating IR for dead IL. In the previously mentioned test suite, this further reduced the memory usage to 300MB. Remnants of the unnecessary `no_inlining` approach still lingered in the code, leading to disabling of inline optimization in some reachable code. This triggered a significant performance regression which this PR addresses. --- src/mono/mono/mini/interp/transform.c | 48 ++++----------------------- src/mono/mono/mini/interp/transform.h | 3 -- 2 files changed, 7 insertions(+), 44 deletions(-) diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index b515c2f7d9dc23..cf17b88970345d 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -760,8 +760,6 @@ handle_branch (TransformData *td, int long_op, int offset) init_bb_stack_state (td, target_bb); if (long_op != MINT_CALL_HANDLER) { - if (td->cbb->no_inlining) - target_bb->jump_targets--; // We don't link finally blocks into the cfg (or other handler blocks for that matter) interp_link_bblocks (td, td->cbb, target_bb); } @@ -803,8 +801,6 @@ one_arg_branch(TransformData *td, int mint_op, int offset, int inst_size) return FALSE; } else { // branch condition always false, it is a NOP - int target = GPTRDIFF_TO_INT (td->ip + offset + inst_size - td->il_code); - td->offset_to_bb [target]->jump_targets--; return TRUE; } } else { @@ -901,8 +897,6 @@ two_arg_branch(TransformData *td, int mint_op, int offset, int inst_size) return FALSE; } else { // branch condition always false, it is a NOP - int target = GPTRDIFF_TO_INT (td->ip + offset + inst_size - td->il_code); - td->offset_to_bb [target]->jump_targets--; return TRUE; } } else { @@ -2884,9 +2878,6 @@ interp_method_check_inlining (TransformData *td, MonoMethod *method, MonoMethodS if (td->disable_inlining) return FALSE; - if (td->cbb->no_inlining) - return FALSE; - // Exception handlers are always uncommon, with the exception of finally. int inner_clause = td->clause_indexes [td->current_il_offset]; if (inner_clause != -1 && td->header->clauses [inner_clause].flags != MONO_EXCEPTION_CLAUSE_FINALLY) @@ -4151,7 +4142,6 @@ get_basic_blocks (TransformData *td, MonoMethodHeader *header, gboolean make_lis unsigned char *target; ptrdiff_t cli_addr; const MonoOpcode *opcode; - InterpBasicBlock *bb; td->offset_to_bb = (InterpBasicBlock**)mono_mempool_alloc0 (td->mempool, (unsigned int)(sizeof (InterpBasicBlock*) * (end - start + 1))); get_bb (td, start, make_list); @@ -4160,21 +4150,18 @@ get_basic_blocks (TransformData *td, MonoMethodHeader *header, gboolean make_lis MonoExceptionClause *c = header->clauses + i; if (start + c->try_offset > end || start + c->try_offset + c->try_len > end) return FALSE; - bb = get_bb (td, start + c->try_offset, make_list); - bb->jump_targets++; + get_bb (td, start + c->try_offset, make_list); mono_bitset_set (il_targets, c->try_offset); mono_bitset_set (il_targets, c->try_offset + c->try_len); if (start + c->handler_offset > end || start + c->handler_offset + c->handler_len > end) return FALSE; - bb = get_bb (td, start + c->handler_offset, make_list); - bb->jump_targets++; + get_bb (td, start + c->handler_offset, make_list); mono_bitset_set (il_targets, c->handler_offset); mono_bitset_set (il_targets, c->handler_offset + c->handler_len); if (c->flags == MONO_EXCEPTION_CLAUSE_FILTER) { if (start + c->data.filter_offset > end) return FALSE; - bb = get_bb (td, start + c->data.filter_offset, make_list); - bb->jump_targets++; + get_bb (td, start + c->data.filter_offset, make_list); mono_bitset_set (il_targets, c->data.filter_offset); } } @@ -4207,8 +4194,7 @@ get_basic_blocks (TransformData *td, MonoMethodHeader *header, gboolean make_lis target = start + cli_addr + 2 + (signed char)ip [1]; if (target > end) return FALSE; - bb = get_bb (td, target, make_list); - bb->jump_targets++; + get_bb (td, target, make_list); ip += 2; get_bb (td, ip, make_list); mono_bitset_set (il_targets, GPTRDIFF_TO_UINT32 (target - start)); @@ -4217,8 +4203,7 @@ get_basic_blocks (TransformData *td, MonoMethodHeader *header, gboolean make_lis target = start + cli_addr + 5 + (gint32)read32 (ip + 1); if (target > end) return FALSE; - bb = get_bb (td, target, make_list); - bb->jump_targets++; + get_bb (td, target, make_list); ip += 5; get_bb (td, ip, make_list); mono_bitset_set (il_targets, GPTRDIFF_TO_UINT32 (target - start)); @@ -4231,15 +4216,13 @@ get_basic_blocks (TransformData *td, MonoMethodHeader *header, gboolean make_lis target = start + cli_addr; if (target > end) return FALSE; - bb = get_bb (td, target, make_list); - bb->jump_targets++; + get_bb (td, target, make_list); mono_bitset_set (il_targets, GPTRDIFF_TO_UINT32 (target - start)); for (j = 0; j < n; ++j) { target = start + cli_addr + (gint32)read32 (ip); if (target > end) return FALSE; - bb = get_bb (td, target, make_list); - bb->jump_targets++; + get_bb (td, target, make_list); ip += 4; mono_bitset_set (il_targets, GPTRDIFF_TO_UINT32 (target - start)); } @@ -5446,13 +5429,6 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, /* We are starting a new basic block. Change cbb and link them together */ if (link_bblocks) { - if (!new_bb->jump_targets && td->cbb->no_inlining) { - // This is a bblock that is not branched to and falls through from - // a dead predecessor. It means it is dead. - new_bb->no_inlining = TRUE; - if (td->verbose_level) - g_print ("Disable inlining in BB%d\n", new_bb->index); - } /* * By default we link cbb with the new starting bblock, unless the previous * instruction is an unconditional branch (BR, LEAVE, ENDFINALLY) @@ -5472,16 +5448,6 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, } // link_bblocks remains true, which is the default } else { - if (!new_bb->jump_targets) { - // This is a bblock that is not branched to and it is not linked to the - // predecessor. It means it is dead. - new_bb->no_inlining = TRUE; - if (td->verbose_level) - g_print ("Disable inlining in BB%d\n", new_bb->index); - } else { - g_assert (new_bb->jump_targets > 0); - } - if (new_bb->stack_height >= 0) { // This is relevant only for copying the vars associated with the values on the stack memcpy (td->stack, new_bb->stack_state, new_bb->stack_height * sizeof(td->stack [0])); diff --git a/src/mono/mono/mini/interp/transform.h b/src/mono/mono/mini/interp/transform.h index 4f27cc8bd0cd35..16e5cc015ae039 100644 --- a/src/mono/mono/mini/interp/transform.h +++ b/src/mono/mono/mini/interp/transform.h @@ -147,7 +147,6 @@ struct _InterpBasicBlock { StackInfo *stack_state; int index; - int jump_targets; InterpBasicBlock *try_bblock; @@ -160,8 +159,6 @@ struct _InterpBasicBlock { // This block has special semantics and it shouldn't be optimized away guint preserve : 1; guint dead: 1; - // This bblock is detectead early as being dead, we don't inline into it - guint no_inlining: 1; // If patchpoint is set we will store mapping information between native offset and bblock index within // InterpMethod. In the unoptimized method we will map from native offset to the bb_index while in the // optimized method we will map the bb_index to the corresponding native offset. From bbe9a9d0f3ae91ca18f6fd7e9404c1fe52d772a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 10 Dec 2024 14:21:18 +0100 Subject: [PATCH 24/70] Enable more ILLinker skipped tests on native AOT (#110353) Progress towards #82447. --- .../TestCases/TestSuites.cs | 3 - .../Reflection/ObjectGetType.cs | 88 +++++++++---------- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCases/TestSuites.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCases/TestSuites.cs index a3493db1786a8d..87d8fc21ce8798 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCases/TestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCases/TestSuites.cs @@ -71,9 +71,6 @@ public void Reflection (string t) { switch (t) { - case "ObjectGetType": - // Skip for now - break; case "ObjectGetTypeLibraryMode": case "TypeHierarchyLibraryModeSuppressions": // No Library mode diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/ObjectGetType.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/ObjectGetType.cs index 3eaa5e496901c6..58090e4ec1c511 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/ObjectGetType.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/ObjectGetType.cs @@ -694,7 +694,7 @@ class InterfaceSeenFirst [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] interface IAnnotatedInterface { - [Kept] + [Kept (By = Tool.Trimmer /* The method is not a target of reflection */)] void InterfaceMethod (); } @@ -744,7 +744,7 @@ class AnnotationsRequestedOnImplementation [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] interface IAnnotatedInterface { - [Kept] // Kept because it's implemented on the class + [Kept (By = Tool.Trimmer /* The method is not a target of reflection */)] // Kept because it's implemented on the class void InterfaceMethod (); // Annotation will not be applied to the interface, since nothing @@ -797,14 +797,14 @@ class AnnotationsRequestedOnInterface [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] interface IAnnotatedInterface { - [Kept] // Kept because it's implemented on the class + [Kept (By = Tool.Trimmer /* The method is not a target of reflection */)] // Kept because it's implemented on the class void InterfaceMethod (); // Annotation applied to the interface since that's what reflection asked about - [Kept] + [Kept (By = Tool.Trimmer /* The method is not a target of reflection */)] static void DoSomething () { } - [Kept] + [Kept (By = Tool.Trimmer /* The method is not a target of reflection */)] void DefaultInterfaceMethod () { } } @@ -843,12 +843,12 @@ public static void Test () [Kept] class AllAnnotationsAreApplied { - [Kept] - [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] + [Kept (By = Tool.Trimmer /* The interface is not actually used */)] + [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute), By = Tool.Trimmer)] [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] interface IMethodsAnnotatedInterface { - [Kept] + [Kept (By = Tool.Trimmer)] void InterfaceMethod (); } @@ -858,14 +858,14 @@ interface IPropertiesAnnotatedInterface bool Property { get; } } - [Kept] - [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] + [Kept (By = Tool.Trimmer /* The interface is not actually used */)] + [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute), By = Tool.Trimmer)] [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicEvents)] interface IEventsAnnotatedInterface { - [Kept] - [KeptEventAddMethod] - [KeptEventRemoveMethod] + [Kept(By = Tool.Trimmer)] + [KeptEventAddMethod(By = Tool.Trimmer)] + [KeptEventRemoveMethod(By = Tool.Trimmer)] event EventHandler MyEvent; } @@ -1037,12 +1037,12 @@ public static void Test () [Kept] class DiamondShapeWithAnnotatedInterface { - [Kept] - [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] + [Kept (By = Tool.Trimmer /* The interface is not actually used */)] + [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute), By = Tool.Trimmer)] [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] interface IAnnotatedCommon { - [Kept] // Due to the annotation + [Kept (By = Tool.Trimmer)] // Due to the annotation void InterfaceMethod (); } @@ -1158,8 +1158,8 @@ public static void Test () [Kept] class ApplyingAnnotationIntroducesTypesToApplyAnnotationToViaInterfaces { - [Kept] - [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] + [Kept (By = Tool.Trimmer /* The interface is not actually used */)] + [KeptAttributeAttribute (typeof(DynamicallyAccessedMembersAttribute), By = Tool.Trimmer)] [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields)] interface IAnnotatedInterface { @@ -1188,7 +1188,7 @@ class FieldTypeAlsoImplementsInterface : IAnnotatedInterface public NestedFieldType _nestedField; [Kept] - public class NestedFieldType + public struct NestedFieldType { } } @@ -1230,11 +1230,11 @@ class DerivedFromMethodsBase : MethodAnnotatedBase void PrivateMethod () { } } - [Kept] - [KeptBaseType (typeof (MethodAnnotatedBase))] + [Kept(By = Tool.Trimmer /* only used in a method signature, not legitimate to keep beyond IL-level trimming */)] + [KeptBaseType (typeof (MethodAnnotatedBase), By = Tool.Trimmer)] class AnotherMethodsDerived : MethodAnnotatedBase { - [Kept] + [Kept(By = Tool.Trimmer)] public static void PublicStaticMethod (DerivedFromPropertiesBase p) { } static void PrivateStaticMethod () { } @@ -1248,27 +1248,27 @@ class PropertiesAnnotatedBase { } - [Kept] - [KeptBaseType (typeof (PropertiesAnnotatedBase))] + [Kept (By = Tool.Trimmer /* only used in a method signature, not legitimate to keep beyond IL-level trimming */)] + [KeptBaseType (typeof (PropertiesAnnotatedBase), By = Tool.Trimmer)] class DerivedFromPropertiesBase : PropertiesAnnotatedBase { - [Kept] - public static AnotherPropertiesDerived PublicProperty { [Kept] get => null; } + [Kept (By = Tool.Trimmer)] + public static AnotherPropertiesDerived PublicProperty { [Kept (By = Tool.Trimmer)] get => null; } private static UnusedType PrivateProperty { get => null; } } - [Kept] - [KeptBaseType (typeof (PropertiesAnnotatedBase))] + [Kept (By = Tool.Trimmer /* only used in a method signature, not legitimate to keep beyond IL-level trimming */)] + [KeptBaseType (typeof (PropertiesAnnotatedBase), By = Tool.Trimmer)] class AnotherPropertiesDerived : PropertiesAnnotatedBase { - [Kept] - public static UsedType PublicProperty { [Kept] get => null; } + [Kept(By = Tool.Trimmer)] + public static UsedType PublicProperty { [Kept(By = Tool.Trimmer)] get => null; } private static UnusedType PrivateProperty { get => null; } } - [Kept] + [Kept (By = Tool.Trimmer /* only used in a method signature, not legitimate to keep beyond IL-level trimming */)] class UsedType { } class UnusedType { } @@ -1343,12 +1343,12 @@ interface INestedInterface void InterfaceMethod (); } - [Kept] - [KeptMember (".ctor()")] - [KeptBaseType (typeof (AnnotatedBase))] + [Kept (By = Tool.Trimmer /* only used in a method signature, not legitimate to keep beyond IL-level trimming */)] + [KeptMember (".ctor()", By = Tool.Trimmer)] + [KeptBaseType (typeof (AnnotatedBase), By = Tool.Trimmer)] class AnotherAnnotatedType : AnnotatedBase { - [Kept] + [Kept (By = Tool.Trimmer)] int _field; } @@ -1557,17 +1557,17 @@ class UsedByDerived { class AnnotatedBase { - [Kept] - [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] + [Kept (By = Tool.Trimmer /* https://github.com/dotnet/runtime/issues/110563 */)] + [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute), By = Tool.Trimmer)] [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] class Base { - [Kept] + [Kept (By = Tool.Trimmer)] public void Method () { } } - [Kept] - [KeptBaseType (typeof (Base))] + [Kept (By = Tool.Trimmer /* https://github.com/dotnet/runtime/issues/110563 */)] + [KeptBaseType (typeof (Base), By = Tool.Trimmer)] class Derived : Base { } @@ -1591,9 +1591,9 @@ class Base public void Method () { } } - [Kept] - [KeptBaseType (typeof (Base))] - [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] + [Kept (By = Tool.Trimmer /* The object.GetType() call is statically unreachable, this could be trimmed */)] + [KeptBaseType (typeof (Base), By = Tool.Trimmer)] + [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute), By = Tool.Trimmer)] [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] class Derived : Base { @@ -1616,7 +1616,7 @@ class AnnotatedInterface [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] interface IBase { - [Kept] + [Kept (By = Tool.Trimmer /* https://github.com/dotnet/runtime/issues/104740 */)] public void Method () { } } From 575027205a2f23dc9284f9df64e3f234d5e09630 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 10 Dec 2024 17:42:46 +0100 Subject: [PATCH 25/70] JIT: Avoid comparing regnums in `GenTreeHWIntrinsic::Equals` (#110535) Register numbers are not part of the syntax of JIT IR, so it does not need to be compared for the purpose of this function. At the same time we can hit asserts for nodes that aren't multi-reg nodes. Fix #110316 --- src/coreclr/jit/gentree.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 09a94a21b1d5b1..c31d6ac1b2c552 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -27641,8 +27641,7 @@ void GenTreeHWIntrinsic::SetHWIntrinsicId(NamedIntrinsic intrinsicId) { return (op1->TypeGet() == op2->TypeGet()) && (op1->GetHWIntrinsicId() == op2->GetHWIntrinsicId()) && (op1->GetSimdBaseType() == op2->GetSimdBaseType()) && (op1->GetSimdSize() == op2->GetSimdSize()) && - (op1->GetAuxiliaryType() == op2->GetAuxiliaryType()) && (op1->GetRegByIndex(1) == op2->GetRegByIndex(1)) && - OperandsAreEqual(op1, op2); + (op1->GetAuxiliaryType() == op2->GetAuxiliaryType()) && OperandsAreEqual(op1, op2); } void GenTreeHWIntrinsic::Initialize(NamedIntrinsic intrinsicId) From 2071313c9715f9ab7e52a98eacdded36010f5db9 Mon Sep 17 00:00:00 2001 From: Tomas Weinfurt Date: Tue, 10 Dec 2024 10:10:04 -0800 Subject: [PATCH 26/70] fix FastOpen compilation (#110561) --- src/native/libs/System.Native/pal_networking.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/native/libs/System.Native/pal_networking.c b/src/native/libs/System.Native/pal_networking.c index 3efd4f7ae4f963..ab996e2c4ff7d7 100644 --- a/src/native/libs/System.Native/pal_networking.c +++ b/src/native/libs/System.Native/pal_networking.c @@ -1780,6 +1780,11 @@ int32_t SystemNative_Connect(intptr_t socket, uint8_t* socketAddress, int32_t so return err == 0 ? Error_SUCCESS : SystemNative_ConvertErrorPlatformToPal(errno); } +#if defined(__linux__) && !defined(TCP_FASTOPEN_CONNECT) +// fixup if compiled against old Kernel headers. +// Can be removed once we have at least 4.11 +#define TCP_FASTOPEN_CONNECT 30 +#endif int32_t SystemNative_Connectx(intptr_t socket, uint8_t* socketAddress, int32_t socketAddressLen, uint8_t* data, int32_t dataLen, int32_t tfo, int* sent) { if (socketAddress == NULL || socketAddressLen < 0 || sent == NULL) From f430ffa1335f87c7e7386d2d2ae7fb5c2517950d Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Tue, 10 Dec 2024 19:26:48 +0100 Subject: [PATCH 27/70] Remove duplicate IsAscii check in string.IsNormalized (#110576) --- .../src/System/Globalization/Normalization.cs | 30 +++++++------------ .../src/System/String.cs | 19 ------------ .../System/StringNormalizationExtensions.cs | 7 +++-- 3 files changed, 15 insertions(+), 41 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.cs index 647e097601cd71..2488ab7824be71 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text; @@ -13,10 +12,10 @@ internal static bool IsNormalized(ReadOnlySpan source, NormalizationForm n { CheckNormalizationForm(normalizationForm); - // In Invariant mode we assume all characters are normalized. - if (GlobalizationMode.Invariant || source.IsEmpty || Ascii.IsValid(source)) + // In Invariant mode we assume all characters are normalized because we don't support any linguistic operations on strings. + // If it's ASCII && one of the 4 main forms, then it's already normalized. + if (GlobalizationMode.Invariant || Ascii.IsValid(source)) { - // This is because we don't support any linguistic operation on the strings return true; } @@ -29,10 +28,10 @@ internal static string Normalize(string strInput, NormalizationForm normalizatio { CheckNormalizationForm(normalizationForm); - if (GlobalizationMode.Invariant) + // In Invariant mode we assume all characters are normalized because we don't support any linguistic operations on strings. + // If it's ASCII && one of the 4 main forms, then it's already normalized. + if (GlobalizationMode.Invariant || Ascii.IsValid(strInput)) { - // In Invariant mode we assume all characters are normalized. - // This is because we don't support any linguistic operation on the strings return strInput; } @@ -45,17 +44,10 @@ internal static bool TryNormalize(ReadOnlySpan source, Span destinat { CheckNormalizationForm(normalizationForm); - if (source.IsEmpty) - { - charsWritten = 0; - return true; - } - + // In Invariant mode we assume all characters are normalized because we don't support any linguistic operations on strings. + // If it's ASCII && one of the 4 main forms, then it's already normalized. if (GlobalizationMode.Invariant || Ascii.IsValid(source)) { - // In Invariant mode we assume all characters are normalized. - // This is because we don't support any linguistic operation on the strings - if (source.TryCopyTo(destination)) { charsWritten = source.Length; @@ -75,10 +67,10 @@ internal static int GetNormalizedLength(this ReadOnlySpan source, Normaliz { CheckNormalizationForm(normalizationForm); - if (GlobalizationMode.Invariant || source.IsEmpty || Ascii.IsValid(source)) + // In Invariant mode we assume all characters are normalized because we don't support any linguistic operations on strings. + // If it's ASCII && one of the 4 main forms, then it's already normalized. + if (GlobalizationMode.Invariant || Ascii.IsValid(source)) { - // In Invariant mode we assume all characters are normalized. - // This is because we don't support any linguistic operation on the strings return source.Length; } diff --git a/src/libraries/System.Private.CoreLib/src/System/String.cs b/src/libraries/System.Private.CoreLib/src/System/String.cs index 4995a972ecd8e8..daa4618fed3ccf 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; -using System.Buffers.Text; using System.Collections; using System.Collections.Generic; using System.ComponentModel; @@ -709,15 +708,6 @@ public bool IsNormalized() public bool IsNormalized(NormalizationForm normalizationForm) { - if (Ascii.IsValid(this)) - { - // If its ASCII && one of the 4 main forms, then its already normalized - if (normalizationForm == NormalizationForm.FormC || - normalizationForm == NormalizationForm.FormKC || - normalizationForm == NormalizationForm.FormD || - normalizationForm == NormalizationForm.FormKD) - return true; - } return Normalization.IsNormalized(this, normalizationForm); } @@ -728,15 +718,6 @@ public string Normalize() public string Normalize(NormalizationForm normalizationForm) { - if (Ascii.IsValid(this)) - { - // If its ASCII && one of the 4 main forms, then its already normalized - if (normalizationForm == NormalizationForm.FormC || - normalizationForm == NormalizationForm.FormKC || - normalizationForm == NormalizationForm.FormD || - normalizationForm == NormalizationForm.FormKD) - return this; - } return Normalization.Normalize(this, normalizationForm); } diff --git a/src/libraries/System.Private.CoreLib/src/System/StringNormalizationExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/StringNormalizationExtensions.cs index 7a94a853581aa2..8bb24afc2c9ee9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/StringNormalizationExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/StringNormalizationExtensions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Globalization; using System.Text; namespace System @@ -41,7 +42,7 @@ public static bool IsNormalized(this string strInput, NormalizationForm normaliz /// if the specified span of characters is in a normalized form; otherwise, . /// The specified character span contains an invalid code point or the normalization form is invalid. public static bool IsNormalized(this ReadOnlySpan source, NormalizationForm normalizationForm = NormalizationForm.FormC) => - System.Globalization.Normalization.IsNormalized(source, normalizationForm); + Normalization.IsNormalized(source, normalizationForm); /// /// Normalizes the specified string to the . @@ -77,7 +78,7 @@ public static string Normalize(this string strInput, NormalizationForm normaliza /// if the specified span of characters was successfully normalized; otherwise, . /// The specified character span contains an invalid code point or the normalization form is invalid. public static bool TryNormalize(this ReadOnlySpan source, Span destination, out int charsWritten, NormalizationForm normalizationForm = NormalizationForm.FormC) => - System.Globalization.Normalization.TryNormalize(source, destination, out charsWritten, normalizationForm); + Normalization.TryNormalize(source, destination, out charsWritten, normalizationForm); /// /// Gets the estimated length of the normalized form of the specified string in the . @@ -87,6 +88,6 @@ public static bool TryNormalize(this ReadOnlySpan source, Span desti /// The estimated length of the normalized form of the specified string. /// The specified character span contains an invalid code point or the normalization form is invalid. public static int GetNormalizedLength(this ReadOnlySpan source, NormalizationForm normalizationForm = NormalizationForm.FormC) => - System.Globalization.Normalization.GetNormalizedLength(source, normalizationForm); + Normalization.GetNormalizedLength(source, normalizationForm); } } From c7fc66783b7469152217d7b11585e8749dd7088a Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Tue, 10 Dec 2024 11:07:12 -0800 Subject: [PATCH 28/70] Remove Helper Method Frames (HMF) from Reflection (#110481) Convert RuntimeTypeHandle.GetFields() to QCall. Convert RuntimeTypeHandle.GetInterfaces() to QCall. Convert Signature.CompareSig() to QCall. Rename to Signature.CompareSig() to Signature.AreEqual(). --------- Co-authored-by: Adeel Mujahid <3840695+am11@users.noreply.github.com> --- .../System/Reflection/RuntimePropertyInfo.cs | 2 +- .../src/System/RuntimeHandles.cs | 60 +++++- .../src/System/RuntimeType.CoreCLR.cs | 76 +++----- src/coreclr/vm/callhelpers.cpp | 4 - src/coreclr/vm/ecalllist.h | 3 - src/coreclr/vm/qcallentrypoints.cpp | 3 + src/coreclr/vm/runtimehandles.cpp | 179 +++++++----------- src/coreclr/vm/runtimehandles.h | 11 +- src/coreclr/vm/siginfo.cpp | 1 - 9 files changed, 164 insertions(+), 175 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs index 2d542fb78ee101..51b1f84864b1a9 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs @@ -107,7 +107,7 @@ internal bool EqualsSig(RuntimePropertyInfo target) Debug.Assert(this != target); Debug.Assert(this.ReflectedType == target.ReflectedType); - return Signature.CompareSig(this.Signature, target.Signature); + return Signature.AreEqual(this.Signature, target.Signature); } internal BindingFlags BindingFlags => m_bindingFlags; #endregion diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index 236078b88c5679..a1907fd90df765 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -514,11 +514,45 @@ internal static IntroducedMethodEnumerator GetIntroducedMethods(RuntimeType type [MethodImpl(MethodImplOptions.InternalCall)] private static extern void GetNextIntroducedMethod(ref RuntimeMethodHandleInternal method); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool GetFields(RuntimeType type, IntPtr* result, int* count); + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeTypeHandle_GetFields")] + private static partial Interop.BOOL GetFields(MethodTable* pMT, Span data, ref int usedCount); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Type[]? GetInterfaces(RuntimeType type); + internal static bool GetFields(RuntimeType type, Span buffer, out int count) + { + Debug.Assert(!IsGenericVariable(type)); + + TypeHandle typeHandle = type.GetNativeTypeHandle(); + if (typeHandle.IsTypeDesc) + { + count = 0; + return true; + } + + int countLocal = buffer.Length; + bool success = GetFields(typeHandle.AsMethodTable(), buffer, ref countLocal) != Interop.BOOL.FALSE; + GC.KeepAlive(type); + count = countLocal; + return success; + } + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeTypeHandle_GetInterfaces")] + private static unsafe partial void GetInterfaces(MethodTable* pMT, ObjectHandleOnStack result); + + internal static Type[] GetInterfaces(RuntimeType type) + { + Debug.Assert(!IsGenericVariable(type)); + + TypeHandle typeHandle = type.GetNativeTypeHandle(); + if (typeHandle.IsTypeDesc) + { + return []; + } + + Type[] result = []; + GetInterfaces(typeHandle.AsMethodTable(), ObjectHandleOnStack.Create(ref result)); + GC.KeepAlive(type); + return result; + } [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeTypeHandle_GetConstraints")] private static partial void GetConstraints(QCallTypeHandle handle, ObjectHandleOnStack types); @@ -1859,10 +1893,11 @@ internal static int GetMDStreamVersion(RuntimeModule module) public int MDStreamVersion => GetMDStreamVersion(GetRuntimeModule()); } - internal sealed unsafe class Signature + internal sealed unsafe partial class Signature { #region FCalls [MemberNotNull(nameof(m_arguments))] + [MemberNotNull(nameof(m_declaringType))] [MemberNotNull(nameof(m_returnTypeORfieldType))] [MethodImpl(MethodImplOptions.InternalCall)] private extern void GetSignature( @@ -1875,7 +1910,7 @@ private extern void GetSignature( // Keep the layout in sync with SignatureNative in the VM // internal RuntimeType[] m_arguments; - internal RuntimeType? m_declaringType; + internal RuntimeType m_declaringType; internal RuntimeType m_returnTypeORfieldType; internal object? m_keepalive; internal void* m_sig; @@ -1923,8 +1958,17 @@ public Signature(void* pCorSig, int cCorSig, RuntimeType declaringType) internal RuntimeType ReturnType => m_returnTypeORfieldType; internal RuntimeType FieldType => m_returnTypeORfieldType; - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool CompareSig(Signature sig1, Signature sig2); + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Signature_AreEqual")] + private static partial Interop.BOOL AreEqual( + void* sig1, int csig1, QCallTypeHandle type1, + void* sig2, int csig2, QCallTypeHandle type2); + + internal static bool AreEqual(Signature sig1, Signature sig2) + { + return AreEqual( + sig1.m_sig, sig1.m_csig, new QCallTypeHandle(ref sig1.m_declaringType), + sig2.m_sig, sig2.m_csig, new QCallTypeHandle(ref sig2.m_declaringType)) != Interop.BOOL.FALSE; + } internal Type[] GetCustomModifiers(int parameterIndex, bool required) => GetCustomModifiersAtOffset(GetParameterOffset(parameterIndex), required); diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index b73f8263782e90..d1591d88036b28 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -826,30 +826,21 @@ private RuntimeFieldInfo[] PopulateFields(Filter filter) #endregion #region Populate Literal Fields on Interfaces + Type[] interfaces; if (ReflectedType.IsGenericParameter) { - Type[] interfaces = ReflectedType.BaseType!.GetInterfaces(); - - for (int i = 0; i < interfaces.Length; i++) - { - // Populate literal fields defined on any of the interfaces implemented by the declaring type - PopulateLiteralFields(filter, (RuntimeType)interfaces[i], ref list); - PopulateRtFields(filter, (RuntimeType)interfaces[i], ref list); - } + interfaces = ReflectedType.BaseType!.GetInterfaces(); } else { - Type[]? interfaces = RuntimeTypeHandle.GetInterfaces(ReflectedType); + interfaces = RuntimeTypeHandle.GetInterfaces(ReflectedType); + } - if (interfaces != null) - { - for (int i = 0; i < interfaces.Length; i++) - { - // Populate literal fields defined on any of the interfaces implemented by the declaring type - PopulateLiteralFields(filter, (RuntimeType)interfaces[i], ref list); - PopulateRtFields(filter, (RuntimeType)interfaces[i], ref list); - } - } + foreach (Type iface in interfaces) + { + // Populate literal fields defined on any of the interfaces implemented by the declaring type + PopulateLiteralFields(filter, (RuntimeType)iface, ref list); + PopulateRtFields(filter, (RuntimeType)iface, ref list); } #endregion @@ -858,25 +849,22 @@ private RuntimeFieldInfo[] PopulateFields(Filter filter) private unsafe void PopulateRtFields(Filter filter, RuntimeType declaringType, ref ListBuilder list) { - IntPtr* pResult = stackalloc IntPtr[64]; - int count = 64; - - if (!RuntimeTypeHandle.GetFields(declaringType, pResult, &count)) + Span result = stackalloc IntPtr[64]; + int count; + while (!RuntimeTypeHandle.GetFields(declaringType, result, out count)) { - fixed (IntPtr* pBigResult = new IntPtr[count]) - { - RuntimeTypeHandle.GetFields(declaringType, pBigResult, &count); - PopulateRtFields(filter, pBigResult, count, declaringType, ref list); - } + Debug.Assert(count > result.Length); + result = new IntPtr[count]; } - else if (count > 0) + + if (count > 0) { - PopulateRtFields(filter, pResult, count, declaringType, ref list); + PopulateRtFields(filter, result.Slice(0, count), declaringType, ref list); } } private unsafe void PopulateRtFields(Filter filter, - IntPtr* ppFieldHandles, int count, RuntimeType declaringType, ref ListBuilder list) + ReadOnlySpan fieldHandles, RuntimeType declaringType, ref ListBuilder list) { Debug.Assert(declaringType != null); Debug.Assert(ReflectedType != null); @@ -884,9 +872,9 @@ private unsafe void PopulateRtFields(Filter filter, bool needsStaticFieldForGeneric = declaringType.IsGenericType && !RuntimeTypeHandle.ContainsGenericVariables(declaringType); bool isInherited = declaringType != ReflectedType; - for (int i = 0; i < count; i++) + foreach (IntPtr handle in fieldHandles) { - RuntimeFieldHandleInternal runtimeFieldHandle = new RuntimeFieldHandleInternal(ppFieldHandles[i]); + RuntimeFieldHandleInternal runtimeFieldHandle = new RuntimeFieldHandleInternal(handle); if (filter.RequiresStringComparison()) { @@ -1017,23 +1005,19 @@ private RuntimeType[] PopulateInterfaces(Filter filter) if (!RuntimeTypeHandle.IsGenericVariable(declaringType)) { - Type[]? ifaces = RuntimeTypeHandle.GetInterfaces(declaringType); - - if (ifaces != null) + Type[] ifaces = RuntimeTypeHandle.GetInterfaces(declaringType); + foreach (Type iface in ifaces) { - for (int i = 0; i < ifaces.Length; i++) - { - RuntimeType interfaceType = (RuntimeType)ifaces[i]; - - if (filter.RequiresStringComparison()) - { - if (!filter.Match(RuntimeTypeHandle.GetUtf8Name(interfaceType))) - continue; - } + RuntimeType interfaceType = (RuntimeType)iface; - Debug.Assert(interfaceType.IsInterface); - list.Add(interfaceType); + if (filter.RequiresStringComparison()) + { + if (!filter.Match(RuntimeTypeHandle.GetUtf8Name(interfaceType))) + continue; } + + Debug.Assert(interfaceType.IsInterface); + list.Add(interfaceType); } if (ReflectedType.IsSZArray) diff --git a/src/coreclr/vm/callhelpers.cpp b/src/coreclr/vm/callhelpers.cpp index 3906d396c87f54..d926ff9d8d62c7 100644 --- a/src/coreclr/vm/callhelpers.cpp +++ b/src/coreclr/vm/callhelpers.cpp @@ -11,10 +11,6 @@ // To include declaration of "AppDomainTransitionExceptionFilter" #include "excep.h" - -// To include declaration of "SignatureNative" -#include "runtimehandles.h" - #include "invokeutil.h" #include "argdestination.h" diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index a934364e77591c..e1ba7a2aa4da91 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -91,8 +91,6 @@ FCFuncStart(gCOMTypeHandleFuncs) FCFuncElement("GetArrayRank", RuntimeTypeHandle::GetArrayRank) FCFuncElement("GetToken", RuntimeTypeHandle::GetToken) FCFuncElement("GetUtf8NameInternal", RuntimeTypeHandle::GetUtf8Name) - FCFuncElement("GetFields", RuntimeTypeHandle::GetFields) - FCFuncElement("GetInterfaces", RuntimeTypeHandle::GetInterfaces) FCFuncElement("GetAttributes", RuntimeTypeHandle::GetAttributes) FCFuncElement("GetNumVirtuals", RuntimeTypeHandle::GetNumVirtuals) FCFuncElement("CanCastTo", RuntimeTypeHandle::CanCastTo) @@ -135,7 +133,6 @@ FCFuncEnd() FCFuncStart(gSignatureNative) FCFuncElement("GetSignature", SignatureNative::GetSignature) - FCFuncElement("CompareSig", SignatureNative::CompareSig) FCFuncElement("GetParameterOffsetInternal", SignatureNative::GetParameterOffsetInternal) FCFuncElement("GetTypeParameterOffset", SignatureNative::GetTypeParameterOffset) FCFuncElement("GetCustomModifiersAtOffset", SignatureNative::GetCustomModifiersAtOffset) diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 56c67fc1e3282c..f59728794c8ede 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -125,12 +125,14 @@ static const Entry s_QCall[] = DllImportEntry(RuntimeTypeHandle_GetModuleSlow) DllImportEntry(RuntimeTypeHandle_GetNumVirtualsAndStaticVirtuals) DllImportEntry(RuntimeTypeHandle_GetMethodAt) + DllImportEntry(RuntimeTypeHandle_GetFields) DllImportEntry(RuntimeTypeHandle_VerifyInterfaceIsImplemented) DllImportEntry(RuntimeTypeHandle_GetInterfaceMethodImplementation) DllImportEntry(RuntimeTypeHandle_GetDeclaringTypeHandleForGenericVariable) DllImportEntry(RuntimeTypeHandle_GetDeclaringTypeHandle) DllImportEntry(RuntimeTypeHandle_IsVisible) DllImportEntry(RuntimeTypeHandle_ConstructName) + DllImportEntry(RuntimeTypeHandle_GetInterfaces) DllImportEntry(RuntimeTypeHandle_GetInstantiation) DllImportEntry(RuntimeTypeHandle_Instantiate) DllImportEntry(RuntimeTypeHandle_GetGenericTypeDefinition) @@ -186,6 +188,7 @@ static const Entry s_QCall[] = DllImportEntry(ModuleHandle_GetPEKind) DllImportEntry(ModuleHandle_GetDynamicMethod) DllImportEntry(AssemblyHandle_GetManifestModuleSlow) + DllImportEntry(Signature_AreEqual) DllImportEntry(TypeBuilder_DefineGenericParam) DllImportEntry(TypeBuilder_DefineType) DllImportEntry(TypeBuilder_SetParentType) diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index 932c69fa34eb5a..42f1772e37d8c7 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -455,55 +455,39 @@ extern "C" MethodDesc* QCALLTYPE RuntimeTypeHandle_GetMethodAt(MethodTable* pMT, return pRetMethod; } -FCIMPL3(FC_BOOL_RET, RuntimeTypeHandle::GetFields, ReflectClassBaseObject *pTypeUNSAFE, INT32 **result, INT32 *pCount) { - CONTRACTL { - FCALL_CHECK; - } - CONTRACTL_END; - - REFLECTCLASSBASEREF refType = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(pTypeUNSAFE); - if (refType == NULL) - FCThrowRes(kArgumentNullException, W("Arg_InvalidHandle")); - - TypeHandle typeHandle = refType->GetType(); - - if (!pCount || !result) - FCThrow(kArgumentNullException); +extern "C" BOOL QCALLTYPE RuntimeTypeHandle_GetFields(MethodTable* pMT, intptr_t* result, INT32* pCount) +{ + QCALL_CONTRACT; - if (typeHandle.IsGenericVariable()) - FCThrowRes(kArgumentException, W("Arg_InvalidHandle")); + _ASSERTE(pMT != NULL); + _ASSERTE(result != NULL); + _ASSERTE(pCount != NULL); - if (typeHandle.IsTypeDesc() || typeHandle.IsArray()) { - *pCount = 0; - FC_RETURN_BOOL(TRUE); - } + BOOL retVal = FALSE; - MethodTable *pMT= typeHandle.GetMethodTable(); - if (!pMT) - FCThrowRes(kArgumentException, W("Arg_InvalidHandle")); + BEGIN_QCALL; - BOOL retVal = FALSE; - HELPER_METHOD_FRAME_BEGIN_RET_1(refType); - // Check this approximation - we may be losing exact type information EncApproxFieldDescIterator fdIterator(pMT, ApproxFieldDescIterator::ALL_FIELDS, TRUE); INT32 count = (INT32)fdIterator.Count(); if (count > *pCount) { *pCount = count; + retVal = FALSE; } else { - for(INT32 i = 0; i < count; i ++) - result[i] = (INT32*)fdIterator.Next(); + for(INT32 i = 0; i < count; ++i) + result[i] = (intptr_t)fdIterator.Next(); *pCount = count; retVal = TRUE; } - HELPER_METHOD_FRAME_END(); - FC_RETURN_BOOL(retVal); + + END_QCALL; + + return retVal; } -FCIMPLEND extern "C" void QCALLTYPE RuntimeMethodHandle_ConstructInstantiation(MethodDesc * pMethod, DWORD format, QCall::StringHandleOnStack retString) { @@ -531,7 +515,50 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_ConstructName(QCall::TypeHandle pTyp END_QCALL; } -PTRARRAYREF CopyRuntimeTypeHandles(TypeHandle * prgTH, INT32 numTypeHandles, BinderClassID arrayElemType) +extern "C" void QCALLTYPE RuntimeTypeHandle_GetInterfaces(MethodTable* pMT, QCall::ObjectHandleOnStack result) +{ + QCALL_CONTRACT; + + _ASSERTE(pMT != NULL); + + BEGIN_QCALL; + + INT32 ifaceCount = pMT->GetNumInterfaces(); + // Allocate the array + if (ifaceCount > 0) + { + GCX_COOP(); + + struct + { + PTRARRAYREF Types; + } gc; + gc.Types = NULL; + GCPROTECT_BEGIN(gc); + TypeHandle arrayHandle = ClassLoader::LoadArrayTypeThrowing(TypeHandle(g_pRuntimeTypeClass), ELEMENT_TYPE_SZARRAY); + gc.Types = (PTRARRAYREF)AllocateSzArray(arrayHandle, ifaceCount); + + UINT i = 0; + + // Populate type array + MethodTable::InterfaceMapIterator it = pMT->IterateInterfaceMap(); + while (it.Next()) + { + _ASSERTE(i < (UINT)ifaceCount); + OBJECTREF refInterface = it.GetInterface(pMT)->GetManagedClassObject(); + gc.Types->SetAt(i, refInterface); + _ASSERTE(gc.Types->GetAt(i) != NULL); + i++; + } + + result.Set(gc.Types); + GCPROTECT_END(); + } + + END_QCALL; +} + +static PTRARRAYREF CopyRuntimeTypeHandles(TypeHandle * prgTH, INT32 numTypeHandles, BinderClassID arrayElemType) { CONTRACTL { THROWS; @@ -595,61 +622,6 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_GetConstraints(QCall::TypeHandle pTy return; } -FCIMPL1(PtrArray*, RuntimeTypeHandle::GetInterfaces, ReflectClassBaseObject *pTypeUNSAFE) { - CONTRACTL { - FCALL_CHECK; - } - CONTRACTL_END; - - REFLECTCLASSBASEREF refType = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(pTypeUNSAFE); - - if (refType == NULL) - FCThrowRes(kArgumentNullException, W("Arg_InvalidHandle")); - - TypeHandle typeHandle = refType->GetType(); - - if (typeHandle.IsGenericVariable()) - FCThrowRes(kArgumentNullException, W("Arg_InvalidHandle")); - - INT32 ifaceCount = 0; - - PTRARRAYREF refRetVal = NULL; - HELPER_METHOD_FRAME_BEGIN_RET_2(refRetVal, refType); - { - if (typeHandle.IsTypeDesc()) - { - ifaceCount = 0; - } - else - { - ifaceCount = typeHandle.GetMethodTable()->GetNumInterfaces(); - } - - // Allocate the array - if (ifaceCount > 0) - { - TypeHandle arrayHandle = ClassLoader::LoadArrayTypeThrowing(TypeHandle(g_pRuntimeTypeClass), ELEMENT_TYPE_SZARRAY); - refRetVal = (PTRARRAYREF)AllocateSzArray(arrayHandle, ifaceCount); - - // populate type array - UINT i = 0; - - MethodTable::InterfaceMapIterator it = typeHandle.GetMethodTable()->IterateInterfaceMap(); - while (it.Next()) - { - OBJECTREF refInterface = it.GetInterface(typeHandle.GetMethodTable())->GetManagedClassObject(); - refRetVal->SetAt(i, refInterface); - _ASSERTE(refRetVal->GetAt(i) != NULL); - i++; - } - } - } - HELPER_METHOD_FRAME_END(); - - return (PtrArray*)OBJECTREFToObject(refRetVal); -} -FCIMPLEND - FCIMPL1(INT32, RuntimeTypeHandle::GetAttributes, ReflectClassBaseObject *pTypeUNSAFE) { CONTRACTL { FCALL_CHECK; @@ -1856,32 +1828,25 @@ FCIMPL6(void, SignatureNative::GetSignature, } FCIMPLEND -FCIMPL2(FC_BOOL_RET, SignatureNative::CompareSig, SignatureNative* pLhsUNSAFE, SignatureNative* pRhsUNSAFE) +extern "C" BOOL QCALLTYPE Signature_AreEqual( + PCCOR_SIGNATURE sig1, INT32 cSig1, QCall::TypeHandle handle1, + PCCOR_SIGNATURE sig2, INT32 cSig2, QCall::TypeHandle handle2) { - FCALL_CONTRACT; + QCALL_CONTRACT; - INT32 ret = 0; + BOOL ret = FALSE; - struct - { - SIGNATURENATIVEREF pLhs; - SIGNATURENATIVEREF pRhs; - } gc; + BEGIN_QCALL; - gc.pLhs = (SIGNATURENATIVEREF)pLhsUNSAFE; - gc.pRhs = (SIGNATURENATIVEREF)pRhsUNSAFE; + ret = MetaSig::CompareMethodSigs( + sig1, cSig1, handle1.AsTypeHandle().GetModule(), NULL, + sig2, cSig2, handle2.AsTypeHandle().GetModule(), NULL, + FALSE); - HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); - { - ret = MetaSig::CompareMethodSigs( - gc.pLhs->GetCorSig(), gc.pLhs->GetCorSigSize(), gc.pLhs->GetModule(), NULL, - gc.pRhs->GetCorSig(), gc.pRhs->GetCorSigSize(), gc.pRhs->GetModule(), NULL, - FALSE); - } - HELPER_METHOD_FRAME_END(); - FC_RETURN_BOOL(ret); + END_QCALL; + + return ret; } -FCIMPLEND extern "C" void QCALLTYPE RuntimeMethodHandle_GetMethodInstantiation(MethodDesc * pMethod, QCall::ObjectHandleOnStack retTypes, BOOL fAsRuntimeTypeArray) { diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index 30683f5c2a8e70..5dd2f5ff5676e9 100644 --- a/src/coreclr/vm/runtimehandles.h +++ b/src/coreclr/vm/runtimehandles.h @@ -137,11 +137,8 @@ class RuntimeTypeHandle static FCDECL2(FC_BOOL_RET, CompareCanonicalHandles, PTR_ReflectClassBaseObject pLeft, PTR_ReflectClassBaseObject pRight); - static FCDECL1(PtrArray*, GetInterfaces, ReflectClassBaseObject *pType); - static FCDECL1(EnregisteredTypeHandle, GetElementTypeHandle, EnregisteredTypeHandle th); static FCDECL1(INT32, GetNumVirtuals, ReflectClassBaseObject *pType); - static FCDECL3(FC_BOOL_RET, GetFields, ReflectClassBaseObject *pType, INT32 **result, INT32 *pCount); static FCDECL1(MethodDesc *, GetFirstIntroducedMethod, ReflectClassBaseObject* pType); static FCDECL1(void, GetNextIntroducedMethod, MethodDesc **ppMethod); @@ -180,6 +177,7 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_MakeArray(QCall::TypeHandle pTypeHan extern "C" BOOL QCALLTYPE RuntimeTypeHandle_IsCollectible(QCall::TypeHandle pTypeHandle); extern "C" void QCALLTYPE RuntimeTypeHandle_PrepareMemberInfoCache(QCall::TypeHandle pMemberInfoCache); extern "C" void QCALLTYPE RuntimeTypeHandle_ConstructName(QCall::TypeHandle pTypeHandle, DWORD format, QCall::StringHandleOnStack retString); +extern "C" void QCALLTYPE RuntimeTypeHandle_GetInterfaces(MethodTable* pMT, QCall::ObjectHandleOnStack result); extern "C" BOOL QCALLTYPE RuntimeTypeHandle_IsVisible(QCall::TypeHandle pTypeHandle); extern "C" void QCALLTYPE RuntimeTypeHandle_GetInstantiation(QCall::TypeHandle pTypeHandle, QCall::ObjectHandleOnStack retType, BOOL fAsRuntimeTypeArray); extern "C" void QCALLTYPE RuntimeTypeHandle_Instantiate(QCall::TypeHandle pTypeHandle, TypeHandle * pInstArray, INT32 cInstArray, QCall::ObjectHandleOnStack retType); @@ -190,6 +188,7 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_GetAssemblySlow(QCall::ObjectHandleO extern "C" void QCALLTYPE RuntimeTypeHandle_GetModuleSlow(QCall::ObjectHandleOnStack type, QCall::ObjectHandleOnStack module); extern "C" INT32 QCALLTYPE RuntimeTypeHandle_GetNumVirtualsAndStaticVirtuals(QCall::TypeHandle pTypeHandle); extern "C" MethodDesc* QCALLTYPE RuntimeTypeHandle_GetMethodAt(MethodTable* pMT, INT32 slot); +extern "C" BOOL QCALLTYPE RuntimeTypeHandle_GetFields(MethodTable* pMT, intptr_t* result, INT32* pCount); extern "C" void QCALLTYPE RuntimeTypeHandle_VerifyInterfaceIsImplemented(QCall::TypeHandle pTypeHandle, QCall::TypeHandle pIFaceHandle); extern "C" MethodDesc* QCALLTYPE RuntimeTypeHandle_GetInterfaceMethodImplementation(QCall::TypeHandle pTypeHandle, QCall::TypeHandle pOwner, MethodDesc * pMD); extern "C" EnregisteredTypeHandle QCALLTYPE RuntimeTypeHandle_GetDeclaringTypeHandleForGenericVariable(EnregisteredTypeHandle pTypeHandle); @@ -328,8 +327,6 @@ class SignatureNative : public Object FieldDesc *pFieldDesc, ReflectMethodObject *pMethodUNSAFE, ReflectClassBaseObject *pDeclaringType); - static FCDECL2(FC_BOOL_RET, CompareSig, SignatureNative* pLhs, SignatureNative* pRhs); - static FCDECL3(INT32, GetParameterOffsetInternal, PCCOR_SIGNATURE sig, DWORD csig, INT32 parameterIndex); static FCDECL3(INT32, GetTypeParameterOffset, SignatureNative* pSig, INT32 offset, INT32 index); @@ -509,6 +506,10 @@ class SignatureNative : public Object MethodDesc* m_pMethod; }; +extern "C" BOOL QCALLTYPE Signature_AreEqual( + PCCOR_SIGNATURE sig1, INT32 cSig1, QCall::TypeHandle handle1, + PCCOR_SIGNATURE sig2, INT32 cSig2, QCall::TypeHandle handle2); + class ReflectionPointer : public Object { public: diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index d1154722b340c5..e7717aba1ce75a 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -16,7 +16,6 @@ #include "gcheaputilities.h" #include "field.h" #include "eeconfig.h" -#include "runtimehandles.h" // for SignatureNative #include "winwrap.h" #include #include "sigbuilder.h" From df0eaa2f9b8bfab371bc6011229ca5623a6b1341 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Tue, 10 Dec 2024 23:45:17 +0100 Subject: [PATCH 29/70] Speed up surrogate validation in string.Normalize (#110574) * Speed up surrogate validation in string.Normalize * Remove the else block --- .../System/Globalization/Normalization.Icu.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.Icu.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.Icu.cs index 076629fa32b841..cffa2705c380f6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.Icu.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.Icu.cs @@ -218,16 +218,20 @@ private static void ValidateArguments(ReadOnlySpan strInput, Normalization /// private static bool HasInvalidUnicodeSequence(ReadOnlySpan s) { - for (int i = 0; i < s.Length; i++) + const char Noncharacter = '\uFFFE'; + + int i = s.IndexOfAnyInRange(CharUnicodeInfo.HIGH_SURROGATE_START, Noncharacter); + + for (; (uint)i < (uint)s.Length; i++) { char c = s[i]; - if (c < '\ud800') + if (c < CharUnicodeInfo.HIGH_SURROGATE_START) { continue; } - if (c == '\uFFFE') + if (c == Noncharacter) { return true; } @@ -240,17 +244,14 @@ private static bool HasInvalidUnicodeSequence(ReadOnlySpan s) if (char.IsHighSurrogate(c)) { - if (i + 1 >= s.Length || !char.IsLowSurrogate(s[i + 1])) + if ((uint)(i + 1) >= (uint)s.Length || !char.IsLowSurrogate(s[i + 1])) { // A high surrogate at the end of the string or a high surrogate // not followed by a low surrogate return true; } - else - { - i++; // consume the low surrogate. - continue; - } + + i++; // consume the low surrogate. } } From 836b8686fc6cfe81f0638ce359c19b4421d031ec Mon Sep 17 00:00:00 2001 From: Eduardo Velarde <32459232+eduardo-vp@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:41:58 -0800 Subject: [PATCH 30/70] Use holding thread id in AwareLock to avoid orphaned lock crash (#107168) --- src/coreclr/vm/syncblk.cpp | 19 ++++++++++++++++--- src/coreclr/vm/syncblk.h | 15 ++++++++++++--- src/coreclr/vm/syncblk.inl | 5 +++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/coreclr/vm/syncblk.cpp b/src/coreclr/vm/syncblk.cpp index 868fffca0c0552..3fb9089bf31c54 100644 --- a/src/coreclr/vm/syncblk.cpp +++ b/src/coreclr/vm/syncblk.cpp @@ -1816,7 +1816,9 @@ BOOL ObjHeader::GetThreadOwningMonitorLock(DWORD *pThreadId, DWORD *pAcquisition _ASSERTE(psb->GetMonitor() != NULL); Thread* pThread = psb->GetMonitor()->GetHoldingThread(); - if(pThread == NULL) + // If the lock is orphaned during sync block creation, pThread would be assigned -1. + // Otherwise pThread would point to the owning thread if there was one or NULL if there wasn't. + if (pThread == NULL || pThread == (Thread*) -1) { *pThreadId = 0; *pAcquisitionCount = 0; @@ -1824,7 +1826,12 @@ BOOL ObjHeader::GetThreadOwningMonitorLock(DWORD *pThreadId, DWORD *pAcquisition } else { - *pThreadId = pThread->GetThreadId(); + // However, the lock might get orphaned after the sync block is created. + // Therefore accessing pThread is not safe and pThread->GetThreadId() shouldn't be used. + // The thread id can be obtained from the monitor, which would be the id of the thread that owned the lock. + // Notice this id now could have been reused for a different thread, + // but this way we avoid crashing the process and orphaned locks shouldn't be expected to work correctly anyway. + *pThreadId = psb->GetMonitor()->GetHoldingThreadId(); *pAcquisitionCount = psb->GetMonitor()->GetRecursionLevel(); return TRUE; } @@ -2190,20 +2197,23 @@ SyncBlock *ObjHeader::GetSyncBlock() _ASSERTE(lockThreadId != 0); Thread *pThread = g_pThinLockThreadIdDispenser->IdToThreadWithValidation(lockThreadId); + DWORD threadId = 0; SIZE_T osThreadId; if (pThread == NULL) { // The lock is orphaned. pThread = (Thread*) -1; + threadId = -1; osThreadId = (SIZE_T)-1; } else { + threadId = pThread->GetThreadId(); osThreadId = pThread->GetOSThreadId64(); } - syncBlock->InitState(recursionLevel + 1, pThread, osThreadId); + syncBlock->InitState(recursionLevel + 1, pThread, threadId, osThreadId); } } else if ((bits & BIT_SBLK_IS_HASHCODE) != 0) @@ -2367,6 +2377,7 @@ void AwareLock::Enter() { // We get here if we successfully acquired the mutex. m_HoldingThread = pCurThread; + m_HoldingThreadId = pCurThread->GetThreadId(); m_HoldingOSThreadId = pCurThread->GetOSThreadId64(); m_Recursion = 1; @@ -2430,6 +2441,7 @@ BOOL AwareLock::TryEnter(INT32 timeOut) { // We get here if we successfully acquired the mutex. m_HoldingThread = pCurThread; + m_HoldingThreadId = pCurThread->GetThreadId(); m_HoldingOSThreadId = pCurThread->GetOSThreadId64(); m_Recursion = 1; @@ -2709,6 +2721,7 @@ BOOL AwareLock::EnterEpilogHelper(Thread* pCurThread, INT32 timeOut) } m_HoldingThread = pCurThread; + m_HoldingThreadId = pCurThread->GetThreadId(); m_HoldingOSThreadId = pCurThread->GetOSThreadId64(); m_Recursion = 1; diff --git a/src/coreclr/vm/syncblk.h b/src/coreclr/vm/syncblk.h index 7cdf92cdda8786..aa76cd3056b887 100644 --- a/src/coreclr/vm/syncblk.h +++ b/src/coreclr/vm/syncblk.h @@ -436,6 +436,7 @@ class AwareLock ULONG m_Recursion; PTR_Thread m_HoldingThread; + DWORD m_HoldingThreadId; SIZE_T m_HoldingOSThreadId; LONG m_TransientPrecious; @@ -459,6 +460,7 @@ class AwareLock // PreFAST has trouble with initializing a NULL PTR_Thread. m_HoldingThread(NULL), #endif // DACCESS_COMPILE + m_HoldingThreadId(0), m_HoldingOSThreadId(0), m_TransientPrecious(0), m_dwSyncIndex(indx), @@ -523,19 +525,26 @@ class AwareLock return m_HoldingThread; } + DWORD GetHoldingThreadId() const + { + LIMITED_METHOD_CONTRACT; + return m_HoldingThreadId; + } + private: void ResetWaiterStarvationStartTime(); void RecordWaiterStarvationStartTime(); bool ShouldStopPreemptingWaiters() const; private: // friend access is required for this unsafe function - void InitializeToLockedWithNoWaiters(ULONG recursionLevel, PTR_Thread holdingThread, SIZE_T holdingOSThreadId) + void InitializeToLockedWithNoWaiters(ULONG recursionLevel, PTR_Thread holdingThread, DWORD holdingThreadId, SIZE_T holdingOSThreadId) { WRAPPER_NO_CONTRACT; m_lockState.InitializeToLockedWithNoWaiters(); m_Recursion = recursionLevel; m_HoldingThread = holdingThread; + m_HoldingThreadId = holdingThreadId; m_HoldingOSThreadId = holdingOSThreadId; } @@ -1270,10 +1279,10 @@ class SyncBlock // This should ONLY be called when initializing a SyncBlock (i.e. ONLY from // ObjHeader::GetSyncBlock()), otherwise we'll have a race condition. // - void InitState(ULONG recursionLevel, PTR_Thread holdingThread, SIZE_T holdingOSThreadId) + void InitState(ULONG recursionLevel, PTR_Thread holdingThread, DWORD holdingThreadId, SIZE_T holdingOSThreadId) { WRAPPER_NO_CONTRACT; - m_Monitor.InitializeToLockedWithNoWaiters(recursionLevel, holdingThread, holdingOSThreadId); + m_Monitor.InitializeToLockedWithNoWaiters(recursionLevel, holdingThread, holdingThreadId, holdingOSThreadId); } #if defined(ENABLE_CONTRACTS_IMPL) diff --git a/src/coreclr/vm/syncblk.inl b/src/coreclr/vm/syncblk.inl index 97b479168f1462..4cad064c8149de 100644 --- a/src/coreclr/vm/syncblk.inl +++ b/src/coreclr/vm/syncblk.inl @@ -479,6 +479,7 @@ FORCEINLINE bool AwareLock::TryEnterHelper(Thread* pCurThread) if (m_lockState.InterlockedTryLock()) { m_HoldingThread = pCurThread; + m_HoldingThreadId = pCurThread->GetThreadId(); m_HoldingOSThreadId = pCurThread->GetOSThreadId64(); m_Recursion = 1; return true; @@ -525,6 +526,7 @@ FORCEINLINE AwareLock::EnterHelperResult AwareLock::TryEnterBeforeSpinLoopHelper // Lock was acquired and the spinner was not registered m_HoldingThread = pCurThread; + m_HoldingThreadId = pCurThread->GetThreadId(); m_HoldingOSThreadId = pCurThread->GetOSThreadId64(); m_Recursion = 1; return EnterHelperResult_Entered; @@ -557,6 +559,7 @@ FORCEINLINE AwareLock::EnterHelperResult AwareLock::TryEnterInsideSpinLoopHelper // Lock was acquired and spinner was unregistered m_HoldingThread = pCurThread; + m_HoldingThreadId = pCurThread->GetThreadId(); m_HoldingOSThreadId = pCurThread->GetOSThreadId64(); m_Recursion = 1; return EnterHelperResult_Entered; @@ -580,6 +583,7 @@ FORCEINLINE bool AwareLock::TryEnterAfterSpinLoopHelper(Thread *pCurThread) // Spinner was unregistered and the lock was acquired m_HoldingThread = pCurThread; + m_HoldingThreadId = pCurThread->GetThreadId(); m_HoldingOSThreadId = pCurThread->GetOSThreadId64(); m_Recursion = 1; return true; @@ -699,6 +703,7 @@ FORCEINLINE AwareLock::LeaveHelperAction AwareLock::LeaveHelper(Thread* pCurThre if (--m_Recursion == 0) { m_HoldingThread = NULL; + m_HoldingThreadId = 0; m_HoldingOSThreadId = 0; // Clear lock bit and determine whether we must signal a waiter to wake From 6d18e0dd96ab5c1d79eeff18b2e5b9f73ca57a75 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 11 Dec 2024 09:48:52 +0800 Subject: [PATCH 31/70] Cleanup some dead code (#110579) * pal/misc.h * COMCharacter * MAX_CACHE_LINE_SIZE * clr/fs --- src/coreclr/inc/clr/fs.h | 13 ------ src/coreclr/inc/clr/fs/path.h | 4 -- src/coreclr/inc/clr/str.h | 27 ------------ src/coreclr/pal/src/include/pal/misc.h | 54 ------------------------ src/coreclr/pal/src/init/pal.cpp | 1 - src/coreclr/pal/src/misc/fmtmessage.cpp | 1 - src/coreclr/pal/src/misc/perftrace.cpp | 1 - src/coreclr/pal/src/misc/time.cpp | 1 - src/coreclr/vm/util.cpp | 56 ------------------------- src/coreclr/vm/util.hpp | 18 -------- 10 files changed, 176 deletions(-) delete mode 100644 src/coreclr/inc/clr/fs.h delete mode 100644 src/coreclr/inc/clr/str.h delete mode 100644 src/coreclr/pal/src/include/pal/misc.h diff --git a/src/coreclr/inc/clr/fs.h b/src/coreclr/inc/clr/fs.h deleted file mode 100644 index d7efac04cf5fb3..00000000000000 --- a/src/coreclr/inc/clr/fs.h +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// - -// -// This header will include all headers relating to file system functionality. - -#ifndef _clr_fs_h_ -#define _clr_fs_h_ - -#include "fs/path.h" - -#endif // _clr_fs_h_ diff --git a/src/coreclr/inc/clr/fs/path.h b/src/coreclr/inc/clr/fs/path.h index efc21a5cdd4392..1f5454c9ac9252 100644 --- a/src/coreclr/inc/clr/fs/path.h +++ b/src/coreclr/inc/clr/fs/path.h @@ -10,10 +10,6 @@ #include "clrtypes.h" -#include "strsafe.h" - -#include "clr/str.h" - namespace clr { namespace fs diff --git a/src/coreclr/inc/clr/str.h b/src/coreclr/inc/clr/str.h deleted file mode 100644 index d09a965fed6816..00000000000000 --- a/src/coreclr/inc/clr/str.h +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// - -// -// This header provides general standard string services. -// - -#ifndef _clr_str_h_ -#define _clr_str_h_ - -namespace clr -{ - namespace str - { - //----------------------------------------------------------------------------------------- - // Returns true if the provided string is a null pointer or the empty string. - static inline bool - IsNullOrEmpty(LPCWSTR wzStr) - { - return wzStr == nullptr || *wzStr == W('\0'); - } - } -} - -#endif // _clr_str_h_ - diff --git a/src/coreclr/pal/src/include/pal/misc.h b/src/coreclr/pal/src/include/pal/misc.h deleted file mode 100644 index ffa6448ed7d308..00000000000000 --- a/src/coreclr/pal/src/include/pal/misc.h +++ /dev/null @@ -1,54 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*++ - - - -Module Name: - - include/pal/misc.h - -Abstract: - Header file for the initialization and clean up functions - for the misc Win32 functions - - - ---*/ - -#ifndef __MISC_H_ -#define __MISC_H_ - -#ifdef __cplusplus -extern "C" -{ -#endif // __cplusplus - -/*++ -Function : - MsgBoxInitialize - - Initialize the critical sections. - -Return value: - TRUE if initialize succeeded - FALSE otherwise - ---*/ -BOOL MsgBoxInitialize( void ); - -/*++ -Function : - MsgBoxCleanup - - Deletes the critical sections. - ---*/ -void MsgBoxCleanup( void ); - -#ifdef __cplusplus -} -#endif // __cplusplus - -#endif /* __MISC_H_ */ diff --git a/src/coreclr/pal/src/init/pal.cpp b/src/coreclr/pal/src/init/pal.cpp index a6bd6f01f1dff0..8dcf3a1aee4830 100644 --- a/src/coreclr/pal/src/init/pal.cpp +++ b/src/coreclr/pal/src/init/pal.cpp @@ -30,7 +30,6 @@ SET_DEFAULT_DEBUG_CHANNEL(PAL); // some headers have code with asserts, so do th #include "../thread/procprivate.hpp" #include "pal/module.h" #include "pal/virtual.h" -#include "pal/misc.h" #include "pal/environ.h" #include "pal/utils.h" #include "pal/debug.h" diff --git a/src/coreclr/pal/src/misc/fmtmessage.cpp b/src/coreclr/pal/src/misc/fmtmessage.cpp index 0598914b06cb51..cfedd815da3cae 100644 --- a/src/coreclr/pal/src/misc/fmtmessage.cpp +++ b/src/coreclr/pal/src/misc/fmtmessage.cpp @@ -23,7 +23,6 @@ Revision History: #include "pal/dbgmsg.h" #include "pal/critsect.h" #include "pal/module.h" -#include "pal/misc.h" #include "errorstrings.h" diff --git a/src/coreclr/pal/src/misc/perftrace.cpp b/src/coreclr/pal/src/misc/perftrace.cpp index 691c9048cb5729..2f5072a8da2375 100644 --- a/src/coreclr/pal/src/misc/perftrace.cpp +++ b/src/coreclr/pal/src/misc/perftrace.cpp @@ -27,7 +27,6 @@ Module Name: #include "pal/perftrace.h" #include "pal/dbgmsg.h" #include "pal/cruntime.h" -#include "pal/misc.h" /* Standard headers */ #include diff --git a/src/coreclr/pal/src/misc/time.cpp b/src/coreclr/pal/src/misc/time.cpp index 7d78ae930c3979..2774cb7f125b75 100644 --- a/src/coreclr/pal/src/misc/time.cpp +++ b/src/coreclr/pal/src/misc/time.cpp @@ -19,7 +19,6 @@ Module Name: #include "pal/palinternal.h" #include "pal/dbgmsg.h" -#include "pal/misc.h" #include #include diff --git a/src/coreclr/vm/util.cpp b/src/coreclr/vm/util.cpp index c279c50bf2b981..331a430436e25b 100644 --- a/src/coreclr/vm/util.cpp +++ b/src/coreclr/vm/util.cpp @@ -1889,62 +1889,6 @@ int __cdecl stricmpUTF8(const char* szStr1, const char* szStr2) } #ifndef DACCESS_COMPILE -// -// -// COMCharacter and Helper functions -// -// - -#ifndef TARGET_UNIX -/*============================GetCharacterInfoHelper============================ -**Determines character type info (digit, whitespace, etc) for the given char. -**Args: c is the character on which to operate. -** CharInfoType is one of CT_CTYPE1, CT_CTYPE2, CT_CTYPE3 and specifies the type -** of information being requested. -**Returns: The bitmask returned by GetStringTypeEx. The caller needs to know -** how to interpret this. -**Exceptions: ArgumentException if GetStringTypeEx fails. -==============================================================================*/ -INT32 GetCharacterInfoHelper(WCHAR c, INT32 CharInfoType) -{ - WRAPPER_NO_CONTRACT; - - unsigned short result=0; - if (!GetStringTypeEx(LOCALE_USER_DEFAULT, CharInfoType, &(c), 1, &result)) { - _ASSERTE(!"This should not happen, verify the arguments passed to GetStringTypeEx()"); - } - return(INT32)result; -} -#endif // !TARGET_UNIX - -/*==============================nativeIsWhiteSpace============================== -**The locally available version of IsWhiteSpace. Designed to be called by other -**native methods. The work is mostly done by GetCharacterInfoHelper -**Args: c -- the character to check. -**Returns: true if c is whitespace, false otherwise. -**Exceptions: Only those thrown by GetCharacterInfoHelper. -==============================================================================*/ -BOOL COMCharacter::nativeIsWhiteSpace(WCHAR c) -{ - WRAPPER_NO_CONTRACT; - -#ifndef TARGET_UNIX - if (c <= (WCHAR) 0x7F) // common case - { - BOOL result = (c == ' ') || (c == '\r') || (c == '\n') || (c == '\t') || (c == '\f') || (c == (WCHAR) 0x0B); - - ASSERT(result == ((GetCharacterInfoHelper(c, CT_CTYPE1) & C1_SPACE)!=0)); - - return result; - } - - // GetCharacterInfoHelper costs around 160 instructions - return((GetCharacterInfoHelper(c, CT_CTYPE1) & C1_SPACE)!=0); -#else // !TARGET_UNIX - return iswspace(c); -#endif // !TARGET_UNIX -} - BOOL RuntimeFileNotFound(HRESULT hr) { LIMITED_METHOD_CONTRACT; diff --git a/src/coreclr/vm/util.hpp b/src/coreclr/vm/util.hpp index a025e86e0768b0..e50c42153b4139 100644 --- a/src/coreclr/vm/util.hpp +++ b/src/coreclr/vm/util.hpp @@ -18,13 +18,6 @@ #include "posterror.h" #include -// Hot cache lines need to be aligned to cache line size to improve performance -#if defined(TARGET_ARM64) -#define MAX_CACHE_LINE_SIZE 128 -#else -#define MAX_CACHE_LINE_SIZE 64 -#endif - #ifndef DACCESS_COMPILE #if defined(TARGET_WINDOWS) && defined(TARGET_ARM64) // Flag to check if atomics feature is available on @@ -800,17 +793,6 @@ BOOL DbgIsExecutable(LPVOID lpMem, SIZE_T length); int GetRandomInt(int maxVal); -// -// -// COMCHARACTER -// -// -class COMCharacter { -public: - //These are here for support from native code. They are never called from our managed classes. - static BOOL nativeIsWhiteSpace(WCHAR c); -}; - // ====================================================================================== // Simple, reusable 100ns timer for normalizing ticks. For use in Q/FCalls to avoid discrepency with // tick frequency between native and managed. From 2579b1ef577f578e9e70aa1ac2f94c3c99369b8c Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Tue, 10 Dec 2024 21:56:58 -0800 Subject: [PATCH 32/70] Revert "[browser] fix code gen overflow (#110539)" (#110599) This reverts commit 1fa2361dced0acec1a204e559bbb5677f863eeb7. --- .../gen/JSImportGenerator/JSSignatureContext.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSSignatureContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSSignatureContext.cs index f59bb25bca0f51..3f058a5c7eeb41 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSSignatureContext.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSSignatureContext.cs @@ -48,7 +48,6 @@ public static JSSignatureContext Create( // there could be multiple method signatures with the same name, get unique signature name uint hash = 17; - int typesHash; unchecked { foreach (var param in sigContext.ElementTypeInformation) @@ -58,8 +57,8 @@ public static JSSignatureContext Create( foreach (char c in param.ManagedType.FullTypeName) hash = hash * 31 + c; } - typesHash = (int)(hash & int.MaxValue); }; + int typesHash = Math.Abs((int)hash); var fullName = $"{method.ContainingType.ToDisplayString()}.{method.Name}"; string qualifiedName = GetFullyQualifiedMethodName(env, method); From 8708c3d1995481c7dbe91e1a307e493cec021c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 11 Dec 2024 07:14:01 +0100 Subject: [PATCH 33/70] Share threadpool configuration (#110469) Threadpool can be configured using AppContext switches or environment variables. The environment variable configuration uses a roundabout mechanism that makes it CoreCLR specific. Move the configuration to managed code that is shared with all runtimes. This should have equivalent behavior in less than half the lines of code unless I'm missing something. --- .../System.Private.CoreLib.csproj | 1 - .../System/Threading/ThreadPool.CoreCLR.cs | 74 ------------------- src/coreclr/inc/clrconfigvalues.h | 30 -------- src/coreclr/vm/CMakeLists.txt | 2 - src/coreclr/vm/ceemain.cpp | 1 - src/coreclr/vm/comthreadpool.cpp | 70 ------------------ src/coreclr/vm/comthreadpool.h | 17 ----- src/coreclr/vm/corelib.cpp | 1 - src/coreclr/vm/ecalllist.h | 5 -- src/coreclr/vm/qcallentrypoints.cpp | 1 - .../src/System/AppContextConfigHelper.cs | 65 ++++++++++++++++ .../PortableThreadPool.GateThread.cs | 4 +- .../PortableThreadPool.HillClimbing.cs | 28 +++---- .../PortableThreadPool.WorkerThread.cs | 3 +- .../System/Threading/PortableThreadPool.cs | 10 +-- .../src/System/Threading/ThreadPool.Unix.cs | 4 - .../System/Threading/ThreadPool.Windows.cs | 4 - 17 files changed, 85 insertions(+), 235 deletions(-) delete mode 100644 src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs delete mode 100644 src/coreclr/vm/comthreadpool.cpp delete mode 100644 src/coreclr/vm/comthreadpool.h diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index f4109a290f9206..70e02de9c08a69 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -231,7 +231,6 @@ - diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs deleted file mode 100644 index e13fdeef311194..00000000000000 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================================= -** -** -** -** Purpose: Class for creating and managing a threadpool -** -** -=============================================================================*/ - -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.ConstrainedExecution; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; - -namespace System.Threading -{ - - public static partial class ThreadPool - { - internal static bool EnsureConfigInitialized() - { - return s_initialized; - } - - private static readonly bool s_initialized = InitializeConfig(); - - private static unsafe bool InitializeConfig() - { - int configVariableIndex = 1; - while (true) - { - int nextConfigVariableIndex = - GetNextConfigUInt32Value( - configVariableIndex, - out uint configValue, - out bool isBoolean, - out char* appContextConfigNameUnsafe); - if (nextConfigVariableIndex < 0) - { - break; - } - - Debug.Assert(nextConfigVariableIndex > configVariableIndex); - configVariableIndex = nextConfigVariableIndex; - - Debug.Assert(appContextConfigNameUnsafe != null); - - var appContextConfigName = new string(appContextConfigNameUnsafe); - if (isBoolean) - { - AppContext.SetSwitch(appContextConfigName, configValue != 0); - } - else - { - AppContext.SetData(appContextConfigName, configValue); - } - } - - return true; - } - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern unsafe int GetNextConfigUInt32Value( - int configVariableIndex, - out uint configValue, - out bool isBoolean, - out char* appContextConfigName); - - } -} diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index d0a76bc85bcefe..61dcad88227714 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -516,36 +516,6 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_Thread_UseAllCpuGroups, W("Thread_UseAllCpuGro RETAIL_CONFIG_DWORD_INFO(EXTERNAL_Thread_AssignCpuGroups, W("Thread_AssignCpuGroups"), 1, "Specifies whether to automatically distribute threads created by the CLR across CPU Groups. Effective only when Thread_UseAllCpuGroups and GCCpuGroup are enabled.") RETAIL_CONFIG_DWORD_INFO_EX(EXTERNAL_ProcessorCount, W("PROCESSOR_COUNT"), 0, "Specifies the number of processors available for the process, which is returned by Environment.ProcessorCount", CLRConfig::LookupOptions::ParseIntegerAsBase10) -/// -/// Threadpool -/// -RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_ForceMinWorkerThreads, W("ThreadPool_ForceMinWorkerThreads"), 0, "Overrides the MinThreads setting for the ThreadPool worker pool") -RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_ForceMaxWorkerThreads, W("ThreadPool_ForceMaxWorkerThreads"), 0, "Overrides the MaxThreads setting for the ThreadPool worker pool") -RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_DisableStarvationDetection, W("ThreadPool_DisableStarvationDetection"), 0, "Disables the ThreadPool feature that forces new threads to be added when workitems run for too long") -RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_DebugBreakOnWorkerStarvation, W("ThreadPool_DebugBreakOnWorkerStarvation"), 0, "Breaks into the debugger if the ThreadPool detects work queue starvation") -RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_EnableWorkerTracking, W("ThreadPool_EnableWorkerTracking"), 0, "Enables extra expensive tracking of how many workers threads are working simultaneously") -#ifdef TARGET_ARM64 -// Spinning scheme is currently different on ARM64 -RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_UnfairSemaphoreSpinLimit, W("ThreadPool_UnfairSemaphoreSpinLimit"), 0x32, "Maximum number of spins per processor a thread pool worker thread performs before waiting for work") -#else // !TARGET_ARM64 -RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_UnfairSemaphoreSpinLimit, W("ThreadPool_UnfairSemaphoreSpinLimit"), 0x46, "Maximum number of spins a thread pool worker thread performs before waiting for work") -#endif // TARGET_ARM64 - -RETAIL_CONFIG_DWORD_INFO(INTERNAL_HillClimbing_Disable, W("HillClimbing_Disable"), 0, "Disables hill climbing for thread adjustments in the thread pool"); -RETAIL_CONFIG_DWORD_INFO(INTERNAL_HillClimbing_WavePeriod, W("HillClimbing_WavePeriod"), 4, ""); -RETAIL_CONFIG_DWORD_INFO(INTERNAL_HillClimbing_TargetSignalToNoiseRatio, W("HillClimbing_TargetSignalToNoiseRatio"), 300, ""); -RETAIL_CONFIG_DWORD_INFO(INTERNAL_HillClimbing_ErrorSmoothingFactor, W("HillClimbing_ErrorSmoothingFactor"), 1, ""); -RETAIL_CONFIG_DWORD_INFO(INTERNAL_HillClimbing_WaveMagnitudeMultiplier, W("HillClimbing_WaveMagnitudeMultiplier"), 100, ""); -RETAIL_CONFIG_DWORD_INFO(INTERNAL_HillClimbing_MaxWaveMagnitude, W("HillClimbing_MaxWaveMagnitude"), 20, ""); -RETAIL_CONFIG_DWORD_INFO(INTERNAL_HillClimbing_WaveHistorySize, W("HillClimbing_WaveHistorySize"), 8, ""); -RETAIL_CONFIG_DWORD_INFO(INTERNAL_HillClimbing_Bias, W("HillClimbing_Bias"), 15, "The 'cost' of a thread. 0 means drive for increased throughput regardless of thread count; higher values bias more against higher thread counts."); -RETAIL_CONFIG_DWORD_INFO(INTERNAL_HillClimbing_MaxChangePerSecond, W("HillClimbing_MaxChangePerSecond"), 4, ""); -RETAIL_CONFIG_DWORD_INFO(INTERNAL_HillClimbing_MaxChangePerSample, W("HillClimbing_MaxChangePerSample"), 20, ""); -RETAIL_CONFIG_DWORD_INFO(INTERNAL_HillClimbing_MaxSampleErrorPercent, W("HillClimbing_MaxSampleErrorPercent"), 15, ""); -RETAIL_CONFIG_DWORD_INFO(INTERNAL_HillClimbing_SampleIntervalLow, W("HillClimbing_SampleIntervalLow"), 10, ""); -RETAIL_CONFIG_DWORD_INFO(INTERNAL_HillClimbing_SampleIntervalHigh, W("HillClimbing_SampleIntervalHigh"), 200, ""); -RETAIL_CONFIG_DWORD_INFO(INTERNAL_HillClimbing_GainExponent, W("HillClimbing_GainExponent"), 200, "The exponent to apply to the gain, times 100. 100 means to use linear gain, higher values will enhance large moves and damp small ones."); - /// /// Tiered Compilation /// diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index 3033d205882218..7254075b562de4 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -302,7 +302,6 @@ set(VM_SOURCES_WKS comdynamic.cpp commodule.cpp comsynchronizable.cpp - comthreadpool.cpp comutilnative.cpp comwaithandle.cpp coreassemblyspec.cpp @@ -402,7 +401,6 @@ set(VM_HEADERS_WKS comdynamic.h commodule.h comsynchronizable.h - comthreadpool.h comutilnative.h comwaithandle.h customattribute.h diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index 3e38d60a538fee..2784196ab2e959 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -151,7 +151,6 @@ #include "../dlls/mscorrc/resource.h" #include "util.hpp" #include "shimload.h" -#include "comthreadpool.h" #include "posterror.h" #include "virtualcallstub.h" #include "strongnameinternal.h" diff --git a/src/coreclr/vm/comthreadpool.cpp b/src/coreclr/vm/comthreadpool.cpp deleted file mode 100644 index daa8d48d3a3078..00000000000000 --- a/src/coreclr/vm/comthreadpool.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#include "common.h" - -#include "comthreadpool.h" -#include "eeconfig.h" - -/*****************************************************************************************************/ -// Enumerates some runtime config variables that are used by CoreLib for initialization. The config variable index should start -// at 0 to begin enumeration. If a config variable at or after the specified config variable index is configured, returns the -// next config variable index to pass in on the next call to continue enumeration. -FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, - INT32 configVariableIndex, - UINT32 *configValueRef, - BOOL *isBooleanRef, - LPCWSTR *appContextConfigNameRef) -{ - FCALL_CONTRACT; - _ASSERTE(configVariableIndex >= 0); - _ASSERTE(configValueRef != NULL); - _ASSERTE(isBooleanRef != NULL); - _ASSERTE(appContextConfigNameRef != NULL); - - auto TryGetConfig = - [=](const CLRConfig::ConfigDWORDInfo &configInfo, bool isBoolean, const WCHAR *appContextConfigName) -> bool - { - bool wasNotConfigured = true; - *configValueRef = CLRConfig::GetConfigValue(configInfo, &wasNotConfigured); - if (wasNotConfigured) - { - return false; - } - - *isBooleanRef = isBoolean; - *appContextConfigNameRef = appContextConfigName; - return true; - }; - - switch (configVariableIndex) - { - case 1: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_ForceMinWorkerThreads, false, W("System.Threading.ThreadPool.MinThreads"))) { return 2; } FALLTHROUGH; - case 2: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_ForceMaxWorkerThreads, false, W("System.Threading.ThreadPool.MaxThreads"))) { return 3; } FALLTHROUGH; - case 3: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_DisableStarvationDetection, true, W("System.Threading.ThreadPool.DisableStarvationDetection"))) { return 4; } FALLTHROUGH; - case 4: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_DebugBreakOnWorkerStarvation, true, W("System.Threading.ThreadPool.DebugBreakOnWorkerStarvation"))) { return 5; } FALLTHROUGH; - case 5: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_UnfairSemaphoreSpinLimit, false, W("System.Threading.ThreadPool.UnfairSemaphoreSpinLimit"))) { return 6; } FALLTHROUGH; - - case 6: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_Disable, true, W("System.Threading.ThreadPool.HillClimbing.Disable"))) { return 7; } FALLTHROUGH; - case 7: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WavePeriod, false, W("System.Threading.ThreadPool.HillClimbing.WavePeriod"))) { return 8; } FALLTHROUGH; - case 8: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_TargetSignalToNoiseRatio, false, W("System.Threading.ThreadPool.HillClimbing.TargetSignalToNoiseRatio"))) { return 9; } FALLTHROUGH; - case 9: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_ErrorSmoothingFactor, false, W("System.Threading.ThreadPool.HillClimbing.ErrorSmoothingFactor"))) { return 10; } FALLTHROUGH; - case 10: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WaveMagnitudeMultiplier, false, W("System.Threading.ThreadPool.HillClimbing.WaveMagnitudeMultiplier"))) { return 11; } FALLTHROUGH; - case 11: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxWaveMagnitude, false, W("System.Threading.ThreadPool.HillClimbing.MaxWaveMagnitude"))) { return 12; } FALLTHROUGH; - case 12: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WaveHistorySize, false, W("System.Threading.ThreadPool.HillClimbing.WaveHistorySize"))) { return 13; } FALLTHROUGH; - case 13: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_Bias, false, W("System.Threading.ThreadPool.HillClimbing.Bias"))) { return 14; } FALLTHROUGH; - case 14: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSecond, false, W("System.Threading.ThreadPool.HillClimbing.MaxChangePerSecond"))) { return 15; } FALLTHROUGH; - case 15: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSample, false, W("System.Threading.ThreadPool.HillClimbing.MaxChangePerSample"))) { return 16; } FALLTHROUGH; - case 16: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxSampleErrorPercent, false, W("System.Threading.ThreadPool.HillClimbing.MaxSampleErrorPercent"))) { return 17; } FALLTHROUGH; - case 17: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_SampleIntervalLow, false, W("System.Threading.ThreadPool.HillClimbing.SampleIntervalLow"))) { return 18; } FALLTHROUGH; - case 18: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_SampleIntervalHigh, false, W("System.Threading.ThreadPool.HillClimbing.SampleIntervalHigh"))) { return 19; } FALLTHROUGH; - case 19: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_GainExponent, false, W("System.Threading.ThreadPool.HillClimbing.GainExponent"))) { return 20; } FALLTHROUGH; - - default: - *configValueRef = 0; - *isBooleanRef = false; - *appContextConfigNameRef = NULL; - return -1; - } -} -FCIMPLEND diff --git a/src/coreclr/vm/comthreadpool.h b/src/coreclr/vm/comthreadpool.h deleted file mode 100644 index e3ad28c01c0c17..00000000000000 --- a/src/coreclr/vm/comthreadpool.h +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#ifndef _COMTHREADPOOL_H -#define _COMTHREADPOOL_H - -class ThreadPoolNative -{ -public: - static FCDECL4(INT32, GetNextConfigUInt32Value, - INT32 configVariableIndex, - UINT32 *configValueRef, - BOOL *isBooleanRef, - LPCWSTR *appContextConfigNameRef); -}; - -#endif diff --git a/src/coreclr/vm/corelib.cpp b/src/coreclr/vm/corelib.cpp index 4c3f7438a559ab..beb66a2086a226 100644 --- a/src/coreclr/vm/corelib.cpp +++ b/src/coreclr/vm/corelib.cpp @@ -31,7 +31,6 @@ #include "comdatetime.h" #include "debugdebugger.h" #include "assemblynative.hpp" -#include "comthreadpool.h" #include "comwaithandle.h" #include "proftoeeinterfaceimpl.h" diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index e1ba7a2aa4da91..7535c717c3955b 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -248,10 +248,6 @@ FCFuncStart(gThreadFuncs) FCFuncElement("get_OptimalMaxSpinWaitsPerSpinIteration", ThreadNative::GetOptimalMaxSpinWaitsPerSpinIteration) FCFuncEnd() -FCFuncStart(gThreadPoolFuncs) - FCFuncElement("GetNextConfigUInt32Value", ThreadPoolNative::GetNextConfigUInt32Value) -FCFuncEnd() - FCFuncStart(gCastHelpers) FCFuncElement("WriteBarrier", ::WriteBarrier_Helper) FCFuncEnd() @@ -414,7 +410,6 @@ FCClassElement("Signature", "System", gSignatureNative) FCClassElement("String", "System", gStringFuncs) FCClassElement("StubHelpers", "System.StubHelpers", gStubHelperFuncs) FCClassElement("Thread", "System.Threading", gThreadFuncs) -FCClassElement("ThreadPool", "System.Threading", gThreadPoolFuncs) #undef FCFuncElement #undef FCFuncElementSig diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index f59728794c8ede..a359ecde8aa68b 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -26,7 +26,6 @@ #include "comdatetime.h" #include "debugdebugger.h" #include "assemblynative.hpp" -#include "comthreadpool.h" #include "comwaithandle.h" #include "proftoeeinterfaceimpl.h" diff --git a/src/libraries/System.Private.CoreLib/src/System/AppContextConfigHelper.cs b/src/libraries/System.Private.CoreLib/src/System/AppContextConfigHelper.cs index cfb4629187dcb5..87119ca280fe8b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/AppContextConfigHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/AppContextConfigHelper.cs @@ -28,6 +28,25 @@ internal static bool GetBooleanConfig(string switchName, string envVariable, boo return GetBooleanConfig(switchName, defaultValue); } + internal static bool GetBooleanComPlusOrDotNetConfig(string configName, string envVariable, bool defaultValue) + { + string? str = Environment.GetEnvironmentVariable("DOTNET_" + envVariable) + ?? Environment.GetEnvironmentVariable("COMPlus_" + envVariable); + + if (str != null && str.StartsWith("0x", StringComparison.Ordinal)) + { + str = str.Substring(2); + } + + if (str != null + && uint.TryParse(str, NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo, out uint resultUnsigned)) + { + return resultUnsigned != 0; + } + + return GetBooleanConfig(configName, defaultValue); + } + internal static int GetInt32Config(string configName, int defaultValue, bool allowNegative = true) { try @@ -112,6 +131,29 @@ internal static int GetInt32Config(string configName, string envVariable, int de return GetInt32Config(configName, defaultValue, allowNegative); } + internal static int GetInt32ComPlusOrDotNetConfig(string configName, string envVariable, int defaultValue, bool allowNegative) + { + string? str = Environment.GetEnvironmentVariable("DOTNET_" + envVariable) + ?? Environment.GetEnvironmentVariable("COMPlus_" + envVariable); + + if (str != null && str.StartsWith("0x", StringComparison.Ordinal)) + { + str = str.Substring(2); + } + + if (str != null + && uint.TryParse(str, NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo, out uint resultUnsigned)) + { + int result = (int)resultUnsigned; + if (allowNegative || result >= 0) + { + return result; + } + } + + return GetInt32Config(configName, defaultValue, allowNegative); + } + internal static short GetInt16Config(string configName, short defaultValue, bool allowNegative = true) { try @@ -198,5 +240,28 @@ internal static short GetInt16Config(string configName, string envVariable, shor return GetInt16Config(configName, defaultValue, allowNegative); } + + internal static short GetInt16ComPlusOrDotNetConfig(string configName, string envVariable, short defaultValue, bool allowNegative) + { + string? str = Environment.GetEnvironmentVariable("DOTNET_" + envVariable) + ?? Environment.GetEnvironmentVariable("COMPlus_" + envVariable); + + if (str != null && str.StartsWith("0x", StringComparison.Ordinal)) + { + str = str.Substring(2); + } + + if (str != null + && ushort.TryParse(str, NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo, out ushort resultUnsigned)) + { + short result = (short)resultUnsigned; + if (allowNegative || result >= 0) + { + return result; + } + } + + return GetInt16Config(configName, defaultValue, allowNegative); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs index 5d1b79a3098e05..1840fd97d5dc92 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs @@ -23,9 +23,9 @@ private static class GateThread private static void GateThreadStart() { bool disableStarvationDetection = - AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.DisableStarvationDetection", false); + AppContextConfigHelper.GetBooleanComPlusOrDotNetConfig("System.Threading.ThreadPool.DisableStarvationDetection", "ThreadPool_DisableStarvationDetection", false); bool debuggerBreakOnWorkStarvation = - AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.DebugBreakOnWorkerStarvation", false); + AppContextConfigHelper.GetBooleanComPlusOrDotNetConfig("System.Threading.ThreadPool.DebugBreakOnWorkerStarvation", "ThreadPool_DebugBreakOnWorkerStarvation", false); // The first reading is over a time range other than what we are focusing on, so we do not use the read other // than to send it to any runtime-specific implementation that may also use the CPU utilization. diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs index be5741e0ec6e20..cf340c81302878 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs @@ -17,7 +17,7 @@ private sealed partial class HillClimbing private const int DefaultSampleIntervalMsLow = 10; private const int DefaultSampleIntervalMsHigh = 200; - public static readonly bool IsDisabled = AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.HillClimbing.Disable", false); + public static readonly bool IsDisabled = AppContextConfigHelper.GetBooleanComPlusOrDotNetConfig("System.Threading.ThreadPool.HillClimbing.Disable", "HillClimbing_Disable", false); // SOS's ThreadPool command depends on this name public static readonly HillClimbing ThreadPoolHillClimber = new HillClimbing(); @@ -80,16 +80,16 @@ private struct LogEntry public HillClimbing() { - _wavePeriod = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.WavePeriod", 4, false); - _maxThreadWaveMagnitude = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.MaxWaveMagnitude", 20, false); - _threadMagnitudeMultiplier = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.WaveMagnitudeMultiplier", 100, false) / 100.0; - _samplesToMeasure = _wavePeriod * AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.WaveHistorySize", 8, false); - _targetThroughputRatio = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.Bias", 15, false) / 100.0; - _targetSignalToNoiseRatio = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.TargetSignalToNoiseRatio", 300, false) / 100.0; - _maxChangePerSecond = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.MaxChangePerSecond", 4, false); - _maxChangePerSample = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.MaxChangePerSample", 20, false); - int sampleIntervalMsLow = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.SampleIntervalLow", DefaultSampleIntervalMsLow, false); - int sampleIntervalMsHigh = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.SampleIntervalHigh", DefaultSampleIntervalMsHigh, false); + _wavePeriod = AppContextConfigHelper.GetInt32ComPlusOrDotNetConfig("System.Threading.ThreadPool.HillClimbing.WavePeriod", "HillClimbing_WavePeriod", 4, false); + _maxThreadWaveMagnitude = AppContextConfigHelper.GetInt32ComPlusOrDotNetConfig("System.Threading.ThreadPool.HillClimbing.MaxWaveMagnitude", "HillClimbing_MaxWaveMagnitude", 20, false); + _threadMagnitudeMultiplier = AppContextConfigHelper.GetInt32ComPlusOrDotNetConfig("System.Threading.ThreadPool.HillClimbing.WaveMagnitudeMultiplier", "HillClimbing_WaveMagnitudeMultiplier", 100, false) / 100.0; + _samplesToMeasure = _wavePeriod * AppContextConfigHelper.GetInt32ComPlusOrDotNetConfig("System.Threading.ThreadPool.HillClimbing.WaveHistorySize", "HillClimbing_WaveHistorySize", 8, false); + _targetThroughputRatio = AppContextConfigHelper.GetInt32ComPlusOrDotNetConfig("System.Threading.ThreadPool.HillClimbing.Bias", "HillClimbing_Bias", 15, false) / 100.0; + _targetSignalToNoiseRatio = AppContextConfigHelper.GetInt32ComPlusOrDotNetConfig("System.Threading.ThreadPool.HillClimbing.TargetSignalToNoiseRatio", "HillClimbing_TargetSignalToNoiseRatio", 300, false) / 100.0; + _maxChangePerSecond = AppContextConfigHelper.GetInt32ComPlusOrDotNetConfig("System.Threading.ThreadPool.HillClimbing.MaxChangePerSecond", "HillClimbing_MaxChangePerSecond", 4, false); + _maxChangePerSample = AppContextConfigHelper.GetInt32ComPlusOrDotNetConfig("System.Threading.ThreadPool.HillClimbing.MaxChangePerSample", "HillClimbing_MaxChangePerSample", 20, false); + int sampleIntervalMsLow = AppContextConfigHelper.GetInt32ComPlusOrDotNetConfig("System.Threading.ThreadPool.HillClimbing.SampleIntervalLow", "HillClimbing_SampleIntervalLow", DefaultSampleIntervalMsLow, false); + int sampleIntervalMsHigh = AppContextConfigHelper.GetInt32ComPlusOrDotNetConfig("System.Threading.ThreadPool.HillClimbing.SampleIntervalHigh", "HillClimbing_SampleIntervalHigh", DefaultSampleIntervalMsHigh, false); if (sampleIntervalMsLow <= sampleIntervalMsHigh) { _sampleIntervalMsLow = sampleIntervalMsLow; @@ -100,9 +100,9 @@ public HillClimbing() _sampleIntervalMsLow = DefaultSampleIntervalMsLow; _sampleIntervalMsHigh = DefaultSampleIntervalMsHigh; } - _throughputErrorSmoothingFactor = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.ErrorSmoothingFactor", 1, false) / 100.0; - _gainExponent = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.GainExponent", 200, false) / 100.0; - _maxSampleError = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.MaxSampleErrorPercent", 15, false) / 100.0; + _throughputErrorSmoothingFactor = AppContextConfigHelper.GetInt32ComPlusOrDotNetConfig("System.Threading.ThreadPool.HillClimbing.ErrorSmoothingFactor", "HillClimbing_ErrorSmoothingFactor", 1, false) / 100.0; + _gainExponent = AppContextConfigHelper.GetInt32ComPlusOrDotNetConfig("System.Threading.ThreadPool.HillClimbing.GainExponent", "HillClimbing_GainExponent", 200, false) / 100.0; + _maxSampleError = AppContextConfigHelper.GetInt32ComPlusOrDotNetConfig("System.Threading.ThreadPool.HillClimbing.MaxSampleErrorPercent", "HillClimbing_MaxSampleErrorPercent", 15, false) / 100.0; _samples = new double[_samplesToMeasure]; _threadCounts = new double[_samplesToMeasure]; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs index c2e69c5dfdff09..d7265b345cd56d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs @@ -53,8 +53,9 @@ private static short DetermineThreadsToKeepAlive() new LowLevelLifoSemaphore( 0, MaxPossibleThreadCount, - AppContextConfigHelper.GetInt32Config( + AppContextConfigHelper.GetInt32ComPlusOrDotNetConfig( "System.Threading.ThreadPool.UnfairSemaphoreSpinLimit", + "ThreadPool_UnfairSemaphoreSpinLimit", SemaphoreSpinCountDefault, false), onWait: () => diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs index 8dc875f7bea96c..37a13598e1657f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -30,16 +30,10 @@ internal sealed partial class PortableThreadPool private const int CpuUtilizationHigh = 95; private const int CpuUtilizationLow = 80; -#if CORECLR -#pragma warning disable CA1823 - private static readonly bool s_initialized = ThreadPool.EnsureConfigInitialized(); -#pragma warning restore CA1823 -#endif - private static readonly short ForcedMinWorkerThreads = - AppContextConfigHelper.GetInt16Config("System.Threading.ThreadPool.MinThreads", 0, false); + AppContextConfigHelper.GetInt16ComPlusOrDotNetConfig("System.Threading.ThreadPool.MinThreads", "ThreadPool_ForceMinWorkerThreads", 0, false); private static readonly short ForcedMaxWorkerThreads = - AppContextConfigHelper.GetInt16Config("System.Threading.ThreadPool.MaxThreads", 0, false); + AppContextConfigHelper.GetInt16ComPlusOrDotNetConfig("System.Threading.ThreadPool.MaxThreads", "ThreadPool_ForceMaxWorkerThreads", 0, false); #if TARGET_WINDOWS // Continuations of IO completions are dispatched to the ThreadPool from IO completion poller threads. This avoids diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Unix.cs index 83faa720e3974a..3766266fa671e7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Unix.cs @@ -22,10 +22,6 @@ public static partial class ThreadPool internal static bool YieldFromDispatchLoop => false; #endif -#if !CORECLR - internal static bool EnsureConfigInitialized() => true; -#endif - internal static object GetOrCreateThreadLocalCompletionCountObject() => PortableThreadPool.ThreadPoolInstance.GetOrCreateThreadLocalCompletionCountObject(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs index 9006732641d149..78ea089ad86ae5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs @@ -56,10 +56,6 @@ public static bool BindHandle(SafeHandle osHandle) => WindowsThreadPool.BindHandle(osHandle) : BindHandlePortableCore(osHandle); -#if !CORECLR - internal static bool EnsureConfigInitialized() => true; -#endif - internal static void InitializeForThreadPoolThread() { if (ThreadPool.UseWindowsThreadPool) From d564cb38d13aa11f3debd093d06403e23a37d64d Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 11 Dec 2024 01:15:14 -0800 Subject: [PATCH 34/70] Ensure that we don't try and optimize masks for promoted fields (#110485) * Ensure that we don't try and optimize masks for promoted fields * Add using for Xunit * Fix test name * Make the xunit analyzer happy --- src/coreclr/jit/optimizemaskconversions.cpp | 11 ++- .../JitBlue/Runtime_110326/Runtime_110326.cs | 91 +++++++++++++++++++ .../Runtime_110326/Runtime_110326.csproj | 12 +++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/tests/JIT/Regression/JitBlue/Runtime_110326/Runtime_110326.cs create mode 100644 src/tests/JIT/Regression/JitBlue/Runtime_110326/Runtime_110326.csproj diff --git a/src/coreclr/jit/optimizemaskconversions.cpp b/src/coreclr/jit/optimizemaskconversions.cpp index 7ab61c698d7e0a..1685581b0523db 100644 --- a/src/coreclr/jit/optimizemaskconversions.cpp +++ b/src/coreclr/jit/optimizemaskconversions.cpp @@ -227,13 +227,22 @@ class MaskConversionsCheckVisitor final : public GenTreeVisitorInvalidateWeight(); return fgWalkResult::WALK_CONTINUE; } + + // Cannot convert any locals that r promoted struct fields + if (varDsc->lvIsStructField) + { + JITDUMP("is struct field. "); + weight->InvalidateWeight(); + return fgWalkResult::WALK_CONTINUE; + } + // TODO: Converting to a mask loses data - as each field is only a single bit. // For parameters, OSR locals, and locals which are used as vectors, then they // cannot be stored as a mask as data will be lost. // For all of these, conversions could be done by creating a new store of type mask. // Then uses as mask could be converted to type mask and pointed to use the new // definition. The weighting would need updating to take this into account. - else if (isLocalUse && !hasConversion) + if (isLocalUse && !hasConversion) { JITDUMP("is used as vector. "); weight->InvalidateWeight(); diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_110326/Runtime_110326.cs b/src/tests/JIT/Regression/JitBlue/Runtime_110326/Runtime_110326.cs new file mode 100644 index 00000000000000..8a1b007c9db28c --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_110326/Runtime_110326.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using Xunit; + +public class Runtime_110326A +{ + public struct S1 + { + public bool bool_2; + public Vector512 v512_short_3; + public Vector512 v512_float_4; + } + + [Fact] + public static void TestEntryPoint() + { + Runtime_110326A.Method1(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static ulong Method1() + { + S1 s1_s1_d1_f3_160 = new S1(); + return Vector512.ExtractMostSignificantBits(s1_s1_d1_f3_160.v512_short_3); + } +} + +public class Runtime_110326B +{ + public struct S2_D1_F2 + { + public struct S2_D2_F2 + { + public Vector v_double_0; + } + + public struct S2_D2_F3 + { + public Vector3 v3_10; + } + } + + public struct S2_D1_F3 + { + public struct S2_D2_F3 + { + public Vector256 v256_int_14; + } + + public Vector128 v128_long_13; + public Vector512 v512_uint_16; + } + + [Fact] + public static void TestEntryPoint() + { + Runtime_110326B.Method0(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Method0() + { + S2_D1_F2.S2_D2_F2 s2_s2_d1_f2_s2_d2_f2_262 = new S2_D1_F2.S2_D2_F2(); + S2_D1_F2.S2_D2_F2 s2_s2_d1_f2_s2_d2_f2_263 = s2_s2_d1_f2_s2_d2_f2_262; + S2_D1_F2.S2_D2_F3 s2_s2_d1_f2_s2_d2_f3_264 = new S2_D1_F2.S2_D2_F3(); + S2_D1_F2.S2_D2_F3 s2_s2_d1_f2_s2_d2_f3_265 = s2_s2_d1_f2_s2_d2_f3_264; + S2_D1_F2 s2_s2_d1_f2_266 = new S2_D1_F2(); + S2_D1_F3.S2_D2_F3 s2_s2_d1_f3_s2_d2_f3_268 = new S2_D1_F3.S2_D2_F3(); + S2_D1_F3 s2_s2_d1_f3_269 = new S2_D1_F3(); + S2_D1_F3 s2_s2_d1_f3_270 = s2_s2_d1_f3_269; + s2_s2_d1_f3_270.v512_uint_16 = Vector512.IsZero(Vector512.AllBitsSet); + + Log("s2_s2_d1_f", s2_s2_d1_f2_s2_d2_f2_262.v_double_0); + Log("s2_s2_d1_f", s2_s2_d1_f2_s2_d2_f2_263.v_double_0); + Log("s2_s2_d1_f", s2_s2_d1_f2_s2_d2_f3_264); + Log("s2_s2_d1_f", s2_s2_d1_f2_s2_d2_f3_265.v3_10); + Log("s2_s2_d1_f", s2_s2_d1_f2_266); + Log("s2_s2_d1_f", s2_s2_d1_f3_s2_d2_f3_268.v256_int_14); + Log("s2_s2_d1_f", s2_s2_d1_f3_269.v128_long_13); + Log("s2_s2_d1_f", s2_s2_d1_f3_270.v128_long_13); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Log(string varName, object varValue) + { + } +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_110326/Runtime_110326.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_110326/Runtime_110326.csproj new file mode 100644 index 00000000000000..1cd524e9fe351b --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_110326/Runtime_110326.csproj @@ -0,0 +1,12 @@ + + + True + + + + + + + + + From 22001f702d506736ef9cfc3a0ac160f5374955fc Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Wed, 11 Dec 2024 04:30:49 -0800 Subject: [PATCH 35/70] [cDAC] SOSDacImpl::GetMethodDescData DynamicMethodObject (#110545) * allow SOSDacImpl::GetMethodDescData to handle dynamic functions * zero-out managedDynamicMethodObject as it is not being used and cDAC does not yet support fetching managed fields --- .../managed/cdacreader/src/Legacy/SOSDacImpl.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index e9c9644121eb14..fbd747b3df172b 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -360,10 +360,11 @@ int ISOSDacInterface.GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDes } #endif - if (data->bIsDynamic != 0) - { - throw new NotImplementedException(); // TODO[cdac]: get the dynamic method managed object - } + // Unlike the legacy implementation, the cDAC does not currently populate + // data->managedDynamicMethodObject. This field is unused in both SOS and CLRMD + // and would require accessing CorLib bound managed fields which the cDAC does not + // currently support. However, it must remain in the return type for compatibility. + data->managedDynamicMethodObject = 0; hr = HResults.S_OK; } @@ -405,7 +406,8 @@ int ISOSDacInterface.GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDes Debug.Assert(data->MDToken == dataLocal.MDToken); Debug.Assert(data->GCInfo == dataLocal.GCInfo); Debug.Assert(data->GCStressCodeCopy == dataLocal.GCStressCodeCopy); - Debug.Assert(data->managedDynamicMethodObject == dataLocal.managedDynamicMethodObject); + // managedDynamicMethodObject is not currently populated by the cDAC API and may differ from legacyImpl. + Debug.Assert(data->managedDynamicMethodObject == 0); Debug.Assert(data->requestedIP == dataLocal.requestedIP); Debug.Assert(data->cJittedRejitVersions == dataLocal.cJittedRejitVersions); From ff171c43a19fc3abaa77eee499194bb9d61b2f15 Mon Sep 17 00:00:00 2001 From: Pierre Arnaud Date: Wed, 11 Dec 2024 15:32:13 +0100 Subject: [PATCH 36/70] Fix comments in AggregateException.GetBaseException() (#107743) * Fix comments in AggregateException.GetBaseException() The public incorrectly stated that the method returned an AggregateException. * Update src/libraries/System.Private.CoreLib/src/System/AggregateException.cs Co-authored-by: Stephen Toub * Extract GetBaseException() content to * Update src/libraries/System.Private.CoreLib/src/System/AggregateException.cs --------- Co-authored-by: Stephen Toub Co-authored-by: Dan Moseley --- .../src/System/AggregateException.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/AggregateException.cs b/src/libraries/System.Private.CoreLib/src/System/AggregateException.cs index 66cc252717d7d4..7621b1fdda9732 100644 --- a/src/libraries/System.Private.CoreLib/src/System/AggregateException.cs +++ b/src/libraries/System.Private.CoreLib/src/System/AggregateException.cs @@ -213,13 +213,16 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont } /// - /// Returns the that is the root cause of this exception. + /// Returns the that is the root cause of this exception. /// + /// + /// This will either be the root exception, or the first + /// that contains either multiple inner exceptions or no inner exceptions at all. + /// public override Exception GetBaseException() { - // Returns the first inner AggregateException that contains more or less than one inner exception - - // Recursively traverse the inner exceptions as long as the inner exception of type AggregateException and has only one inner exception + // Recursively traverse the inner exceptions as long as the inner exception is of type + // AggregateException and has exactly one inner exception Exception? back = this; AggregateException? backAsAggregate = this; while (backAsAggregate != null && backAsAggregate.InnerExceptions.Count == 1) From 9652163f0e7daced7ac323570c37bdc9ac647e71 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:02:35 +0100 Subject: [PATCH 37/70] [browser] Remove WASM `HybridGlobalization` from library tests, WBT and docs (#110534) * Remove `HybridGlobalization` from library tests. * Remove `HybridGlobalization` from WBT. * Remove docs about HG on WASM. --- .../features/globalization-hybrid-mode.md | 376 ----------------- .../scenarios/BuildWasmAppsJobsList.txt | 1 - eng/testing/tests.browser.targets | 1 - eng/testing/tests.wasi.targets | 1 - .../TestUtilities/System/PlatformDetection.cs | 4 - .../Common/tests/Tests/System/StringTests.cs | 12 +- .../tests/LateBindingTests.cs | 10 +- .../tests/LikeOperatorTests.cs | 4 +- .../tests/ObjectTypeTests.cs | 4 +- .../tests/OperatorsTests.Comparison.cs | 24 +- .../tests/StringTypeTests.cs | 4 +- .../tests/StringsTests.cs | 12 +- .../CaseInsensitiveHashCodeProviderTests.cs | 1 - .../Mono/DataRowComparerTest.cs | 2 +- .../Mono/DataTableExtensionsTest.cs | 2 +- .../Mono/EnumerableRowCollectionTest.cs | 12 +- .../XsltArgumentList.cs | 2 +- .../tests/DataContractJsonSerializer.cs | 4 +- .../Serialization/Schema/RoundTripTest.cs | 1 - .../tests/DataContractSerializer.cs | 2 +- .../System.Runtime/System.Runtime.sln | 4 - ...ization.Calendars.Hybrid.WASM.Tests.csproj | 116 ------ .../System/Globalization/CalendarTestBase.cs | 4 +- .../CompareInfo/CompareInfoTests.Compare.cs | 219 +++------- .../CompareInfo/CompareInfoTests.IndexOf.cs | 51 +-- .../CompareInfo/CompareInfoTests.IsPrefix.cs | 33 +- .../CompareInfo/CompareInfoTests.IsSuffix.cs | 38 +- .../CompareInfoTests.LastIndexOf.cs | 39 +- .../CompareInfo/CompareInfoTests.SortKey.cs | 6 +- .../CompareInfo/CompareInfoTestsBase.cs | 11 - .../CultureInfo/CultureInfoCtor.cs | 4 +- .../DateTimeFormatInfoAMDesignator.cs | 201 --------- .../DateTimeFormatInfoAbbreviatedDayNames.cs | 67 --- ...FormatInfoAbbreviatedMonthGenitiveNames.cs | 217 +--------- ...DateTimeFormatInfoAbbreviatedMonthNames.cs | 215 ---------- .../DateTimeFormatInfoCalendarWeekRule.cs | 190 --------- .../DateTimeFormatInfoData.cs | 2 +- .../DateTimeFormatInfoDayNames.cs | 61 --- .../DateTimeFormatInfoFirstDayOfWeek.cs | 199 --------- .../DateTimeFormatInfoFullDateTimePattern.cs | 198 --------- ...DateTimeFormatInfoGetAbbreviatedEraName.cs | 191 --------- .../DateTimeFormatInfoGetEraName.cs | 392 +----------------- .../DateTimeFormatInfoLongDatePattern.cs | 201 --------- .../DateTimeFormatInfoLongTimePattern.cs | 199 --------- .../DateTimeFormatInfoMonthDayPattern.cs | 197 --------- .../DateTimeFormatInfoMonthGenitiveNames.cs | 198 --------- .../DateTimeFormatInfoMonthNames.cs | 206 --------- .../DateTimeFormatInfoNativeCalendarName.cs | 214 ---------- .../DateTimeFormatInfoPMDesignator.cs | 200 --------- .../DateTimeFormatInfoShortDatePattern.cs | 199 --------- .../DateTimeFormatInfoShortTimePattern.cs | 199 --------- .../DateTimeFormatInfoShortestDayNames.cs | 70 ---- .../DateTimeFormatInfoTests.cs | 2 +- .../DateTimeFormatInfoYearMonthPattern.cs | 24 -- ...tem.Globalization.Hybrid.WASM.Tests.csproj | 61 --- .../System/Globalization/TextInfoTests.cs | 10 +- .../System/StringComparer.cs | 12 +- .../System/StringGetHashCodeTests.cs | 2 +- .../System/StringTests.cs | 32 +- .../System/Text/RuneTests.cs | 3 +- src/libraries/tests.proj | 12 - src/mono/browser/README.md | 2 - .../HybridGlobalizationTests.cs | 62 --- src/mono/wasm/features.md | 2 - 64 files changed, 212 insertions(+), 4832 deletions(-) delete mode 100644 src/libraries/System.Runtime/tests/System.Globalization.Calendars.Tests/Hybrid/System.Globalization.Calendars.Hybrid.WASM.Tests.csproj delete mode 100644 src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoNativeCalendarName.cs delete mode 100644 src/libraries/System.Runtime/tests/System.Globalization.Tests/Hybrid/System.Globalization.Hybrid.WASM.Tests.csproj delete mode 100644 src/mono/wasm/Wasm.Build.Tests/HybridGlobalizationTests.cs diff --git a/docs/design/features/globalization-hybrid-mode.md b/docs/design/features/globalization-hybrid-mode.md index 1dec94595b1c45..9840c30fed0bba 100644 --- a/docs/design/features/globalization-hybrid-mode.md +++ b/docs/design/features/globalization-hybrid-mode.md @@ -11,382 +11,6 @@ Hybrid has lower priority than Invariant. To switch on the mode set the property Hybrid mode does not use ICU data for some functions connected with globalization but relies on functions native to the platform. Because native APIs do not fully cover all the functionalities we currently support and because ICU data can be excluded from the ICU datafile only in batches defined by ICU filters, not all functions will work the same way or not all will be supported. To see what to expect after switching on `HybridGlobalization`, read the following paragraphs. -### WASM - -For WebAssembly in Browser we are using Web API instead of some ICU data. Ideally, we would use `System.Runtime.InteropServices.JavaScript` to call JS code from inside of C# but we cannot reference any assemblies from inside of `System.Private.CoreLib`. That is why we are using iCalls instead. The host support depends on used Web API functions support - see **dependencies** in each section. - -Hybrid has higher priority than sharding or custom modes, described in globalization-icu-wasm.md. - -**HashCode** - -Affected public APIs: -- System.Globalization.CompareInfo.GetHashCode - -For invariant culture all `CompareOptions` are available. - -For non-invariant cultures following `CompareOptions` are available: -- `CompareOption.None` -- `CompareOption.IgnoreCase` - -The remaining combinations for non-invariant cultures throw `PlatformNotSupportedException`. - -**SortKey** - -Affected public APIs: -- System.Globalization.CompareInfo.GetSortKey -- System.Globalization.CompareInfo.GetSortKeyLength - -For invariant culture all `CompareOptions` are available. - -For non-invariant cultures `PlatformNotSupportedException` is thrown. - -Indirectly affected APIs (the list might not be complete): -- Microsoft.VisualBasic.Collection.Add -- System.Collections.Hashtable.Add -- System.Collections.Hashtable.GetHash -- System.Collections.CaseInsensitiveHashCodeProvider.GetHashCode -- System.Collections.Specialized.NameObjectCollectionBase.BaseAdd -- System.Collections.Specialized.NameValueCollection.Add -- System.Collections.Specialized.NameObjectCollectionBase.BaseGet -- System.Collections.Specialized.NameValueCollection.Get -- System.Collections.Specialized.NameObjectCollectionBase.BaseRemove -- System.Collections.Specialized.NameValueCollection.Remove -- System.Collections.Specialized.OrderedDictionary.Add -- System.Collections.Specialized.NameObjectCollectionBase.BaseSet -- System.Collections.Specialized.NameValueCollection.Set -- System.Data.DataColumnCollection.Add -- System.Collections.Generic.HashSet -- System.Collections.Generic.Dictionary -- System.Net.Mail.MailAddress.GetHashCode -- System.Xml.Xsl.XslCompiledTransform.Transform - -**Case change** - -Affected public APIs: -- System.Globalization.TextInfo.ToLower, -- System.Globalization.TextInfo.ToUpper, -- System.Globalization.TextInfo.ToTitleCase. - -Case change with invariant culture uses `toUpperCase` / `toLoweCase` functions that do not guarantee a full match with the original invariant culture. -Hybrid case change, same as ICU-based, does not support code points expansion e.g. "straße" -> "STRAßE". - -- Final sigma behavior correction: - -ICU-based case change does not respect final-sigma rule, but hybrid does, so "ΒΌΛΟΣ" -> "βόλος", not "βόλοσ". - -Dependencies: -- [String.prototype.toUpperCase()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) -- [String.prototype.toLoweCase()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase) -- [String.prototype.toLocaleUpperCase()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLocaleUpperCase) -- [String.prototype.toLocaleLoweCase()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLocaleLowerCase) - -**String comparison** - -Affected public APIs: -- System.Globalization.CompareInfo.Compare, -- System.String.Compare, -- System.String.Equals. -Indirectly affected APIs (the list might not be complete): -- Microsoft.VisualBasic.Strings.InStrRev -- Microsoft.VisualBasic.Strings.Replace -- Microsoft.VisualBasic.Strings.InStr -- Microsoft.VisualBasic.Strings.Split -- Microsoft.VisualBasic.Strings.StrComp -- Microsoft.VisualBasic.CompilerServices.LikeOperator.LikeObject -- Microsoft.VisualBasic.CompilerServices.LikeOperator.LikeString -- Microsoft.VisualBasic.CompilerServices.ObjectType.ObjTst -- Microsoft.VisualBasic.CompilerServices.ObjectType.LikeObj -- Microsoft.VisualBasic.CompilerServices.StringType.StrLike -- Microsoft.VisualBasic.CompilerServices.Operators.ConditionalCompareObjectEqual -- Microsoft.VisualBasic.CompilerServices.StringType.StrLike -- Microsoft.VisualBasic.CompilerServices.StringType.StrLikeText -- Microsoft.VisualBasic.CompilerServices.StringType.StrCmp -- System.Data.DataSet.ReadXml -- System.Data.DataTableCollection.Add - -Dependencies: -- [String.prototype.localeCompare()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) - -The number of `CompareOptions` and `StringComparison` combinations is limited. Originally supported combinations can be found [here for CompareOptions](https://learn.microsoft.com/dotnet/api/system.globalization.compareoptions) and [here for StringComparison](https://learn.microsoft.com/dotnet/api/system.stringcomparison). - - -- `IgnoreWidth` is not supported because there is no equivalent in Web API. Throws `PlatformNotSupportedException`. -``` JS -let high = String.fromCharCode(65281) // %uff83 = テ -let low = String.fromCharCode(12486) // %u30c6 = テ -high.localeCompare(low, "ja-JP", { sensitivity: "case" }) // -1 ; case: a ≠ b, a = á, a ≠ A; expected: 0 - -let wide = String.fromCharCode(65345) // %uFF41 = a -let narrow = "a" -wide.localeCompare(narrow, "en-US", { sensitivity: "accent" }) // 0; accent: a ≠ b, a ≠ á, a = A; expected: -1 -``` - -For comparison where "accent" sensitivity is used, ignoring some type of character widths is applied and cannot be switched off (see: point about `IgnoreCase`). - -- `IgnoreKanaType`: - -It is always switched on for comparison with locale "ja-JP", even if this comparison option was not set explicitly. - -``` JS -let hiragana = String.fromCharCode(12353) // %u3041 = ぁ -let katakana = String.fromCharCode(12449) // %u30A1 = ァ -let enCmp = hiragana.localeCompare(katakana, "en-US") // -1 -let jaCmp = hiragana.localeCompare(katakana, "ja-JP") // 0 -``` - -For locales different than "ja-JP" it cannot be used separately (no equivalent in Web API) - throws `PlatformNotSupportedException`. - -- `None`: - -No equivalent in Web API for "ja-JP" locale. See previous point about `IgnoreKanaType`. For "ja-JP" it throws `PlatformNotSupportedException`. - -- `IgnoreCase`, `CurrentCultureIgnoreCase`, `InvariantCultureIgnoreCase` - -For `IgnoreCase | IgnoreKanaType`, argument `sensitivity: "accent"` is used. - -``` JS -let hiraganaBig = `${String.fromCharCode(12353)} A` // %u3041 = ぁ -let katakanaSmall = `${String.fromCharCode(12449)} a` // %u30A1 = ァ -hiraganaBig.localeCompare(katakanaSmall, "en-US", { sensitivity: "accent" }) // 0; accent: a ≠ b, a ≠ á, a = A -``` - -Known exceptions: - -| **character 1** | **character 2** | **CompareOptions** | **hybrid globalization** | **icu** | **comments** | -|:---------------:|:---------------:|--------------------|:------------------------:|:-------:|:-------------------------------------------------------:| -| a | `\uFF41` a | IgnoreKanaType | 0 | -1 | applies to all wide-narrow chars | -| `\u30DC` ボ | `\uFF8E` ホ | IgnoreCase | 1 | -1 | 1 is returned in icu when we additionally ignore width | -| `\u30BF` タ | `\uFF80` タ | IgnoreCase | 0 | -1 | | - - -For `IgnoreCase` alone, a comparison with default option: `sensitivity: "variant"` is used after string case unification. - -``` JS -let hiraganaBig = `${String.fromCharCode(12353)} A` // %u3041 = ぁ -let katakanaSmall = `${String.fromCharCode(12449)} a` // %u30A1 = ァ -let unchangedLocale = "en-US" -let unchangedStr1 = hiraganaBig.toLocaleLowerCase(unchangedLocale); -let unchangedStr2 = katakanaSmall.toLocaleLowerCase(unchangedLocale); -unchangedStr1.localeCompare(unchangedStr2, unchangedLocale) // -1; -let changedLocale = "ja-JP" -let changedStr1 = hiraganaBig.toLocaleLowerCase(changedLocale); -let changedStr2 = katakanaSmall.toLocaleLowerCase(changedLocale); -changedStr1.localeCompare(changedStr2, changedLocale) // 0; -``` - -From this reason, comparison with locale `ja-JP` `CompareOption` `IgnoreCase` and `StringComparison`: `CurrentCultureIgnoreCase` and `InvariantCultureIgnoreCase` behave like a combination `IgnoreCase | IgnoreKanaType` (see: previous point about `IgnoreKanaType`). For other locales the behavior is unchanged with the following known exceptions: - -| **character 1** | **character 2** | **CompareOptions** | **hybrid globalization** | **icu** | -|:------------------------------------------------:|:----------------------------------------------------------:|-----------------------------------|:------------------------:|:-------:| -| `\uFF9E` (HALFWIDTH KATAKANA VOICED SOUND MARK) | `\u3099` (COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK) | None / IgnoreCase / IgnoreSymbols | 1 | 0 | - -- `IgnoreNonSpace` - -`IgnoreNonSpace` cannot be used separately without `IgnoreKanaType`. Argument `sensitivity: "case"` is used for comparison and it ignores both types of characters. Option `IgnoreNonSpace` alone throws `PlatformNotSupportedException`. - -``` JS -let hiraganaAccent = `${String.fromCharCode(12353)} á` // %u3041 = ぁ -let katakanaNoAccent = `${String.fromCharCode(12449)} a` // %u30A1 = ァ -hiraganaAccent.localeCompare(katakanaNoAccent, "en-US", { sensitivity: "case" }) // 0; case: a ≠ b, a = á, a ≠ A -``` - -- `IgnoreNonSpace | IgnoreCase` -Combination of `IgnoreNonSpace` and `IgnoreCase` cannot be used without `IgnoreKanaType`. Argument `sensitivity: "base"` is used for comparison and it ignores three types of characters. Combination `IgnoreNonSpace | IgnoreCase` alone throws `PlatformNotSupportedException`. - -``` JS -let hiraganaBigAccent = `${String.fromCharCode(12353)} A á` // %u3041 = ぁ -let katakanaSmallNoAccent = `${String.fromCharCode(12449)} a a` // %u30A1 = ァ -hiraganaBigAccent.localeCompare(katakanaSmallNoAccent, "en-US", { sensitivity: "base" }) // 0; base: a ≠ b, a = á, a = A -``` - -- `IgnoreSymbols` - -The subset of ignored symbols is limited to the symbols ignored by `string1.localeCompare(string2, locale, { ignorePunctuation: true })`. E.g. currency symbols, & are not ignored - -``` JS -let hiraganaAccent = `${String.fromCharCode(12353)} á` // %u3041 = ぁ -let katakanaNoAccent = `${String.fromCharCode(12449)} a` // %u30A1 = ァ -hiraganaBig.localeCompare(katakanaSmall, "en-US", { sensitivity: "base" }) // 0; base: a ≠ b, a = á, a = A -``` - -- List of all `CompareOptions` combinations always throwing `PlatformNotSupportedException`: - -`IgnoreCase`, - -`IgnoreNonSpace`, - -`IgnoreNonSpace | IgnoreCase`, - -`IgnoreSymbols | IgnoreCase`, - -`IgnoreSymbols | IgnoreNonSpace`, - -`IgnoreSymbols | IgnoreNonSpace | IgnoreCase`, - -`IgnoreWidth`, - -`IgnoreWidth | IgnoreCase`, - -`IgnoreWidth | IgnoreNonSpace`, - -`IgnoreWidth | IgnoreNonSpace | IgnoreCase`, - -`IgnoreWidth | IgnoreSymbols` - -`IgnoreWidth | IgnoreSymbols | IgnoreCase` - -`IgnoreWidth | IgnoreSymbols | IgnoreNonSpace` - -`IgnoreWidth | IgnoreSymbols | IgnoreNonSpace | IgnoreCase` - -`IgnoreKanaType | IgnoreWidth` - -`IgnoreKanaType | IgnoreWidth | IgnoreCase` - -`IgnoreKanaType | IgnoreWidth | IgnoreNonSpace` - -`IgnoreKanaType | IgnoreWidth | IgnoreNonSpace | IgnoreCase` - -`IgnoreKanaType | IgnoreWidth | IgnoreSymbols` - -`IgnoreKanaType | IgnoreWidth | IgnoreSymbols | IgnoreCase` - -`IgnoreKanaType | IgnoreWidth | IgnoreSymbols | IgnoreNonSpace` - -`IgnoreKanaType | IgnoreWidth | IgnoreSymbols | IgnoreNonSpace | IgnoreCase` - - -**String starts with / ends with** - -Affected public APIs: -- CompareInfo.IsPrefix -- CompareInfo.IsSuffix -- String.StartsWith -- String.EndsWith - -Dependencies: -- [String.prototype.normalize()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize) -- [String.prototype.localeCompare()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) - -Web API does not expose locale-sensitive endsWith/startsWith function. As a workaround, both strings get normalized and weightless characters are removed. Resulting strings are cut to the same length and comparison is performed. This approach, beyond having the same compare option limitations as described under **String comparison**, has additional limitations connected with the workaround used. Because we are normalizing strings to be able to cut them, we cannot calculate the match length on the original strings. Methods that calculate this information throw PlatformNotSupported exception: - -- [CompareInfo.IsPrefix](https://learn.microsoft.com/dotnet/api/system.globalization.compareinfo.isprefix?view=#system-globalization-compareinfo-isprefix(system-readonlyspan((system-char))-system-readonlyspan((system-char))-system-globalization-compareoptions-system-int32@)) -- [CompareInfo.IsSuffix](https://learn.microsoft.com/dotnet/api/system.globalization.compareinfo.issuffix?view=system-globalization-compareinfo-issuffix(system-readonlyspan((system-char))-system-readonlyspan((system-char))-system-globalization-compareoptions-system-int32@)) - -- `IgnoreSymbols` -Only comparisons that do not skip character types are allowed. E.g. `IgnoreSymbols` skips symbol-chars in comparison/indexing. All `CompareOptions` combinations that include `IgnoreSymbols` throw `PlatformNotSupportedException`. - -**String indexing** - -Affected public APIs: -- CompareInfo.IndexOf -- CompareInfo.LastIndexOf -- String.IndexOf -- String.LastIndexOf - -Web API does not expose locale-sensitive indexing function. There is a discussion on adding it: https://github.com/tc39/ecma402/issues/506. In the current state, as a workaround, locale-sensitive string segmenter combined with locale-sensitive comparison is used. This approach, beyond having the same compare option limitations as described under **String comparison**, has additional limitations connected with the workaround used. Information about additional limitations: - -- Methods that calculate `matchLength` always return throw PlatformNotSupported exception: - -[CompareInfo.IndexOf](https://learn.microsoft.com/en-us/dotnet/api/system.globalization.compareinfo.indexof?view=system-globalization-compareinfo-indexof(system-readonlyspan((system-char))-system-readonlyspan((system-char))-system-globalization-compareoptions-system-int32@)) - -[CompareInfo.LastIndexOf](https://learn.microsoft.com/en-us/dotnet/api/system.globalization.compareinfo.lastindexof?view=system-globalization-compareinfo-lastindexof(system-readonlyspan((system-char))-system-readonlyspan((system-char))-system-globalization-compareoptions-system-int32@)) - -- String.Replace that uses `StringComparison` argument relies internally on IndexOf with `matchLength` argument. From this reason, it throws PlatformNotSupportedException: -[String.Replace](https://learn.microsoft.com/en-us/dotnet/api/system.string.replace?view=system-string-replace(system-string-system-string-system-stringcomparison)) - -- Support depends on [`Intl.segmenter's support`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter#browser_compatibility). - -- `IgnoreSymbols` - -Only comparisons that ignore types of characters but do not skip them are allowed. E.g. `IgnoreCase` ignores type (case) of characters but `IgnoreSymbols` skips symbol-chars in comparison/indexing. All `CompareOptions` combinations that include `IgnoreSymbols` throw `PlatformNotSupportedException`. - -- Some letters consist of more than one grapheme. - -Using locale-sensitive segmenter `Intl.Segmenter(locale, { granularity: "grapheme" })` does not guarantee that string will be segmented by letters but by graphemes. E.g. in `cs-CZ` and `sk-SK` "ch" is 1 letter, 2 graphemes. The following code with `HybridGlobalization` switched off returns -1 (not found) while with `HybridGlobalization` switched on, it returns 1. - -``` C# -new CultureInfo("sk-SK").CompareInfo.IndexOf("ch", "h"); // -1 or 1 -``` - -- Some graphemes consist of more than one character. -E.g. `\r\n` that represents two characters in C#, is treated as one grapheme by the segmenter: - -``` JS -const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" }); -Array.from(segmenter.segment("\r\n")) // {segment: '\r\n', index: 0, input: '\r\n'} -``` - -Because we are comparing grapheme-by-grapheme, character `\r` or character `\n` will not be found in `\r\n` string when `HybridGlobalization` is switched on. - -- Some graphemes have multi-grapheme equivalents. -E.g. in `de-DE` ß (%u00DF) is one letter and one grapheme and "ss" is one letter and is recognized as two graphemes. Web API's equivalent of `IgnoreNonSpace` treats them as the same letter when comparing. Similar case: dz (%u01F3) and dz. -``` JS -"ß".localeCompare("ss", "de-DE", { sensitivity: "case" }); // 0 -``` - -Using `IgnoreNonSpace` for these two with `HybridGlobalization` off, also returns 0 (they are equal). However, the workaround used in `HybridGlobalization` will compare them grapheme-by-grapheme and will return -1. - -``` C# -new CultureInfo("de-DE").CompareInfo.IndexOf("strasse", "stra\u00DFe", 0, CompareOptions.IgnoreNonSpace); // 0 or -1 -``` - -**Calandars** - -Affected public APIs: -- DateTimeFormatInfo.AbbreviatedDayNames -- DateTimeFormatInfo.GetAbbreviatedDayName() -- DateTimeFormatInfo.AbbreviatedMonthGenitiveNames -- DateTimeFormatInfo.AbbreviatedMonthNames -- DateTimeFormatInfo.GetAbbreviatedMonthName() -- DateTimeFormatInfo.AMDesignator -- DateTimeFormatInfo.CalendarWeekRule -- DateTimeFormatInfo.DayNames -- DateTimeFormatInfo.GetDayName -- DateTimeFormatInfo.GetAbbreviatedEraName() -- DateTimeFormatInfo.GetEraName() -- DateTimeFormatInfo.FirstDayOfWeek -- DateTimeFormatInfo.FullDateTimePattern -- DateTimeFormatInfo.LongDatePattern -- DateTimeFormatInfo.LongTimePattern -- DateTimeFormatInfo.MonthDayPattern -- DateTimeFormatInfo.MonthGenitiveNames -- DateTimeFormatInfo.MonthNames -- DateTimeFormatInfo.GetMonthName() -- DateTimeFormatInfo.NativeCalendarName -- DateTimeFormatInfo.PMDesignator -- DateTimeFormatInfo.ShortDatePattern -- DateTimeFormatInfo.ShortestDayNames -- DateTimeFormatInfo.GetShortestDayName() -- DateTimeFormatInfo.ShortTimePattern -- DateTimeFormatInfo.YearMonthPattern - - -The Hybrid responses may differ because they use Web API functions. To better ilustrate the mechanism we provide an example for each endpoint. All exceptions cannot be listed, for reference check the response of specific version of Web API on your host. -| **API** | **Functions used** | **Example of difference for locale** | **non-Hybrid** | **Hybrid** | -|:-----------------------------:|:----------------------------------------------------------------------------------------------------------------------------------:|:------------------------------------:|:------------------:|:-----------------:| -| AbbreviatedDayNames | `Date.prototype.toLocaleDateString(locale, { weekday: "short" })` | en-CA | Sun. | Sun | -| AbbreviatedMonthGenitiveNames | `Date.prototype.toLocaleDateString(locale, { month: "short", day: "numeric"})` | kn-IN | ಆಗ | ಆಗಸ್ಟ್ | -| AbbreviatedMonthNames | `Date.prototype.toLocaleDateString(locale, { month: "short" })` | lt-LT | saus. | 01 | -| AMDesignator | `Date.prototype.toLocaleTimeString(locale, { hourCycle: "h12"})`; `Date.prototype.toLocaleTimeString(locale, { hourCycle: "h24"})` | sr-Cyrl-RS | пре подне | AM | -| CalendarWeekRule | `Intl.Locale.prototype.getWeekInfo().minimalDay` | none | - | - | -| DayNames | `Date.prototype.toLocaleDateString(locale, { weekday: "long" })` | none | - | - | -| GetAbbreviatedEraName() | `Date.prototype.toLocaleDateString(locale, { era: "narrow" })` | bn-IN | খৃষ্টাব্দ | খ্রিঃ | -| GetEraName() | `Date.prototype.toLocaleDateString(locale, { era: "short" })` | vi-VI | sau CN | CN | -| FirstDayOfWeek | `Intl.Locale.prototype.getWeekInfo().firstDay` | zn-CN | Sunday | Monday | -| FullDateTimePattern | `LongDatePattern` and `LongTimePattern` | - | | | -| LongDatePattern | `Intl.DateTimeFormat(locale, { weekday: "long", year: "numeric", month: "long", day: "numeric"}).format(date)` | en-BW | dddd, dd MMMM yyyy | dddd, d MMMM yyyy | -| LongTimePattern | `Intl.DateTimeFormat(locale, { timeStyle: "medium" })` | zn-CN | tth:mm:ss | HH:mm:ss | -| MonthDayPattern | `Date.prototype.toLocaleDateString(locale, { month: "long", day: "numeric"})` | en-PH | d MMMM | MMMM d | -| MonthGenitiveNames | `Date.prototype.toLocaleDateString(locale, { month: "long", day: "numeric"})` | ca-AD | de gener | gener | -| MonthNames | `Date.prototype.toLocaleDateString(locale, { month: "long" })` | el-GR | Ιανουαρίου | Ιανουάριος | -| NativeCalendarName | `Intl.Locale.prototype.getCalendars()` | for all locales it has English names | Gregorian Calendar | gregory | -| PMDesignator | `Date.prototype.toLocaleTimeString(locale, { hourCycle: "h12"})`; `Date.prototype.toLocaleTimeString(locale, { hourCycle: "h24"})` | mr-IN | म.उ. | PM | -| ShortDatePattern | `Date.prototype.toLocaleDateString(locale, {dateStyle: "short"})` | en-CH | dd.MM.yyyy | dd/MM/yyyy | -| ShortestDayNames | `Date.prototype.toLocaleDateString(locale, { weekday: "narrow" })` | none | - | - | -| ShortTimePattern | `Intl.DateTimeFormat(locale, { timeStyle: "medium" })` | bg-BG | HH:mm | H:mm | -| YearMonthPattern | `Date.prototype.toLocaleDateString(locale, { year: "numeric", month: "long" })` | ar-SA | MMMM yyyy | MMMM yyyy g | - ### Apple platforms For Apple platforms (iOS/tvOS/maccatalyst) we are using native apis instead of ICU data. diff --git a/eng/testing/scenarios/BuildWasmAppsJobsList.txt b/eng/testing/scenarios/BuildWasmAppsJobsList.txt index f94f6fbcaa6c1d..1f8b21d6eca66e 100644 --- a/eng/testing/scenarios/BuildWasmAppsJobsList.txt +++ b/eng/testing/scenarios/BuildWasmAppsJobsList.txt @@ -20,7 +20,6 @@ Wasm.Build.Tests.Blazor.IcuShardingTests Wasm.Build.Tests.Blazor.SignalRClientTests Wasm.Build.Tests.BuildPublishTests Wasm.Build.Tests.ConfigSrcTests -Wasm.Build.Tests.HybridGlobalizationTests Wasm.Build.Tests.IcuShardingTests Wasm.Build.Tests.IcuShardingTests2 Wasm.Build.Tests.IcuTests diff --git a/eng/testing/tests.browser.targets b/eng/testing/tests.browser.targets index 44c7bc6805fa80..e7029f44e21915 100644 --- a/eng/testing/tests.browser.targets +++ b/eng/testing/tests.browser.targets @@ -227,7 +227,6 @@ <_WasmPropertyNames Include="EmccLinkOptimizationFlag" /> <_WasmPropertyNames Include="WasmIncludeFullIcuData" /> <_WasmPropertyNames Include="WasmIcuDataFileName" /> - <_WasmPropertyNames Include="HybridGlobalization" /> diff --git a/eng/testing/tests.wasi.targets b/eng/testing/tests.wasi.targets index 7f981dde513c7f..e29f86cbc2aa04 100644 --- a/eng/testing/tests.wasi.targets +++ b/eng/testing/tests.wasi.targets @@ -138,7 +138,6 @@ <_WasmPropertyNames Include="WasiClangLinkOptimizationFlag" /> <_WasmPropertyNames Include="WasmIncludeFullIcuData" /> <_WasmPropertyNames Include="WasmIcuDataFileName" /> - <_WasmPropertyNames Include="HybridGlobalization" /> diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index 23d63a5ab0e70e..a37457fda62a54 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -389,9 +389,7 @@ public static string GetDistroVersionString() public static bool IsInvariantGlobalization => m_isInvariant.Value; public static bool IsHybridGlobalization => m_isHybrid.Value; - public static bool IsHybridGlobalizationOnBrowser => m_isHybrid.Value && IsBrowser; public static bool IsHybridGlobalizationOnApplePlatform => m_isHybrid.Value && (IsMacCatalyst || IsiOS || IstvOS); - public static bool IsNotHybridGlobalizationOnBrowser => !IsHybridGlobalizationOnBrowser; public static bool IsNotInvariantGlobalization => !IsInvariantGlobalization; public static bool IsNotHybridGlobalization => !IsHybridGlobalization; public static bool IsNotHybridGlobalizationOnApplePlatform => !IsHybridGlobalizationOnApplePlatform; @@ -401,8 +399,6 @@ public static string GetDistroVersionString() // HG on apple platforms implies ICU public static bool IsIcuGlobalization => !IsInvariantGlobalization && (IsHybridGlobalizationOnApplePlatform || ICUVersion > new Version(0, 0, 0, 0)); - - public static bool IsIcuGlobalizationAndNotHybridOnBrowser => IsIcuGlobalization && IsNotHybridGlobalizationOnBrowser; public static bool IsNlsGlobalization => IsNotInvariantGlobalization && !IsIcuGlobalization && !IsHybridGlobalization; public static bool IsSubstAvailable diff --git a/src/libraries/Common/tests/Tests/System/StringTests.cs b/src/libraries/Common/tests/Tests/System/StringTests.cs index 0d904ecb7ef774..caa4383c3ce001 100644 --- a/src/libraries/Common/tests/Tests/System/StringTests.cs +++ b/src/libraries/Common/tests/Tests/System/StringTests.cs @@ -1034,7 +1034,7 @@ public static void CompareToNoMatch_StringComparison() var secondSpan = new ReadOnlySpan(second); Assert.True(0 > firstSpan.CompareTo(secondSpan, StringComparison.Ordinal)); - // On Apple platforms, string comparison is handled by native Apple functions, which apply normalization techniques + // On Apple platforms, string comparison is handled by native Apple functions, which apply normalization techniques // like `precomposedStringWithCanonicalMapping`. This can lead to differences in behavior compared to other platforms. if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { @@ -1315,7 +1315,7 @@ public static void ContainsNoMatch_StringComparison() Assert.False(firstSpan.Contains(secondSpan, StringComparison.OrdinalIgnoreCase)); - // On Apple platforms, string comparison is handled by native Apple functions, which apply normalization techniques + // On Apple platforms, string comparison is handled by native Apple functions, which apply normalization techniques // like `precomposedStringWithCanonicalMapping`. This can lead to differences in behavior compared to other platforms. if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { @@ -3227,8 +3227,7 @@ which ignore the contraction collation weights (defined as 'tertiary' rules) Assert.Equal(PlatformDetection.IsNlsGlobalization ? 0 : -1, source.IndexOf(target)); Assert.Equal(PlatformDetection.IsNlsGlobalization ? 0 : -1, source.IndexOf(target, StringComparison.CurrentCulture)); - if (!PlatformDetection.IsHybridGlobalizationOnBrowser) - Assert.Equal(0, source.IndexOf(target, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal(0, source.IndexOf(target, StringComparison.CurrentCultureIgnoreCase)); Assert.Equal(-1, source.IndexOf(target, StringComparison.Ordinal)); Assert.Equal(-1, source.IndexOf(target, StringComparison.OrdinalIgnoreCase)); @@ -3236,8 +3235,7 @@ which ignore the contraction collation weights (defined as 'tertiary' rules) Assert.Equal(PlatformDetection.IsNlsGlobalization ? 0 : -1, span.IndexOf(target.AsSpan(), StringComparison.CurrentCulture)); - if (!PlatformDetection.IsHybridGlobalizationOnBrowser) - Assert.Equal(0, span.IndexOf(target.AsSpan(), StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal(0, span.IndexOf(target.AsSpan(), StringComparison.CurrentCultureIgnoreCase)); Assert.Equal(-1, span.IndexOf(target.AsSpan(), StringComparison.Ordinal)); Assert.Equal(-1, span.IndexOf(target.AsSpan(), StringComparison.OrdinalIgnoreCase)); } @@ -5425,7 +5423,6 @@ private static IEnumerable ToLower_Culture_TestData() [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))] [ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnApplePlatform))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/95503", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] public static void Test_ToLower_Culture() { foreach (object[] testdata in ToLower_Culture_TestData()) @@ -5943,7 +5940,6 @@ public static IEnumerable ToUpper_Culture_TestData() [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))] [ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnApplePlatform))] [MemberData(nameof(ToUpper_Culture_TestData))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/95503", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] public static void Test_ToUpper_Culture(string actual, string expected, CultureInfo culture) { Assert.Equal(expected, actual.ToUpper(culture)); diff --git a/src/libraries/Microsoft.VisualBasic.Core/tests/LateBindingTests.cs b/src/libraries/Microsoft.VisualBasic.Core/tests/LateBindingTests.cs index a95f101731dfc0..a04e4b0b29333e 100644 --- a/src/libraries/Microsoft.VisualBasic.Core/tests/LateBindingTests.cs +++ b/src/libraries/Microsoft.VisualBasic.Core/tests/LateBindingTests.cs @@ -9,7 +9,7 @@ namespace Microsoft.VisualBasic.CompilerServices.Tests { public class LateBindingTests { - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(LateCall_TestData))] public void LateCall(object obj, Type objType, string name, object[] args, string[] paramNames, bool[] copyBack, Func getResult, object expected) { @@ -17,14 +17,14 @@ public void LateCall(object obj, Type objType, string name, object[] args, strin Assert.Equal(expected, getResult(obj)); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(LateGet_TestData))] public void LateGet(object obj, Type objType, string name, object[] args, string[] paramNames, bool[] copyBack, object expected) { Assert.Equal(expected, LateBinding.LateGet(obj, objType, name, args, paramNames, copyBack)); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(LateSet_TestData))] public void LateSet(object obj, Type objType, string name, object[] args, string[] paramNames, Func getResult, object expected) { @@ -74,14 +74,14 @@ public void LateIndexSet(object obj, object[] args, string[] paramNames, Func(() => LateBinding.LateIndexSet(obj, args, paramNames)); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(LateIndexSetComplex_TestData))] public void LateIndexSetComplex(object obj, object[] args, string[] paramNames, bool missing, bool valueType) { diff --git a/src/libraries/Microsoft.VisualBasic.Core/tests/LikeOperatorTests.cs b/src/libraries/Microsoft.VisualBasic.Core/tests/LikeOperatorTests.cs index 892fbc5c31b5e1..1452be5b7fb333 100644 --- a/src/libraries/Microsoft.VisualBasic.Core/tests/LikeOperatorTests.cs +++ b/src/libraries/Microsoft.VisualBasic.Core/tests/LikeOperatorTests.cs @@ -9,7 +9,7 @@ namespace Microsoft.VisualBasic.CompilerServices.Tests { public class LikeOperatorTests { - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(LikeObject_TestData))] [MemberData(nameof(LikeString_TestData))] public void LikeObject(object source, object pattern, object expectedBinaryCompare, object expectedTextCompare) @@ -18,7 +18,7 @@ public void LikeObject(object source, object pattern, object expectedBinaryCompa Assert.Equal(expectedTextCompare, LikeOperator.LikeObject(source, pattern, CompareMethod.Text)); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(LikeString_TestData))] public void LikeString(string source, string pattern, bool expectedBinaryCompare, bool expectedTextCompare) { diff --git a/src/libraries/Microsoft.VisualBasic.Core/tests/ObjectTypeTests.cs b/src/libraries/Microsoft.VisualBasic.Core/tests/ObjectTypeTests.cs index d520d69d6cb6f6..f1e4b8ac6935f1 100644 --- a/src/libraries/Microsoft.VisualBasic.Core/tests/ObjectTypeTests.cs +++ b/src/libraries/Microsoft.VisualBasic.Core/tests/ObjectTypeTests.cs @@ -287,7 +287,7 @@ public static IEnumerable GetObjectValuePrimitive_TestData() // Add more... } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(LikeObj_TestData))] public void LikeObj(object left, object right, object expectedBinaryCompare, object expectedTextCompare) { @@ -323,7 +323,7 @@ public static IEnumerable LikeObj_NullReference_TestData() yield return new object[] { null, "*" }; } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(ObjTst_TestData))] public void ObjTst(object x, object y, bool textCompare, object expected) { diff --git a/src/libraries/Microsoft.VisualBasic.Core/tests/OperatorsTests.Comparison.cs b/src/libraries/Microsoft.VisualBasic.Core/tests/OperatorsTests.Comparison.cs index 9e300ec063a232..f90fd7d819824c 100644 --- a/src/libraries/Microsoft.VisualBasic.Core/tests/OperatorsTests.Comparison.cs +++ b/src/libraries/Microsoft.VisualBasic.Core/tests/OperatorsTests.Comparison.cs @@ -1107,7 +1107,7 @@ public static IEnumerable Compare_InvalidObjects_TestData() yield return new object[] { new object(), new char[] { '8' } }; } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(Compare_Primitives_TestData))] public void CompareObjectEqual_Invoke_ReturnsExpected(object left, object right, bool greater, bool equal, bool less) { @@ -1171,7 +1171,7 @@ public class CompareObjectEqual public static string op_Equality(OperatorsTests left, CompareObjectEqual right) => "tcejbomotsuc"; } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(Compare_Primitives_TestData))] public void CompareObjectGreater_Invoke_ReturnsExpected(object left, object right, bool greater, bool equal, bool less) { @@ -1235,7 +1235,7 @@ public class CompareObjectGreater public static string op_GreaterThan(OperatorsTests left, CompareObjectGreater right) => "tcejbomotsuc"; } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(Compare_Primitives_TestData))] public void CompareObjectGreaterEqual_Invoke_ReturnsExpected(object left, object right, bool greater, bool equal, bool less) { @@ -1298,7 +1298,7 @@ public class CompareObjectGreaterEqual public static string op_GreaterThanOrEqual(OperatorsTests left, CompareObjectGreaterEqual right) => "tcejbomotsuc"; } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(Compare_Primitives_TestData))] public void CompareObjectLess_Invoke_ReturnsExpected(object left, object right, bool greater, bool equal, bool less) { @@ -1362,7 +1362,7 @@ public class CompareObjectLess public static string op_LessThan(OperatorsTests left, CompareObjectLess right) => "tcejbomotsuc"; } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(Compare_Primitives_TestData))] public void CompareObjectLessEqual_Invoke_ReturnsExpected(object left, object right, bool greater, bool equal, bool less) { @@ -1425,7 +1425,7 @@ public class CompareObjectLessEqual public static string op_LessThanOrEqual(OperatorsTests left, CompareObjectLessEqual right) => "tcejbomotsuc"; } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(Compare_Primitives_TestData))] public void CompareObjectNotEqual_Invoke_ReturnsExpected(object left, object right, bool greater, bool equal, bool less) { @@ -1490,7 +1490,7 @@ public class CompareObjectNotEqual } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(Compare_Primitives_TestData))] public void ConditionalCompareObjectEqual_Invoke_ReturnsExpected(object left, object right, bool greater, bool equal, bool less) { @@ -1554,7 +1554,7 @@ public class ConditionalCompareObjectEqual public static bool op_Equality(OperatorsTests left, ConditionalCompareObjectEqual right) => true; } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(Compare_Primitives_TestData))] public void ConditionalCompareObjectGreater_Invoke_ReturnsExpected(object left, object right, bool greater, bool equal, bool less) { @@ -1618,7 +1618,7 @@ public class ConditionalCompareObjectGreater public static bool op_GreaterThan(OperatorsTests left, ConditionalCompareObjectGreater right) => true; } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(Compare_Primitives_TestData))] public void ConditionalCompareObjectGreaterEqual_Invoke_ReturnsExpected(object left, object right, bool greater, bool equal, bool less) { @@ -1681,7 +1681,7 @@ public class ConditionalCompareObjectGreaterEqual public static bool op_GreaterThanOrEqual(OperatorsTests left, ConditionalCompareObjectGreaterEqual right) => true; } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(Compare_Primitives_TestData))] public void ConditionalCompareObjectLess_Invoke_ReturnsExpected(object left, object right, bool greater, bool equal, bool less) { @@ -1745,7 +1745,7 @@ public class ConditionalCompareObjectLess public static bool op_LessThan(OperatorsTests left, ConditionalCompareObjectLess right) => true; } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(Compare_Primitives_TestData))] public void ConditionalCompareObjectLessEqual_Invoke_ReturnsExpected(object left, object right, bool greater, bool equal, bool less) { @@ -1808,7 +1808,7 @@ public class ConditionalCompareObjectLessEqual public static bool op_LessThanOrEqual(OperatorsTests left, ConditionalCompareObjectLessEqual right) => true; } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(Compare_Primitives_TestData))] public void ConditionalCompareObjectNotEqual_Invoke_ReturnsExpected(object left, object right, bool greater, bool equal, bool less) { diff --git a/src/libraries/Microsoft.VisualBasic.Core/tests/StringTypeTests.cs b/src/libraries/Microsoft.VisualBasic.Core/tests/StringTypeTests.cs index 76d390ea840e61..8d4d2842766d7f 100644 --- a/src/libraries/Microsoft.VisualBasic.Core/tests/StringTypeTests.cs +++ b/src/libraries/Microsoft.VisualBasic.Core/tests/StringTypeTests.cs @@ -363,7 +363,7 @@ public void MidStmtStr_ArgumentException(string str, int start, int length, stri Assert.Throws(() => StringType.MidStmtStr(ref str, start, length, insert)); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(StrCmp_TestData))] public void StrCmp(string left, string right, int expectedBinaryCompare, int expectedTextCompare) { @@ -388,7 +388,7 @@ public static IEnumerable StrCmp_TestData() yield return new object[] { "abc", "ABC", 32, 0 }; } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [InlineData(null, null, true, true)] [InlineData("", null, true, true)] [InlineData("", "*", true, true)] diff --git a/src/libraries/Microsoft.VisualBasic.Core/tests/StringsTests.cs b/src/libraries/Microsoft.VisualBasic.Core/tests/StringsTests.cs index 037412ed582757..27967fa777fe10 100644 --- a/src/libraries/Microsoft.VisualBasic.Core/tests/StringsTests.cs +++ b/src/libraries/Microsoft.VisualBasic.Core/tests/StringsTests.cs @@ -431,7 +431,7 @@ public void InStr_BinaryCompare(string string1, string string2, int expected) Assert.Equal(expected, Strings.InStr(1, string1, string2, CompareMethod.Binary)); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [InlineData("A", "a", 1)] [InlineData("Aa", "a", 1)] public void InStr_TextCompare(string string1, string string2, int expected) @@ -479,7 +479,7 @@ public void InStrRev_BinaryCompare(string stringCheck, string stringMatch, int s Assert.Equal(expected, Strings.InStrRev(stringCheck, stringMatch, start, CompareMethod.Binary)); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [InlineData("A", "a", 1, 1)] [InlineData("aA", "a", 2, 2)] public void InStrRev_TextCompare(string stringCheck, string stringMatch, int start, int expected) @@ -779,7 +779,7 @@ public void Trim_Valid(string str, string expected) Assert.Equal(expected, Strings.Trim(str)); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [InlineData("", "", null, 1, -1, CompareMethod.Text, null)] [InlineData("", null, "", 1, -1, CompareMethod.Text, null)] [InlineData("", "", "", 1, -1, CompareMethod.Text, null)] @@ -815,7 +815,7 @@ public void Space(int number, string expected) Assert.Equal(expected, Strings.Space(number)); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [InlineData(null, null, -1, CompareMethod.Text, new string[] { "" })] [InlineData(null, "", -1, CompareMethod.Text, new string[] { "" })] [InlineData("", null, -1, CompareMethod.Text, new string[] { "" })] @@ -833,14 +833,14 @@ public void Split(string expression, string delimiter, int limit, CompareMethod Assert.Equal(expected, Strings.Split(expression, delimiter, limit, compare)); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [InlineData("A, B, C", ", ", 0, CompareMethod.Text)] public void Split_IndexOutOfRangeException(string expression, string delimiter, int limit, CompareMethod compare) { Assert.Throws< IndexOutOfRangeException>(() => Strings.Split(expression, delimiter, limit, compare)); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [InlineData("a", "a", 0, 0)] [InlineData("a", "b", -1, -1)] [InlineData("b", "a", 1, 1)] diff --git a/src/libraries/System.Collections.NonGeneric/tests/CaseInsensitiveHashCodeProviderTests.cs b/src/libraries/System.Collections.NonGeneric/tests/CaseInsensitiveHashCodeProviderTests.cs index b0f4cc6c9ee3b9..ee5087139eb0eb 100644 --- a/src/libraries/System.Collections.NonGeneric/tests/CaseInsensitiveHashCodeProviderTests.cs +++ b/src/libraries/System.Collections.NonGeneric/tests/CaseInsensitiveHashCodeProviderTests.cs @@ -97,7 +97,6 @@ public void Ctor_CultureInfo_ChangeCurrentCulture_GetHashCodeCompare(object a, o [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/37069", TestPlatforms.Android | TestPlatforms.LinuxBionic)] [ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnApplePlatform))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/95503", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] public void Ctor_CultureInfo_GetHashCodeCompare_TurkishI() { var cultureNames = Helpers.TestCultureNames; diff --git a/src/libraries/System.Data.Common/tests/System.Data.DataSetExtensions.Tests/Mono/DataRowComparerTest.cs b/src/libraries/System.Data.Common/tests/System.Data.DataSetExtensions.Tests/Mono/DataRowComparerTest.cs index 0e8fb95a74e6df..6d4ddbe9b3f58d 100644 --- a/src/libraries/System.Data.Common/tests/System.Data.DataSetExtensions.Tests/Mono/DataRowComparerTest.cs +++ b/src/libraries/System.Data.Common/tests/System.Data.DataSetExtensions.Tests/Mono/DataRowComparerTest.cs @@ -216,7 +216,7 @@ public void Equals_Rows_Deleted() } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Fact] public void GetHashCodeWithVersions() { DataSet ds = new DataSet(); diff --git a/src/libraries/System.Data.Common/tests/System.Data.DataSetExtensions.Tests/Mono/DataTableExtensionsTest.cs b/src/libraries/System.Data.Common/tests/System.Data.DataSetExtensions.Tests/Mono/DataTableExtensionsTest.cs index 7f6d7bb12c0f29..479807057113e7 100644 --- a/src/libraries/System.Data.Common/tests/System.Data.DataSetExtensions.Tests/Mono/DataTableExtensionsTest.cs +++ b/src/libraries/System.Data.Common/tests/System.Data.DataSetExtensions.Tests/Mono/DataTableExtensionsTest.cs @@ -75,7 +75,7 @@ public void CopyToDataTableTableArgNoRows() dt.AsEnumerable().CopyToDataTable(dst, LoadOption.PreserveChanges); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Fact] public void AsEnumerable() { DataSet ds = new DataSet(); diff --git a/src/libraries/System.Data.Common/tests/System.Data.DataSetExtensions.Tests/Mono/EnumerableRowCollectionTest.cs b/src/libraries/System.Data.Common/tests/System.Data.DataSetExtensions.Tests/Mono/EnumerableRowCollectionTest.cs index ba292235291e37..a1f0dd6ec13ff8 100644 --- a/src/libraries/System.Data.Common/tests/System.Data.DataSetExtensions.Tests/Mono/EnumerableRowCollectionTest.cs +++ b/src/libraries/System.Data.Common/tests/System.Data.DataSetExtensions.Tests/Mono/EnumerableRowCollectionTest.cs @@ -41,7 +41,7 @@ public class EnumerableRowCollectionTest { private string _testDataSet = "Mono/testdataset1.xml"; - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Fact] public void QueryWhere() { var ds = new DataSet(); @@ -68,7 +68,7 @@ where line.Field("Score") > 80 } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Fact] public void QueryWhereSelect () { var ds = new DataSet (); @@ -89,7 +89,7 @@ where line.Field ("Score") > 80 } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Fact] public void QueryWhereSelectOrderBy () { var ds = new DataSet (); @@ -119,7 +119,7 @@ orderby line.Field ("ID") } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Fact] public void QueryWhereSelectOrderByDescending () { var ds = new DataSet (); @@ -149,7 +149,7 @@ orderby line.Field ("ID") descending } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Fact] public void ThenBy () { var ds = new DataSet (); @@ -179,7 +179,7 @@ orderby line.Field ("Gender"), line.Field ("ID") } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Fact] public void ThenByDescending () { var ds = new DataSet (); diff --git a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XsltArgumentList.cs b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XsltArgumentList.cs index 21603113f5cec4..71de03fbcabc9e 100644 --- a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XsltArgumentList.cs +++ b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XsltArgumentList.cs @@ -3252,7 +3252,7 @@ public void AddExtObject32(object param, XslInputType xslInputType, ReaderType r [InlineData("sort.xsl", "sort.txt", XslInputType.Navigator, ReaderType.XmlValidatingReader, OutputType.Writer, NavType.XPathDocument)] [InlineData("sort.xsl", "sort.txt", XslInputType.Navigator, ReaderType.XmlValidatingReader, OutputType.TextWriter, NavType.XPathDocument)] - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] public void AddExtObject33(object param0, object param1, XslInputType xslInputType, ReaderType readerType, OutputType outputType, NavType navType) { ExObj obj = new ExObj(0, _output); diff --git a/src/libraries/System.Runtime.Serialization.Json/tests/DataContractJsonSerializer.cs b/src/libraries/System.Runtime.Serialization.Json/tests/DataContractJsonSerializer.cs index 94cc7459d0b9e2..5de787c502988b 100644 --- a/src/libraries/System.Runtime.Serialization.Json/tests/DataContractJsonSerializer.cs +++ b/src/libraries/System.Runtime.Serialization.Json/tests/DataContractJsonSerializer.cs @@ -2599,7 +2599,7 @@ public static void DCJS_VerifyDateTimeForFormatStringDCJsonSerSetting() Assert.Equal(value, actual); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))] [ActiveIssue("https://github.com/dotnet/runtime/issues/60462", TestPlatforms.iOS | TestPlatforms.tvOS)] public static void DCJS_VerifyDateTimeForFormatStringDCJsonSerSettings() { @@ -2660,7 +2660,7 @@ public static void DCJS_VerifyDateTimeForFormatStringDCJsonSerSettings() Assert.True(actual6 == dateTime); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))] public static void DCJS_VerifyDateTimeForDateTimeFormat() { var jsonTypes = new JsonTypes(); diff --git a/src/libraries/System.Runtime.Serialization.Schema/tests/System/Runtime/Serialization/Schema/RoundTripTest.cs b/src/libraries/System.Runtime.Serialization.Schema/tests/System/Runtime/Serialization/Schema/RoundTripTest.cs index 8e26f7a0704b8a..f1a852b3a510ab 100644 --- a/src/libraries/System.Runtime.Serialization.Schema/tests/System/Runtime/Serialization/Schema/RoundTripTest.cs +++ b/src/libraries/System.Runtime.Serialization.Schema/tests/System/Runtime/Serialization/Schema/RoundTripTest.cs @@ -82,7 +82,6 @@ public void RountTripTest() [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/73961", typeof(PlatformDetection), nameof(PlatformDetection.IsBuiltWithAggressiveTrimming), nameof(PlatformDetection.IsBrowser))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/95981", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] public void RoundTripXmlSerializableWithSpecialAttributesTest() { XsdDataContractExporter exporter = new XsdDataContractExporter(); diff --git a/src/libraries/System.Runtime.Serialization.Xml/tests/DataContractSerializer.cs b/src/libraries/System.Runtime.Serialization.Xml/tests/DataContractSerializer.cs index e63c47b8207027..34550236dda8e0 100644 --- a/src/libraries/System.Runtime.Serialization.Xml/tests/DataContractSerializer.cs +++ b/src/libraries/System.Runtime.Serialization.Xml/tests/DataContractSerializer.cs @@ -3542,7 +3542,7 @@ public static void DCS_BasicPerSerializerRoundTripAndCompare_SampleTypes() } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Fact] public static void DCS_BasicPerSerializerRoundTripAndCompare_DataSet() { diff --git a/src/libraries/System.Runtime/System.Runtime.sln b/src/libraries/System.Runtime/System.Runtime.sln index 3bb4e42789fd32..4af0484c2667c7 100644 --- a/src/libraries/System.Runtime/System.Runtime.sln +++ b/src/libraries/System.Runtime/System.Runtime.sln @@ -77,8 +77,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Dynamic.Runtime.Test EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Globalization.CalendarsWithConfigSwitch.Tests", "tests\System.Globalization.Calendars.Tests\CalendarTestWithConfigSwitch\System.Globalization.CalendarsWithConfigSwitch.Tests.csproj", "{B73090B8-20CB-4586-A586-B7F37C1A06FF}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Globalization.Calendars.Hybrid.WASM.Tests", "tests\System.Globalization.Calendars.Tests\Hybrid\System.Globalization.Calendars.Hybrid.WASM.Tests.csproj", "{4C1F2761-857C-40A4-8CDD-7139380DA4D7}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Globalization.Calendars.IOS.Tests", "tests\System.Globalization.Calendars.Tests\Hybrid\System.Globalization.Calendars.IOS.Tests.csproj", "{70441C80-1F14-42F9-8225-A891E3C9A82A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Globalization.Calendars.Tests", "tests\System.Globalization.Calendars.Tests\System.Globalization.Calendars.Tests.csproj", "{EA3FA657-060E-43A3-9CF0-45FCC8E7E5B6}" @@ -91,8 +89,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Globalization.Extens EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppleHybrid.Tests", "tests\System.Globalization.Tests\Hybrid\AppleHybrid.Tests.csproj", "{988AECC5-6EB4-48AB-9467-3FB63619631B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Globalization.Hybrid.WASM.Tests", "tests\System.Globalization.Tests\Hybrid\System.Globalization.Hybrid.WASM.Tests.csproj", "{C5F86889-E147-4424-9165-D2DF453741F2}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Globalization.IOS.Tests", "tests\System.Globalization.Tests\Hybrid\System.Globalization.IOS.Tests.csproj", "{67D9B289-AA6D-4FD8-A99D-F8651A51BE7E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IcuAppLocal.Tests", "tests\System.Globalization.Tests\IcuAppLocal\IcuAppLocal.Tests.csproj", "{B6F2F0D5-9275-4F00-A2C3-2048AF0CAF12}" diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Calendars.Tests/Hybrid/System.Globalization.Calendars.Hybrid.WASM.Tests.csproj b/src/libraries/System.Runtime/tests/System.Globalization.Calendars.Tests/Hybrid/System.Globalization.Calendars.Hybrid.WASM.Tests.csproj deleted file mode 100644 index 9c93e65d291eb8..00000000000000 --- a/src/libraries/System.Runtime/tests/System.Globalization.Calendars.Tests/Hybrid/System.Globalization.Calendars.Hybrid.WASM.Tests.csproj +++ /dev/null @@ -1,116 +0,0 @@ - - - $(NetCoreAppCurrent)-browser - true - true - true - - - - WasmTestOnChrome - $(TestArchiveRoot)browseronly/ - $(TestArchiveTestsRoot)$(OSPlatformConfig)/ - $(DefineConstants);TARGET_BROWSER - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Calendars.Tests/System/Globalization/CalendarTestBase.cs b/src/libraries/System.Runtime/tests/System.Globalization.Calendars.Tests/System/Globalization/CalendarTestBase.cs index 061d07108e526b..62e1b360069875 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Calendars.Tests/System/Globalization/CalendarTestBase.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Calendars.Tests/System/Globalization/CalendarTestBase.cs @@ -429,8 +429,8 @@ public void GetEra_Invalid_ThrowsArgumentOutOfRangeException() Calendar calendar = Calendar; Assert.All(DateTime_TestData(calendar), dt => { - // JapaneseCalendar throws on ICU, but not on NLS or in HybridGlobalization on Browser - if ((calendar is JapaneseCalendar && (PlatformDetection.IsNlsGlobalization || PlatformDetection.IsHybridGlobalizationOnBrowser)) || calendar is HebrewCalendar || calendar is TaiwanLunisolarCalendar || calendar is JapaneseLunisolarCalendar) + // JapaneseCalendar throws on ICU, but not on NLS + if ((calendar is JapaneseCalendar && PlatformDetection.IsNlsGlobalization) || calendar is HebrewCalendar || calendar is TaiwanLunisolarCalendar || calendar is JapaneseLunisolarCalendar) { calendar.GetEra(dt); } diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.Compare.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.Compare.cs index fd20fa33ffbdc7..c3e5fd0e586a81 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.Compare.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.Compare.cs @@ -15,20 +15,15 @@ public class CompareInfoCompareTests : CompareInfoTestsBase public static IEnumerable Compare_Kana_TestData() { - // HybridGlobalization does not support IgnoreWidth - if (!PlatformDetection.IsHybridGlobalizationOnBrowser) - { - CompareOptions ignoreKanaIgnoreWidthIgnoreCase = CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase; - yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u3076\u30D9\uFF8E\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u30D0\u30D3\u3076\u30D9\uFF8E\uFF9E", "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u3060", "\uFF80\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u30C7\u30BF\u30D9\u30B9", "\uFF83\uFF9E\uFF80\uFF8D\uFF9E\uFF7D", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u30C7", "\uFF83\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u30C7\u30BF", "\uFF83\uFF9E\uFF80", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u30C7\u30BF\u30D9", "\uFF83\uFF9E\uFF80\uFF8D\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "\u3067\u30FC\u305F\u3079\u30FC\u3059", "\uFF83\uFF9E\uFF70\uFF80\uFF8D\uFF9E\uFF70\uFF7D", ignoreKanaIgnoreWidthIgnoreCase, 0 }; - } - + CompareOptions ignoreKanaIgnoreWidthIgnoreCase = CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase; + yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u3076\u30D9\uFF8E\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u30D0\u30D3\u3076\u30D9\uFF8E\uFF9E", "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u3060", "\uFF80\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u30C7\u30BF\u30D9\u30B9", "\uFF83\uFF9E\uFF80\uFF8D\uFF9E\uFF7D", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u30C7", "\uFF83\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u30C7\u30BF", "\uFF83\uFF9E\uFF80", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u30C7\u30BF\u30D9", "\uFF83\uFF9E\uFF80\uFF8D\uFF9E", ignoreKanaIgnoreWidthIgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "\u3067\u30FC\u305F\u3079\u30FC\u3059", "\uFF83\uFF9E\uFF70\uFF80\uFF8D\uFF9E\uFF70\uFF7D", ignoreKanaIgnoreWidthIgnoreCase, 0 }; yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u3076\u30D9\uFF8E\uFF9E", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; yield return new object[] { s_invariantCompare, "\u3060", "\uFF80\uFF9E", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; @@ -43,12 +38,6 @@ public static IEnumerable Compare_Kana_TestData() public static IEnumerable Compare_TestData() { - // PlatformDetection.IsHybridGlobalizationOnBrowser does not support IgnoreNonSpace alone, it needs to be with IgnoreKanaType - CompareOptions validIgnoreNonSpaceOption = - PlatformDetection.IsHybridGlobalizationOnBrowser - ? CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreKanaType - : CompareOptions.IgnoreNonSpace; - #region Numeric ordering if (PlatformDetection.IsNumericComparisonSupported) { @@ -84,7 +73,7 @@ public static IEnumerable Compare_TestData() yield return new object[] { s_invariantCompare, "A01", "a1", CompareOptions.NumericOrdering, isNls ? -1 : 1 }; // ICU treats 01 == 1 // With diacritics - yield return new object[] { s_invariantCompare, "1\u00E102", "1a02", CompareOptions.NumericOrdering | validIgnoreNonSpaceOption, 0 }; + yield return new object[] { s_invariantCompare, "1\u00E102", "1a02", CompareOptions.NumericOrdering | CompareOptions.IgnoreNonSpace, 0 }; yield return new object[] { s_invariantCompare, "\u00E11", "a2", CompareOptions.NumericOrdering, -1 }; // Numerical differences have higher precedence yield return new object[] { s_invariantCompare, "\u00E101", "a1", CompareOptions.NumericOrdering, isNls ? -1 : 1 }; // ICU treats 01 == 1 @@ -93,30 +82,21 @@ public static IEnumerable Compare_TestData() } #endregion - // PlatformDetection.IsHybridGlobalizationOnBrowser does not support IgnoreKanaType alone, it needs to be e.g. with IgnoreCase - CompareOptions validIgnoreKanaTypeOption = PlatformDetection.IsHybridGlobalizationOnBrowser ? - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreCase : - CompareOptions.IgnoreKanaType; - yield return new object[] { s_invariantCompare, "\u3042", "\u30A2", validIgnoreKanaTypeOption, 0 }; - yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u30E3", validIgnoreKanaTypeOption, 0 }; - yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u3083", validIgnoreKanaTypeOption, 0 }; - yield return new object[] { s_invariantCompare, "\u304D \u3083", "\u30AD\u3083", validIgnoreKanaTypeOption, -1 }; - yield return new object[] { s_invariantCompare, "\u3044", "I", validIgnoreKanaTypeOption, 1 }; - yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u30D6\u30D9\u30DC", validIgnoreKanaTypeOption, 0 }; - yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u3076\u30D9\u30DC", validIgnoreKanaTypeOption, 0 }; - yield return new object[] { s_invariantCompare, "\u3060", "\u305F", validIgnoreKanaTypeOption, 1 }; - yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", validIgnoreKanaTypeOption, 0 }; - yield return new object[] { s_invariantCompare, "\u68EE\u9D0E\u5916", "\u68EE\u9DD7\u5916", validIgnoreKanaTypeOption, -1 }; - yield return new object[] { s_invariantCompare, "\u2019", "'", validIgnoreKanaTypeOption, 1 }; - yield return new object[] { s_invariantCompare, "", "'", validIgnoreKanaTypeOption, -1 }; + yield return new object[] { s_invariantCompare, "\u3042", "\u30A2", CompareOptions.IgnoreKanaType, 0 }; + yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u30E3", CompareOptions.IgnoreKanaType, 0 }; + yield return new object[] { s_invariantCompare, "\u304D\u3083", "\u30AD\u3083", CompareOptions.IgnoreKanaType, 0 }; + yield return new object[] { s_invariantCompare, "\u304D \u3083", "\u30AD\u3083", CompareOptions.IgnoreKanaType, -1 }; + yield return new object[] { s_invariantCompare, "\u3044", "I", CompareOptions.IgnoreKanaType, 1 }; + yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u30D6\u30D9\u30DC", CompareOptions.IgnoreKanaType, 0 }; + yield return new object[] { s_invariantCompare, "\u3070\u3073\u3076\u3079\u307C", "\u30D0\u30D3\u3076\u30D9\u30DC", CompareOptions.IgnoreKanaType, 0 }; + yield return new object[] { s_invariantCompare, "\u3060", "\u305F", CompareOptions.IgnoreKanaType, 1 }; + yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", CompareOptions.IgnoreKanaType, 0 }; + yield return new object[] { s_invariantCompare, "\u68EE\u9D0E\u5916", "\u68EE\u9DD7\u5916", CompareOptions.IgnoreKanaType, -1 }; + yield return new object[] { s_invariantCompare, "\u2019", "'", CompareOptions.IgnoreKanaType, 1 }; + yield return new object[] { s_invariantCompare, "", "'", CompareOptions.IgnoreKanaType, -1 }; yield return new object[] { s_invariantCompare, "\u30FC", "\uFF70", CompareOptions.IgnoreKanaType | CompareOptions.IgnoreCase, 0 }; - // PlatformDetection.IsHybridGlobalizationOnBrowser does not support IgnoreWidth - if (!PlatformDetection.IsHybridGlobalizationOnBrowser) - { - yield return new object[] { s_invariantCompare, "\u3042", "\uFF71", CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "'\u3000'", "' '", CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, PlatformDetection.IsHybridGlobalizationOnApplePlatform ? 1 : 0 }; - } - + yield return new object[] { s_invariantCompare, "\u3042", "\uFF71", CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "'\u3000'", "' '", CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, PlatformDetection.IsHybridGlobalizationOnApplePlatform ? 1 : 0 }; yield return new object[] { s_invariantCompare, "\u6FA4", "\u6CA2", CompareOptions.None, 1 }; yield return new object[] { s_invariantCompare, "\u3070\u3073\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\uFF8E\uFF9E", CompareOptions.None, -1 }; yield return new object[] { s_invariantCompare, "\u3070\u30DC\uFF8C\uFF9E\uFF8D\uFF9E\u307C", "\uFF8E\uFF9E", CompareOptions.None, -1 }; @@ -138,25 +118,21 @@ public static IEnumerable Compare_TestData() yield return new object[] { s_invariantCompare, "\u30FC", "\u2010", CompareOptions.None, 1 }; yield return new object[] { s_invariantCompare, "\u68EE\u9DD7\u5916", "\u68EE\u9DD7\u5916", CompareOptions.IgnoreCase, 0 }; yield return new object[] { s_invariantCompare, "a", "A", CompareOptions.IgnoreCase, 0 }; - // PlatformDetection.IsHybridGlobalizationOnBrowser does not support IgnoreWidth - if (!PlatformDetection.IsHybridGlobalizationOnBrowser) - { - yield return new object[] { s_invariantCompare, "a", "\uFF41", CompareOptions.IgnoreWidth, 0 }; - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23\uFF24\uFF25", CompareOptions.IgnoreWidth, 0 }; - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23D\uFF25", CompareOptions.IgnoreWidth, 0 }; - yield return new object[] { s_invariantCompare, "ABCDE", "a\uFF22\uFF23D\uFF25", CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF41\uFF42\uFF23D\uFF25", CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, 0 }; - yield return new object[] { s_invariantCompare, "ABCDE", "\uFF43D", CompareOptions.IgnoreWidth, -1 }; - yield return new object[] { s_invariantCompare, "ABCDE", "c", CompareOptions.IgnoreWidth, -1 }; - yield return new object[] { s_invariantCompare, "\u30BF", "\uFF80", CompareOptions.IgnoreWidth, 0 }; - yield return new object[] { s_invariantCompare, "0", "\uFF10", CompareOptions.IgnoreWidth, 0 }; - yield return new object[] { s_invariantCompare, "10", "1\uFF10", CompareOptions.IgnoreWidth, 0 }; - yield return new object[] { s_invariantCompare, "\uFF1B", ";", CompareOptions.IgnoreWidth, 0 }; - yield return new object[] { s_invariantCompare, "\uFF08", "(", CompareOptions.IgnoreWidth, 0 }; - yield return new object[] { s_invariantCompare, "/", "\uFF0F", CompareOptions.IgnoreWidth, 0 }; - yield return new object[] { s_invariantCompare, "'", "\uFF07", CompareOptions.IgnoreWidth, 0 }; - yield return new object[] { s_invariantCompare, "\"", "\uFF02", CompareOptions.IgnoreWidth, 0 }; - } + yield return new object[] { s_invariantCompare, "a", "\uFF41", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23\uFF24\uFF25", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "ABCDE", "\uFF21\uFF22\uFF23D\uFF25", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "ABCDE", "a\uFF22\uFF23D\uFF25", CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "ABCDE", "\uFF41\uFF42\uFF23D\uFF25", CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, 0 }; + yield return new object[] { s_invariantCompare, "ABCDE", "\uFF43D", CompareOptions.IgnoreWidth, -1 }; + yield return new object[] { s_invariantCompare, "ABCDE", "c", CompareOptions.IgnoreWidth, -1 }; + yield return new object[] { s_invariantCompare, "\u30BF", "\uFF80", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "0", "\uFF10", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "10", "1\uFF10", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "\uFF1B", ";", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "\uFF08", "(", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "/", "\uFF0F", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "'", "\uFF07", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "\"", "\uFF02", CompareOptions.IgnoreWidth, 0 }; yield return new object[] { s_invariantCompare, "\u3042", "\u30A1", CompareOptions.None, PlatformDetection.IsHybridGlobalizationOnApplePlatform ? 1 : s_expectedHiraganaToKatakanaCompare }; yield return new object[] { s_invariantCompare, "\u3042", "\u30A2", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; yield return new object[] { s_invariantCompare, "\u3042", "\uFF71", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; @@ -247,7 +223,7 @@ public static IEnumerable Compare_TestData() yield return new object[] { s_invariantCompare, "\u00C0", "a\u0300", CompareOptions.Ordinal, 1 }; yield return new object[] { s_invariantCompare, "\u00C0", "a\u0300", CompareOptions.OrdinalIgnoreCase, 1 }; yield return new object[] { s_invariantCompare, "FooBar", "Foo\u0400Bar", CompareOptions.Ordinal, -1 }; - yield return new object[] { s_invariantCompare, "FooBA\u0300R", "FooB\u00C0R", validIgnoreNonSpaceOption, 0 }; + yield return new object[] { s_invariantCompare, "FooBA\u0300R", "FooB\u00C0R", CompareOptions.IgnoreNonSpace, 0 }; // In HybridGlobalization on Apple platforms IgnoreSymbols is not supported if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) @@ -268,11 +244,11 @@ public static IEnumerable Compare_TestData() yield return new object[] { s_invariantCompare, "", "", CompareOptions.None, 0 }; yield return new object[] { s_invariantCompare, new string('a', 5555), new string('a', 5555), CompareOptions.None, 0 }; - yield return new object[] { s_invariantCompare, "foobar", "FooB\u00C0R", supportedIgnoreCaseIgnoreNonSpaceOptions, 0 }; - yield return new object[] { s_invariantCompare, "foobar", "FooB\u00C0R", validIgnoreNonSpaceOption, -1 }; + yield return new object[] { s_invariantCompare, "foobar", "FooB\u00C0R", CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, 0 }; + yield return new object[] { s_invariantCompare, "foobar", "FooB\u00C0R", CompareOptions.IgnoreNonSpace, -1 }; - yield return new object[] { s_invariantCompare, "\uFF9E", "\u3099", validIgnoreNonSpaceOption, 0 }; - yield return new object[] { s_invariantCompare, "\uFF9E", "\u3099", CompareOptions.IgnoreCase, PlatformDetection.IsHybridGlobalizationOnBrowser ? 1 : 0 }; + yield return new object[] { s_invariantCompare, "\uFF9E", "\u3099", CompareOptions.IgnoreNonSpace, 0 }; + yield return new object[] { s_invariantCompare, "\uFF9E", "\u3099", CompareOptions.IgnoreCase, 0 }; yield return new object[] { s_invariantCompare, "\u20A9", "\uFFE6", CompareOptions.IgnoreCase, -1 }; yield return new object[] { s_invariantCompare, "\u20A9", "\uFFE6", CompareOptions.None, -1 }; @@ -281,21 +257,13 @@ public static IEnumerable Compare_TestData() { yield return new object[] { s_invariantCompare, "\u0021", "\uFF01", CompareOptions.IgnoreSymbols, 0 }; yield return new object[] { s_invariantCompare, "\uFF65", "\u30FB", CompareOptions.IgnoreSymbols, 0 }; - // some symbols e.g. currencies are not ignored correctly in HybridGlobalization - if (!PlatformDetection.IsHybridGlobalizationOnBrowser) - { - yield return new object[] { s_invariantCompare, "\u00A2", "\uFFE0", CompareOptions.IgnoreSymbols, 0 }; - yield return new object[] { s_invariantCompare, "$", "&", CompareOptions.IgnoreSymbols, 0 }; - } + yield return new object[] { s_invariantCompare, "\u00A2", "\uFFE0", CompareOptions.IgnoreSymbols, 0 }; + yield return new object[] { s_invariantCompare, "$", "&", CompareOptions.IgnoreSymbols, 0 }; } yield return new object[] { s_invariantCompare, "\u0021", "\uFF01", CompareOptions.None, -1 }; - - if (!PlatformDetection.IsHybridGlobalizationOnBrowser) - { - yield return new object[] { s_invariantCompare, "\u20A9", "\uFFE6", CompareOptions.IgnoreWidth, 0 }; - yield return new object[] { s_invariantCompare, "\u0021", "\uFF01", CompareOptions.IgnoreWidth, 0 }; - yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreWidth, 0 }; - } + yield return new object[] { s_invariantCompare, "\u20A9", "\uFFE6", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "\u0021", "\uFF01", CompareOptions.IgnoreWidth, 0 }; + yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreWidth, 0 }; // In HybridGlobalization mode on Apple platforms IgnoreSymbols is not supported if(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) @@ -304,16 +272,10 @@ public static IEnumerable Compare_TestData() } yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreCase, s_expectedHalfToFullFormsComparison }; - // in HybridGlobalization on Browser IgnoreNonSpace is not supported and comparison of katakana/hiragana equivalents with supportedIgnoreNonSpaceOption gives 0 - if (!PlatformDetection.IsHybridGlobalizationOnBrowser) - yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreNonSpace, s_expectedHalfToFullFormsComparison }; + yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.IgnoreNonSpace, s_expectedHalfToFullFormsComparison }; yield return new object[] { s_invariantCompare, "\uFF66", "\u30F2", CompareOptions.None, s_expectedHalfToFullFormsComparison }; - - // in HybridGlobalization on Browser IgnoreKanaType is supported only for "ja" - var kanaComparison = PlatformDetection.IsHybridGlobalizationOnBrowser ? s_japaneseCompare : s_invariantCompare; - - yield return new object[] { kanaComparison, "\u3060", "\u30C0", CompareOptions.IgnoreKanaType, 0 }; - yield return new object[] { kanaComparison, "c", "C", CompareOptions.IgnoreKanaType, -1 }; + yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", CompareOptions.IgnoreKanaType, 0 }; + yield return new object[] { s_invariantCompare, "c", "C", CompareOptions.IgnoreKanaType, -1 }; yield return new object[] { s_invariantCompare, "\u3060", "\u30C0", CompareOptions.IgnoreCase, PlatformDetection.IsHybridGlobalizationOnApplePlatform ? 1 : s_expectedHiraganaToKatakanaCompare }; @@ -324,12 +286,12 @@ public static IEnumerable Compare_TestData() yield return new object[] { s_invariantCompare, "\u30CF", "\u30D0", CompareOptions.IgnoreCase, -1 }; yield return new object[] { s_invariantCompare, "\u30CF", "\u30D1", CompareOptions.IgnoreCase, -1 }; yield return new object[] { s_invariantCompare, "\u30D0", "\u30D1", CompareOptions.IgnoreCase, -1 }; - yield return new object[] { s_invariantCompare, "\u306F", "\u3070", validIgnoreNonSpaceOption, 0 }; - yield return new object[] { s_invariantCompare, "\u306F", "\u3071", validIgnoreNonSpaceOption, 0 }; - yield return new object[] { s_invariantCompare, "\u3070", "\u3071", validIgnoreNonSpaceOption, 0 }; - yield return new object[] { s_invariantCompare, "\u30CF", "\u30D0", validIgnoreNonSpaceOption, 0 }; - yield return new object[] { s_invariantCompare, "\u30CF", "\u30D1", validIgnoreNonSpaceOption, 0 }; - yield return new object[] { s_invariantCompare, "\u30D0", "\u30D1", validIgnoreNonSpaceOption, 0 }; + yield return new object[] { s_invariantCompare, "\u306F", "\u3070", CompareOptions.IgnoreNonSpace, 0 }; + yield return new object[] { s_invariantCompare, "\u306F", "\u3071", CompareOptions.IgnoreNonSpace, 0 }; + yield return new object[] { s_invariantCompare, "\u3070", "\u3071", CompareOptions.IgnoreNonSpace, 0 }; + yield return new object[] { s_invariantCompare, "\u30CF", "\u30D0", CompareOptions.IgnoreNonSpace, 0 }; + yield return new object[] { s_invariantCompare, "\u30CF", "\u30D1", CompareOptions.IgnoreNonSpace, 0 }; + yield return new object[] { s_invariantCompare, "\u30D0", "\u30D1", CompareOptions.IgnoreNonSpace, 0 }; // Spanish yield return new object[] { new CultureInfo("es-ES").CompareInfo, "llegar", "lugar", CompareOptions.None, -1 }; @@ -337,10 +299,7 @@ public static IEnumerable Compare_TestData() // Misc differences between platforms bool useNls = PlatformDetection.IsNlsGlobalization; - var japaneseCmp = PlatformDetection.IsHybridGlobalizationOnBrowser ? - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreCase : - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase; - + var japaneseCmp = CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase; yield return new object[] { s_invariantCompare, "\u3042", "\u30A1", japaneseCmp, useNls || PlatformDetection.IsHybridGlobalizationOnApplePlatform ? 1: 0 }; yield return new object[] { s_invariantCompare, "'\u3000'", "''", japaneseCmp, useNls ? 1 : -1 }; @@ -376,7 +335,7 @@ public void CompareWithUnassignedChars() { int result = PlatformDetection.IsNlsGlobalization ? 0 : -1; Compare(s_invariantCompare, "FooBar", "Foo\uFFFFBar", CompareOptions.None, result); - Compare(s_invariantCompare, "FooBar", "Foo\uFFFFBar", supportedIgnoreNonSpaceOption, result); + Compare(s_invariantCompare, "FooBar", "Foo\uFFFFBar", CompareOptions.IgnoreNonSpace, result); } [ConditionalTheory(nameof(IsNotWindowsKanaRegressedVersion))] @@ -622,22 +581,7 @@ public void TestIgnoreKanaAndWidthCases() public static IEnumerable Compare_HiraganaAndKatakana_TestData() { - CompareOptions[] optionsPositive = PlatformDetection.IsHybridGlobalizationOnBrowser ? - new[] { - CompareOptions.None, - CompareOptions.IgnoreCase, - CompareOptions.IgnoreSymbols, - CompareOptions.IgnoreSymbols | CompareOptions.IgnoreCase, - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreSymbols, - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreCase, - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreNonSpace, - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols, - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace, - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, - CompareOptions.Ordinal, - CompareOptions.OrdinalIgnoreCase, - } : PlatformDetection.IsHybridGlobalizationOnApplePlatform ? + CompareOptions[] data = PlatformDetection.IsHybridGlobalizationOnApplePlatform ? new[] { CompareOptions.None, CompareOptions.IgnoreCase, @@ -675,45 +619,21 @@ public static IEnumerable Compare_HiraganaAndKatakana_TestData() CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, }; - CompareOptions[] optionsNegative = PlatformDetection.IsHybridGlobalizationOnBrowser ? - new[] { - CompareOptions.IgnoreNonSpace, - CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, - CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace, - CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, - CompareOptions.IgnoreWidth, - CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, - CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace, - CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, - CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols, - CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreCase, - CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace, - CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, - CompareOptions.IgnoreKanaType, - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth, - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase, - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace, - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols, - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreCase, - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace, - CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase, - } : + CompareOptions[] optionsNegative = Array.Empty(); if (PlatformDetection.IsNumericComparisonSupported) { // Adding NumericOrdering does not affect whether an option set is supported or not - optionsPositive = optionsPositive.Concat(from opt in optionsPositive where opt != CompareOptions.Ordinal && opt != CompareOptions.OrdinalIgnoreCase select opt | CompareOptions.NumericOrdering).ToArray(); - optionsNegative = optionsNegative.Concat(from opt in optionsNegative where opt != CompareOptions.Ordinal && opt != CompareOptions.OrdinalIgnoreCase select opt | CompareOptions.NumericOrdering).ToArray(); + data = data.Concat(from opt in data where opt != CompareOptions.Ordinal && opt != CompareOptions.OrdinalIgnoreCase select opt | CompareOptions.NumericOrdering).ToArray(); } - yield return new object[] { optionsPositive, optionsNegative }; + yield return new object[] { data }; } [Theory] [MemberData(nameof(Compare_HiraganaAndKatakana_TestData))] - public void TestHiraganaAndKatakana(CompareOptions[] optionsPositive, CompareOptions[] optionsNegative) + public void TestHiraganaAndKatakana(CompareOptions[] data) { const char hiraganaStart = '\u3041'; const char hiraganaEnd = '\u3096'; @@ -747,7 +667,7 @@ public void TestHiraganaAndKatakana(CompareOptions[] optionsPositive, CompareOpt { hiraganaList.Add(c); } - foreach (var option in optionsPositive) + foreach (var option in data) { for (int i = 0; i < hiraganaList.Count; i++) { @@ -766,13 +686,6 @@ public void TestHiraganaAndKatakana(CompareOptions[] optionsPositive, CompareOpt } } } - foreach (var option in optionsNegative) - { - char hiraganaChar1 = hiraganaList[0]; - char katakanaChar1 = (char)(hiraganaChar1 + hiraganaToKatakanaOffset); - Assert.Throws( - () => s_invariantCompare.Compare(new string(hiraganaChar1, 1), new string(katakanaChar1, 1), option)); - } } } } diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IndexOf.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IndexOf.cs index 2750a648bb80d9..77a972222a4a27 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IndexOf.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IndexOf.cs @@ -33,7 +33,7 @@ public static IEnumerable IndexOf_TestData() yield return new object[] { s_invariantCompare, "foobardzsdzs", "rddzs", 0, 12, CompareOptions.Ordinal, -1, 0 }; // Slovak - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { yield return new object[] { s_slovakCompare, "ch", "h", 0, 2, CompareOptions.None, -1, 0 }; // Android has its own ICU, which doesn't work well with slovak @@ -66,23 +66,16 @@ public static IEnumerable IndexOf_TestData() yield return new object[] { s_invariantCompare, "Exhibit \u00C0", "a\u0300", 0, 9, CompareOptions.IgnoreCase, 8, 1 }; yield return new object[] { s_invariantCompare, "Exhibit \u00C0", "a\u0300", 0, 9, CompareOptions.OrdinalIgnoreCase, -1, 0 }; yield return new object[] { s_invariantCompare, "FooBar", "Foo\u0400Bar", 0, 6, CompareOptions.Ordinal, -1, 0 }; - yield return new object[] { s_invariantCompare, "TestFooBA\u0300R", "FooB\u00C0R", 0, 11, supportedIgnoreNonSpaceOption, 4, 7 }; + yield return new object[] { s_invariantCompare, "TestFooBA\u0300R", "FooB\u00C0R", 0, 11, CompareOptions.IgnoreNonSpace, 4, 7 }; yield return new object[] { s_invariantCompare, "o\u0308", "o", 0, 2, CompareOptions.None, -1, 0 }; - if (PlatformDetection.IsHybridGlobalizationOnBrowser) - { - yield return new object[] { s_invariantCompare, "\r\n", "\n", 0, 2, CompareOptions.None, -1, 0 }; - } - else - { - yield return new object[] { s_invariantCompare, "\r\n", "\n", 0, 2, CompareOptions.None, 1, 1 }; - } + yield return new object[] { s_invariantCompare, "\r\n", "\n", 0, 2, CompareOptions.None, 1, 1 }; // Weightless characters yield return new object[] { s_invariantCompare, "", "\u200d", 0, 0, CompareOptions.None, 0, 0 }; yield return new object[] { s_invariantCompare, "hello", "\u200d", 1, 3, CompareOptions.IgnoreCase, 1, 0 }; // Ignore symbols - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) yield return new object[] { s_invariantCompare, "More Test's", "Tests", 0, 11, CompareOptions.IgnoreSymbols, 5, 6 }; yield return new object[] { s_invariantCompare, "More Test's", "Tests", 0, 11, CompareOptions.None, -1, 0 }; yield return new object[] { s_invariantCompare, "cbabababdbaba", "ab", 0, 13, CompareOptions.None, 2, 2 }; @@ -126,16 +119,12 @@ public static IEnumerable IndexOf_TestData() yield return new object[] { s_currentCompare, "\u0131", "\u0131", 0, 1, CompareOptions.Ordinal, 0, 1 }; yield return new object[] { s_currentCompare, "\u0130", "\u0131", 0, 1, CompareOptions.Ordinal, -1, 0 }; yield return new object[] { s_currentCompare, "\u0131", "\u0130", 0, 1, CompareOptions.Ordinal, -1, 0 }; - - if (!PlatformDetection.IsHybridGlobalizationOnBrowser) - { - yield return new object[] { s_invariantCompare, "est", "est", 0, 2, CompareOptions.IgnoreCase, 0, 2 }; - yield return new object[] { s_invariantCompare, " est", "est", 0, 3, CompareOptions.IgnoreCase, 1, 2 }; - yield return new object[] { s_invariantCompare, " st", "st", 0, 2, CompareOptions.IgnoreCase, 1, 1 }; - yield return new object[] { s_invariantCompare, "est", "est", 0, 3, CompareOptions.IgnoreCase, 0, 3 }; - yield return new object[] { s_invariantCompare, " est", "est", 0, 4, CompareOptions.IgnoreCase, 1, 3 }; - yield return new object[] { s_invariantCompare, " st", "st", 0, 3, CompareOptions.IgnoreCase, 1, 2 }; - } + yield return new object[] { s_invariantCompare, "est", "est", 0, 2, CompareOptions.IgnoreCase, 0, 2 }; + yield return new object[] { s_invariantCompare, " est", "est", 0, 3, CompareOptions.IgnoreCase, 1, 2 }; + yield return new object[] { s_invariantCompare, " st", "st", 0, 2, CompareOptions.IgnoreCase, 1, 1 }; + yield return new object[] { s_invariantCompare, "est", "est", 0, 3, CompareOptions.IgnoreCase, 0, 3 }; + yield return new object[] { s_invariantCompare, " est", "est", 0, 4, CompareOptions.IgnoreCase, 1, 3 }; + yield return new object[] { s_invariantCompare, " st", "st", 0, 3, CompareOptions.IgnoreCase, 1, 2 }; // Platform differences if (PlatformDetection.IsNlsGlobalization) @@ -148,18 +137,18 @@ public static IEnumerable IndexOf_TestData() } // Inputs where matched length does not equal value string length - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { - yield return new object[] { s_germanCompare, "abc Strasse Strasse xyz", "stra\u00DFe", 0, 23, supportedIgnoreCaseIgnoreNonSpaceOptions, 4, 7 }; - yield return new object[] { s_germanCompare, "abc stra\u00DFe stra\u00DFe xyz", "Strasse", 0, 21, supportedIgnoreCaseIgnoreNonSpaceOptions, 4, 6 }; + yield return new object[] { s_germanCompare, "abc Strasse Strasse xyz", "stra\u00DFe", 0, 23, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, 4, 7 }; + yield return new object[] { s_germanCompare, "abc stra\u00DFe stra\u00DFe xyz", "Strasse", 0, 21, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, 4, 6 }; if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { - yield return new object[] { s_invariantCompare, "abcdzxyz", "\u01F3", 0, 8, supportedIgnoreNonSpaceOption, 3, 2 }; - yield return new object[] { s_invariantCompare, "abc\u01F3xyz", "dz", 0, 7, supportedIgnoreNonSpaceOption, 3, 1 }; + yield return new object[] { s_invariantCompare, "abcdzxyz", "\u01F3", 0, 8, CompareOptions.IgnoreNonSpace, 3, 2 }; + yield return new object[] { s_invariantCompare, "abc\u01F3xyz", "dz", 0, 7, CompareOptions.IgnoreNonSpace, 3, 1 }; } } - yield return new object[] { s_germanCompare, "abc Strasse Strasse xyz", "xtra\u00DFe", 0, 23, supportedIgnoreCaseIgnoreNonSpaceOptions, -1, 0 }; - yield return new object[] { s_germanCompare, "abc stra\u00DFe stra\u00DFe xyz", "Xtrasse", 0, 21, supportedIgnoreCaseIgnoreNonSpaceOptions, -1, 0 }; + yield return new object[] { s_germanCompare, "abc Strasse Strasse xyz", "xtra\u00DFe", 0, 23, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, -1, 0 }; + yield return new object[] { s_germanCompare, "abc stra\u00DFe stra\u00DFe xyz", "Xtrasse", 0, 21, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, -1, 0 }; } public static IEnumerable IndexOf_Aesc_Ligature_TestData() @@ -256,7 +245,7 @@ static void RunSpanIndexOfTest(CompareInfo compareInfo, ReadOnlySpan sourc valueBoundedMemory.MakeReadonly(); Assert.Equal(expected, compareInfo.IndexOf(sourceBoundedMemory.Span, valueBoundedMemory.Span, options)); - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { Assert.Equal(expected, compareInfo.IndexOf(sourceBoundedMemory.Span, valueBoundedMemory.Span, options, out int actualMatchLength)); Assert.Equal(expectedMatchLength, actualMatchLength); @@ -303,7 +292,7 @@ public void IndexOf_UnassignedUnicode() bool useNls = PlatformDetection.IsNlsGlobalization; int expectedMatchLength = (useNls) ? 6 : 0; IndexOf_String(s_invariantCompare, "FooBar", "Foo\uFFFFBar", 0, 6, CompareOptions.None, useNls ? 0 : -1, expectedMatchLength); - IndexOf_String(s_invariantCompare, "~FooBar", "Foo\uFFFFBar", 0, 7, supportedIgnoreNonSpaceOption, useNls ? 1 : -1, expectedMatchLength); + IndexOf_String(s_invariantCompare, "~FooBar", "Foo\uFFFFBar", 0, 7, CompareOptions.IgnoreNonSpace, useNls ? 1 : -1, expectedMatchLength); } [Fact] @@ -353,7 +342,7 @@ public void IndexOf_Invalid() AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's", 'c', 0, 2, CompareOptions.NumericOrdering)); AssertExtensions.Throws("options", () => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.NumericOrdering)); - if (PlatformDetection.IsHybridGlobalizationOnBrowser || PlatformDetection.IsHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsHybridGlobalizationOnApplePlatform) { Assert.Throws(() => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.StringSort, out _)); Assert.Throws(() => s_invariantCompare.IndexOf("Test's".AsSpan(), "b".AsSpan(), CompareOptions.NumericOrdering, out _)); diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsPrefix.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsPrefix.cs index 7dba521344e1c0..3fba71b155c171 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsPrefix.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsPrefix.cs @@ -25,7 +25,7 @@ public static IEnumerable IsPrefix_TestData() yield return new object[] { s_invariantCompare, "dzsdzsfoobar", "ddzsf", CompareOptions.Ordinal, false, 0 }; yield return new object[] { s_hungarianCompare, "dzsdzsfoobar", "ddzsf", CompareOptions.Ordinal, false, 0 }; yield return new object[] { s_invariantCompare, "dz", "d", CompareOptions.None, true, 1 }; - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) yield return new object[] { s_hungarianCompare, "dz", "d", CompareOptions.None, false, 0 }; yield return new object[] { s_hungarianCompare, "dz", "d", CompareOptions.Ordinal, true, 1 }; @@ -52,7 +52,7 @@ public static IEnumerable IsPrefix_TestData() yield return new object[] { s_invariantCompare, "\u00C0nimal", "a\u0300", CompareOptions.Ordinal, false, 0 }; yield return new object[] { s_invariantCompare, "\u00C0nimal", "a\u0300", CompareOptions.OrdinalIgnoreCase, false, 0 }; yield return new object[] { s_invariantCompare, "FooBar", "Foo\u0400Bar", CompareOptions.Ordinal, false, 0 }; - yield return new object[] { s_invariantCompare, "FooBA\u0300R", "FooB\u00C0R", supportedIgnoreNonSpaceOption, true, 7 }; + yield return new object[] { s_invariantCompare, "FooBA\u0300R", "FooB\u00C0R", CompareOptions.IgnoreNonSpace, true, 7 }; yield return new object[] { s_invariantCompare, "o\u0308", "o", CompareOptions.None, false, 0 }; yield return new object[] { s_invariantCompare, "o\u0308", "o", CompareOptions.Ordinal, true, 1 }; yield return new object[] { s_invariantCompare, "o\u0000\u0308", "o", CompareOptions.None, true, 1 }; @@ -72,19 +72,16 @@ public static IEnumerable IsPrefix_TestData() yield return new object[] { s_invariantCompare, "\uD800\uD800", "\uD800\uD800", CompareOptions.None, true, 2 }; // Ignore symbols - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { yield return new object[] { s_invariantCompare, "Test's can be interesting", "Tests", CompareOptions.IgnoreSymbols, true, 6 }; yield return new object[] { s_invariantCompare, "Test's can be interesting", "Tests", CompareOptions.None, false, 0 }; } // Platform differences - // in HybridGlobalization on Browser we use TextEncoder that is not supported for v8 and the manual decoding works like NLS - bool behavesLikeNls = PlatformDetection.IsNlsGlobalization || - (PlatformDetection.IsHybridGlobalizationOnBrowser && !PlatformDetection.IsBrowserDomSupportedOrNodeJS); - if (behavesLikeNls) + if (PlatformDetection.IsNlsGlobalization) { - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { yield return new object[] { s_hungarianCompare, "dzsdzsfoobar", "ddzsf", CompareOptions.None, true, 7 }; yield return new object[] { s_invariantCompare, "''Tests", "Tests", CompareOptions.IgnoreSymbols, true, 7 }; @@ -96,7 +93,7 @@ public static IEnumerable IsPrefix_TestData() else { yield return new object[] { s_hungarianCompare, "dzsdzsfoobar", "ddzsf", CompareOptions.None, false, 0 }; - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) yield return new object[] { s_invariantCompare, "''Tests", "Tests", CompareOptions.IgnoreSymbols, false, 0 }; yield return new object[] { s_frenchCompare, "\u0153", "oe", CompareOptions.None, false, 0 }; if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) @@ -114,15 +111,15 @@ public static IEnumerable IsPrefix_TestData() } // Prefixes where matched length does not equal value string length - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { - yield return new object[] { s_invariantCompare, "dzxyz", "\u01F3", supportedIgnoreNonSpaceOption, true, 2 }; - yield return new object[] { s_invariantCompare, "\u01F3xyz", "dz", supportedIgnoreNonSpaceOption, true, 1 }; - yield return new object[] { s_germanCompare, "Strasse xyz", "stra\u00DFe", supportedIgnoreCaseIgnoreNonSpaceOptions, true, 7 }; - yield return new object[] { s_germanCompare, "stra\u00DFe xyz", "Strasse", supportedIgnoreCaseIgnoreNonSpaceOptions, true, 6 }; + yield return new object[] { s_invariantCompare, "dzxyz", "\u01F3", CompareOptions.IgnoreNonSpace, true, 2 }; + yield return new object[] { s_invariantCompare, "\u01F3xyz", "dz", CompareOptions.IgnoreNonSpace, true, 1 }; + yield return new object[] { s_germanCompare, "Strasse xyz", "stra\u00DFe", CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, true, 7 }; + yield return new object[] { s_germanCompare, "stra\u00DFe xyz", "Strasse", CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, true, 6 }; } - yield return new object[] { s_germanCompare, "Strasse xyz", "xtra\u00DFe", supportedIgnoreCaseIgnoreNonSpaceOptions, false, 0 }; - yield return new object[] { s_germanCompare, "stra\u00DFe xyz", "Xtrasse", supportedIgnoreCaseIgnoreNonSpaceOptions, false, 0 }; + yield return new object[] { s_germanCompare, "Strasse xyz", "xtra\u00DFe", CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, false, 0 }; + yield return new object[] { s_germanCompare, "stra\u00DFe xyz", "Xtrasse", CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, false, 0 }; } [Theory] @@ -151,7 +148,7 @@ public void IsPrefix(CompareInfo compareInfo, string source, string value, Compa valueBoundedMemory.MakeReadonly(); Assert.Equal(expected, compareInfo.IsPrefix(sourceBoundedMemory.Span, valueBoundedMemory.Span, options)); - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { Assert.Equal(expected, compareInfo.IsPrefix(sourceBoundedMemory.Span, valueBoundedMemory.Span, options, out int actualMatchLength)); Assert.Equal(expectedMatchLength, actualMatchLength); @@ -164,7 +161,7 @@ public void IsPrefix_UnassignedUnicode() bool result = PlatformDetection.IsNlsGlobalization ? true : false; int expectedMatchLength = (result) ? 6 : 0; IsPrefix(s_invariantCompare, "FooBar", "Foo\uFFFFBar", CompareOptions.None, result, expectedMatchLength); - IsPrefix(s_invariantCompare, "FooBar", "Foo\uFFFFBar", supportedIgnoreNonSpaceOption, result, expectedMatchLength); + IsPrefix(s_invariantCompare, "FooBar", "Foo\uFFFFBar", CompareOptions.IgnoreNonSpace, result, expectedMatchLength); } [Fact] diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsSuffix.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsSuffix.cs index e2506d7fdd84b7..d545be88e67476 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsSuffix.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.IsSuffix.cs @@ -25,12 +25,12 @@ public static IEnumerable IsSuffix_TestData() yield return new object[] { s_invariantCompare, "foobardzsdzs", "rddzs", CompareOptions.None, false, 0 }; yield return new object[] { s_invariantCompare, "foobardzsdzs", "rddzs", CompareOptions.Ordinal, false, 0 }; yield return new object[] { s_invariantCompare, "dz", "z", CompareOptions.None, true, 1 }; - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) yield return new object[] { s_hungarianCompare, "dz", "z", CompareOptions.None, false, 0 }; yield return new object[] { s_hungarianCompare, "dz", "z", CompareOptions.Ordinal, true, 1 }; // Slovak - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { yield return new object[] { s_slovakCompare, "ch", "h", CompareOptions.None, false, 0 }; yield return new object[] { s_slovakCompare, "velmi chora", "hora", CompareOptions.None, false, 0 }; @@ -59,7 +59,7 @@ public static IEnumerable IsSuffix_TestData() yield return new object[] { s_invariantCompare, "Exhibit \u00C0", "a\u0300", CompareOptions.Ordinal, false, 0 }; yield return new object[] { s_invariantCompare, "Exhibit \u00C0", "a\u0300", CompareOptions.OrdinalIgnoreCase, false, 0 }; yield return new object[] { s_invariantCompare, "FooBar", "Foo\u0400Bar", CompareOptions.Ordinal, false, 0 }; - yield return new object[] { s_invariantCompare, "FooBA\u0300R", "FooB\u00C0R", supportedIgnoreNonSpaceOption, true, 7 }; + yield return new object[] { s_invariantCompare, "FooBA\u0300R", "FooB\u00C0R", CompareOptions.IgnoreNonSpace, true, 7 }; yield return new object[] { s_invariantCompare, "o\u0308", "o", CompareOptions.None, false, 0 }; yield return new object[] { s_invariantCompare, "o\u0308", "o", CompareOptions.Ordinal, false, 0 }; yield return new object[] { s_invariantCompare, "o\u0308o", "o", CompareOptions.None, true, 1 }; @@ -80,7 +80,7 @@ public static IEnumerable IsSuffix_TestData() yield return new object[] { s_invariantCompare, "\uD800\uD800", "\uD800\uD800", CompareOptions.None, true, 2 }; // Ignore symbols - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { yield return new object[] { s_invariantCompare, "More Test's", "Tests", CompareOptions.IgnoreSymbols, true, 6 }; yield return new object[] { s_invariantCompare, "More Test's", "Tests", CompareOptions.None, false, 0 }; @@ -91,16 +91,10 @@ public static IEnumerable IsSuffix_TestData() yield return new object[] { s_invariantCompare, "a\u0000b", "b\u0000b", CompareOptions.None, false, 0 }; // Platform differences - // in HybridGlobalization on Browser we use TextEncoder that is not supported for v8 and the manual decoding works like NLS - bool behavesLikeNls = PlatformDetection.IsNlsGlobalization || - (PlatformDetection.IsHybridGlobalizationOnBrowser && !PlatformDetection.IsBrowserDomSupportedOrNodeJS); - if (behavesLikeNls) + if (PlatformDetection.IsNlsGlobalization) { - if (!PlatformDetection.IsHybridGlobalizationOnBrowser) - { - yield return new object[] { s_hungarianCompare, "foobardzsdzs", "rddzs", CompareOptions.None, true, 7 }; - yield return new object[] { s_frenchCompare, "\u0153", "oe", CompareOptions.None, true, 1 }; - } + yield return new object[] { s_hungarianCompare, "foobardzsdzs", "rddzs", CompareOptions.None, true, 7 }; + yield return new object[] { s_frenchCompare, "\u0153", "oe", CompareOptions.None, true, 1 }; yield return new object[] { s_invariantCompare, "\uD800\uDC00", "\uDC00", CompareOptions.None, true, 1 }; yield return new object[] { s_invariantCompare, "\uD800\uDC00", "\uDC00", CompareOptions.IgnoreCase, true, 1 }; } else @@ -115,15 +109,15 @@ public static IEnumerable IsSuffix_TestData() } // Suffixes where matched length does not equal value string length - yield return new object[] { s_germanCompare, "xyz Strasse", "xtra\u00DFe", supportedIgnoreCaseIgnoreNonSpaceOptions, false, 0 }; - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + yield return new object[] { s_germanCompare, "xyz Strasse", "xtra\u00DFe", CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, false, 0 }; + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { - yield return new object[] { s_invariantCompare, "xyzdz", "\u01F3", supportedIgnoreNonSpaceOption, true, 2 }; - yield return new object[] { s_invariantCompare, "xyz\u01F3", "dz", supportedIgnoreNonSpaceOption, true, 1 }; - yield return new object[] { s_germanCompare, "xyz stra\u00DFe", "Strasse", supportedIgnoreCaseIgnoreNonSpaceOptions, true, 6 }; - yield return new object[] { s_germanCompare, "xyz Strasse", "stra\u00DFe", supportedIgnoreCaseIgnoreNonSpaceOptions, true, 7 }; + yield return new object[] { s_invariantCompare, "xyzdz", "\u01F3", CompareOptions.IgnoreNonSpace, true, 2 }; + yield return new object[] { s_invariantCompare, "xyz\u01F3", "dz", CompareOptions.IgnoreNonSpace, true, 1 }; + yield return new object[] { s_germanCompare, "xyz stra\u00DFe", "Strasse", CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, true, 6 }; + yield return new object[] { s_germanCompare, "xyz Strasse", "stra\u00DFe", CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, true, 7 }; } - yield return new object[] { s_germanCompare, "xyz stra\u00DFe", "Xtrasse", supportedIgnoreCaseIgnoreNonSpaceOptions, false, 0 }; + yield return new object[] { s_germanCompare, "xyz stra\u00DFe", "Xtrasse", CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, false, 0 }; } [Theory] @@ -152,7 +146,7 @@ public void IsSuffix(CompareInfo compareInfo, string source, string value, Compa valueBoundedMemory.MakeReadonly(); Assert.Equal(expected, compareInfo.IsSuffix(sourceBoundedMemory.Span, valueBoundedMemory.Span, options)); - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { Assert.Equal(expected, compareInfo.IsSuffix(sourceBoundedMemory.Span, valueBoundedMemory.Span, options, out int actualMatchLength)); Assert.Equal(expectedMatchLength, actualMatchLength); @@ -166,7 +160,7 @@ public void IsSuffix_UnassignedUnicode() int expectedMatchLength = (result) ? 6 : 0; IsSuffix(s_invariantCompare, "FooBar", "Foo\uFFFFBar", CompareOptions.None, result, expectedMatchLength); - IsSuffix(s_invariantCompare, "FooBar", "Foo\uFFFFBar", supportedIgnoreNonSpaceOption, result, expectedMatchLength); + IsSuffix(s_invariantCompare, "FooBar", "Foo\uFFFFBar", CompareOptions.IgnoreNonSpace, result, expectedMatchLength); } [Fact] diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.LastIndexOf.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.LastIndexOf.cs index 380610e4412b27..da76d1c08810ee 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.LastIndexOf.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.LastIndexOf.cs @@ -49,12 +49,10 @@ public static IEnumerable LastIndexOf_TestData() // Slovak yield return new object[] { s_slovakCompare, "ch", "h", 0, 1, CompareOptions.None, -1, 0 }; // Android has its own ICU, which doesn't work well with slovak - if (!PlatformDetection.IsAndroid && !PlatformDetection.IsLinuxBionic && !PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (!PlatformDetection.IsAndroid && !PlatformDetection.IsLinuxBionic && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { yield return new object[] { s_slovakCompare, "hore chodit", "HO", 11, 12, CompareOptions.IgnoreCase, 0, 2 }; - } - if (!PlatformDetection.IsHybridGlobalizationOnBrowser) - yield return new object[] { s_slovakCompare, "chh", "h", 2, 2, CompareOptions.None, 2, 1 }; + }yield return new object[] { s_slovakCompare, "chh", "h", 2, 2, CompareOptions.None, 2, 1 }; // Turkish // Android has its own ICU, which doesn't work well with tr @@ -79,16 +77,9 @@ public static IEnumerable LastIndexOf_TestData() yield return new object[] { s_invariantCompare, "Exhibit \u00C0", "a\u0300", 8, 9, CompareOptions.OrdinalIgnoreCase, -1, 0 }; yield return new object[] { s_invariantCompare, "Exhibit \u00C0", "a\u0300", 8, 9, CompareOptions.Ordinal, -1, 0 }; yield return new object[] { s_invariantCompare, "FooBar", "Foo\u0400Bar", 5, 6, CompareOptions.Ordinal, -1, 0 }; - yield return new object[] { s_invariantCompare, "TestFooBA\u0300R", "FooB\u00C0R", 10, 11, supportedIgnoreNonSpaceOption, 4, 7 }; + yield return new object[] { s_invariantCompare, "TestFooBA\u0300R", "FooB\u00C0R", 10, 11, CompareOptions.IgnoreNonSpace, 4, 7 }; yield return new object[] { s_invariantCompare, "o\u0308", "o", 1, 2, CompareOptions.None, -1, 0 }; - if (PlatformDetection.IsHybridGlobalizationOnBrowser) - { - yield return new object[] { s_invariantCompare, "\r\n", "\n", 1, 2, CompareOptions.None, -1, 0 }; - } - else - { - yield return new object[] { s_invariantCompare, "\r\n", "\n", 1, 1, CompareOptions.None, 1, 1 }; - } + yield return new object[] { s_invariantCompare, "\r\n", "\n", 1, 1, CompareOptions.None, 1, 1 }; // Weightless characters // NLS matches weightless characters at the end of the string @@ -104,7 +95,7 @@ public static IEnumerable LastIndexOf_TestData() yield return new object[] { s_invariantCompare, "AA\u200DA", "\u200d", 3, 4, CompareOptions.None, 4, 0}; // Ignore symbols - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) yield return new object[] { s_invariantCompare, "More Test's", "Tests", 10, 11, CompareOptions.IgnoreSymbols, 5, 6 }; yield return new object[] { s_invariantCompare, "More Test's", "Tests", 10, 11, CompareOptions.None, -1, 0 }; yield return new object[] { s_invariantCompare, "cbabababdbaba", "ab", 12, 13, CompareOptions.None, 10, 2 }; @@ -120,15 +111,15 @@ public static IEnumerable LastIndexOf_TestData() } // Inputs where matched length does not equal value string length - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { - yield return new object[] { s_germanCompare, "abc Strasse Strasse xyz", "stra\u00DFe", 22, 23, supportedIgnoreCaseIgnoreNonSpaceOptions, 12, 7 }; - yield return new object[] { s_germanCompare, "abc stra\u00DFe stra\u00DFe xyz", "Strasse", 20, 21, supportedIgnoreCaseIgnoreNonSpaceOptions, 11, 6 }; - yield return new object[] { s_invariantCompare, "abcdzxyz", "\u01F3", 7, 8, supportedIgnoreNonSpaceOption, 3, 2 }; - yield return new object[] { s_invariantCompare, "abc\u01F3xyz", "dz", 6, 7, supportedIgnoreNonSpaceOption, 3, 1 }; + yield return new object[] { s_germanCompare, "abc Strasse Strasse xyz", "stra\u00DFe", 22, 23, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, 12, 7 }; + yield return new object[] { s_germanCompare, "abc stra\u00DFe stra\u00DFe xyz", "Strasse", 20, 21, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, 11, 6 }; + yield return new object[] { s_invariantCompare, "abcdzxyz", "\u01F3", 7, 8, CompareOptions.IgnoreNonSpace, 3, 2 }; + yield return new object[] { s_invariantCompare, "abc\u01F3xyz", "dz", 6, 7, CompareOptions.IgnoreNonSpace, 3, 1 }; } - yield return new object[] { s_germanCompare, "abc Strasse Strasse xyz", "xtra\u00DFe", 22, 23, supportedIgnoreCaseIgnoreNonSpaceOptions, -1, 0 }; - yield return new object[] { s_germanCompare, "abc stra\u00DFe stra\u00DFe xyz", "Xtrasse", 20, 21, supportedIgnoreCaseIgnoreNonSpaceOptions, -1, 0 }; + yield return new object[] { s_germanCompare, "abc Strasse Strasse xyz", "xtra\u00DFe", 22, 23, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, -1, 0 }; + yield return new object[] { s_germanCompare, "abc stra\u00DFe stra\u00DFe xyz", "Xtrasse", 20, 21, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, -1, 0 }; } public static IEnumerable LastIndexOf_Aesc_Ligature_TestData() @@ -253,7 +244,7 @@ static void RunSpanLastIndexOfTest(CompareInfo compareInfo, ReadOnlySpan s valueBoundedMemory.MakeReadonly(); Assert.Equal(expected, compareInfo.LastIndexOf(sourceBoundedMemory.Span, valueBoundedMemory.Span, options)); - if (!PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { Assert.Equal(expected, compareInfo.LastIndexOf(sourceBoundedMemory.Span, valueBoundedMemory.Span, options, out int actualMatchLength)); Assert.Equal(expectedMatchLength, actualMatchLength); @@ -307,7 +298,7 @@ public void LastIndexOf_UnassignedUnicode() bool useNls = PlatformDetection.IsNlsGlobalization; int expectedMatchLength = (useNls) ? 6 : 0; LastIndexOf_String(s_invariantCompare, "FooBar", "Foo\uFFFFBar", 5, 6, CompareOptions.None, useNls ? 0 : -1, expectedMatchLength); - LastIndexOf_String(s_invariantCompare, "~FooBar", "Foo\uFFFFBar", 6, 7, supportedIgnoreNonSpaceOption, useNls ? 1 : -1, expectedMatchLength); + LastIndexOf_String(s_invariantCompare, "~FooBar", "Foo\uFFFFBar", 6, 7, CompareOptions.IgnoreNonSpace, useNls ? 1 : -1, expectedMatchLength); } [Fact] @@ -357,7 +348,7 @@ public void LastIndexOf_Invalid() AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", 'a', 0, 1, CompareOptions.NumericOrdering)); AssertExtensions.Throws("options", () => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.NumericOrdering)); - if (PlatformDetection.IsHybridGlobalizationOnBrowser || PlatformDetection.IsHybridGlobalizationOnApplePlatform) + if (PlatformDetection.IsHybridGlobalizationOnApplePlatform) { Assert.Throws(() => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.StringSort, out int matchLength)); Assert.Throws(() => s_invariantCompare.LastIndexOf("Test's", "a".AsSpan(), CompareOptions.NumericOrdering, out int matchLength)); diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.SortKey.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.SortKey.cs index 193b55baf47b5b..51533fa4552542 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.SortKey.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.SortKey.cs @@ -272,14 +272,14 @@ public static IEnumerable SortKey_Kana_TestData() yield return new object[] { s_invariantCompare, "\u3060", "\uFF80\uFF9E", CompareOptions.None, s_expectedHiraganaToKatakanaCompare }; } - [ConditionalTheory(typeof(CompareInfoSortKeyTests), nameof(IsNotWindowsKanaRegressedVersionAndNotHybridGlobalizationOnWasm))] + [ConditionalTheory(typeof(CompareInfoSortKeyTests), nameof(IsNotWindowsKanaRegressedVersion))] [MemberData(nameof(SortKey_Kana_TestData))] public void SortKeyKanaTest(CompareInfo compareInfo, string string1, string string2, CompareOptions options, int expected) { SortKeyTest(compareInfo, string1, string2, options, expected); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Theory] [MemberData(nameof(SortKey_TestData))] public void SortKeyTest(CompareInfo compareInfo, string string1, string string2, CompareOptions options, int expectedSign) { @@ -328,7 +328,7 @@ unsafe static void RunSpanSortKeyTest(CompareInfo compareInfo, ReadOnlySpan !PlatformDetection.IsWindows10Version1903OrGreater || PlatformDetection.IsIcuGlobalization || s_invariantCompare.Compare("\u3060", "\uFF80\uFF9E", CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase) == 0; - protected static bool IsNotWindowsKanaRegressedVersionAndNotHybridGlobalizationOnWasm() => !PlatformDetection.IsHybridGlobalizationOnBrowser && IsNotWindowsKanaRegressedVersion(); - public class CustomComparer : StringComparer { private readonly CompareInfo _compareInfo; diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoCtor.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoCtor.cs index 9a9523784716ce..e5fd8c7fd0a123 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoCtor.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoCtor.cs @@ -446,7 +446,7 @@ public void TestCreationWithTemporaryLCID(int lcid) [InlineData("de-DE-u-co-phonebk-t-xx", "de-DE-t-xx", "de-DE-t-xx_phoneboo")] [InlineData("de-DE-u-co-phonebk-t-xx-u-yy", "de-DE-t-xx-u-yy", "de-DE-t-xx-u-yy_phoneboo")] [InlineData("de-DE", "de-DE", "de-DE")] - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform))] public void TestCreationWithMangledSortName(string cultureName, string expectedCultureName, string expectedSortName) { CultureInfo ci = CultureInfo.GetCultureInfo(cultureName); @@ -461,7 +461,7 @@ public void TestCreationWithMangledSortName(string cultureName, string expectedC [InlineData("qps-plocm", "qps-PLOCM")] // ICU normalize this name to "qps--plocm" which we normalize it back to "qps-plocm" [InlineData("zh_CN", "zh_cn")] [InlineData("km_KH", "km_kh")] - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser), nameof(PlatformDetection.IsNotWindowsServerCore))] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform), nameof(PlatformDetection.IsNotWindowsServerCore))] public void TestCreationWithICUNormalizedNames(string cultureName, string expectedCultureName) { CultureInfo ci = CultureInfo.GetCultureInfo(cultureName); diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoAMDesignator.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoAMDesignator.cs index 927c88c5bcb4db..1e8a615d3c2663 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoAMDesignator.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoAMDesignator.cs @@ -14,207 +14,6 @@ public void AMDesignator_GetInvariantInfo_ReturnsExpected() Assert.Equal("AM", DateTimeFormatInfo.InvariantInfo.AMDesignator); } - public static IEnumerable AMDesignator_Get_TestData_HybridGlobalization() - { - // see the comments on the right to check the non-Hybrid result, if it differs - yield return new object[] { "ar-SA", "ص" }; - yield return new object[] { "am-ET", "ጥዋት" }; - yield return new object[] { "bg-BG", "пр.об." }; - yield return new object[] { "bn-BD", "AM" }; - yield return new object[] { "bn-IN", "AM" }; - yield return new object[] { "ca-AD", "a.\u00A0m." }; - yield return new object[] { "ca-ES", "a.\u00A0m." }; - yield return new object[] { "cs-CZ", "dop." }; - yield return new object[] { "da-DK", "AM" }; - yield return new object[] { "de-AT", "AM" }; - yield return new object[] { "de-BE", "AM" }; - yield return new object[] { "de-CH", "AM" }; - yield return new object[] { "de-DE", "AM" }; - yield return new object[] { "de-IT", "AM" }; - yield return new object[] { "de-LI", "AM" }; - yield return new object[] { "de-LU", "AM" }; - yield return new object[] { "el-CY", "π.μ." }; - yield return new object[] { "el-GR", "π.μ." }; - yield return new object[] { "en-AE", "AM" }; - yield return new object[] { "en-AG", "am" }; - yield return new object[] { "en-AI", "am" }; - yield return new object[] { "en-AS", "AM" }; - yield return new object[] { "en-AT", "am" }; - yield return new object[] { "en-AU", "am" }; - yield return new object[] { "en-BB", "am" }; - yield return new object[] { "en-BE", "am" }; - yield return new object[] { "en-BI", "AM" }; - yield return new object[] { "en-BM", "am" }; - yield return new object[] { "en-BS", "am" }; - yield return new object[] { "en-BW", "am" }; - yield return new object[] { "en-BZ", "am" }; - yield return new object[] { "en-CA", "a.m." }; - yield return new object[] { "en-CC", "am" }; - yield return new object[] { "en-CH", "am" }; - yield return new object[] { "en-CK", "am" }; - yield return new object[] { "en-CM", "am" }; - yield return new object[] { "en-CX", "am" }; - yield return new object[] { "en-CY", "am" }; - yield return new object[] { "en-DE", "am" }; - yield return new object[] { "en-DK", "am" }; - yield return new object[] { "en-DM", "am" }; - yield return new object[] { "en-ER", "am" }; - yield return new object[] { "en-FI", "am" }; - yield return new object[] { "en-FJ", "am" }; - yield return new object[] { "en-FK", "am" }; - yield return new object[] { "en-FM", "am" }; - yield return new object[] { "en-GB", "am" }; - yield return new object[] { "en-GD", "am" }; - yield return new object[] { "en-GG", "am" }; - yield return new object[] { "en-GH", "am" }; - yield return new object[] { "en-GI", "am" }; - yield return new object[] { "en-GM", "am" }; - yield return new object[] { "en-GU", "AM" }; - yield return new object[] { "en-GY", "am" }; - yield return new object[] { "en-HK", "am" }; - yield return new object[] { "en-IE", "a.m." }; - yield return new object[] { "en-IL", "am" }; - yield return new object[] { "en-IM", "am" }; - yield return new object[] { "en-IN", "am" }; - yield return new object[] { "en-IO", "am" }; - yield return new object[] { "en-JE", "am" }; - yield return new object[] { "en-JM", "am" }; - yield return new object[] { "en-KE", "am" }; - yield return new object[] { "en-KI", "am" }; - yield return new object[] { "en-KN", "am" }; - yield return new object[] { "en-KY", "am" }; - yield return new object[] { "en-LC", "am" }; - yield return new object[] { "en-LR", "am" }; - yield return new object[] { "en-LS", "am" }; - yield return new object[] { "en-MG", "am" }; - yield return new object[] { "en-MH", "AM" }; - yield return new object[] { "en-MO", "am" }; - yield return new object[] { "en-MP", "AM" }; - yield return new object[] { "en-MS", "am" }; - yield return new object[] { "en-MT", "am" }; - yield return new object[] { "en-MU", "am" }; - yield return new object[] { "en-MW", "am" }; - yield return new object[] { "en-MY", "am" }; - yield return new object[] { "en-NA", "am" }; - yield return new object[] { "en-NF", "am" }; - yield return new object[] { "en-NG", "am" }; - yield return new object[] { "en-NL", "am" }; - yield return new object[] { "en-NR", "am" }; - yield return new object[] { "en-NU", "am" }; - yield return new object[] { "en-NZ", "am" }; - yield return new object[] { "en-PG", "am" }; - yield return new object[] { "en-PH", "AM" }; // am - yield return new object[] { "en-PK", "am" }; - yield return new object[] { "en-PN", "am" }; - yield return new object[] { "en-PR", "AM" }; - yield return new object[] { "en-PW", "am" }; - yield return new object[] { "en-RW", "am" }; - yield return new object[] { "en-SB", "am" }; - yield return new object[] { "en-SC", "am" }; - yield return new object[] { "en-SD", "am" }; - yield return new object[] { "en-SE", "am" }; - yield return new object[] { "en-SG", "am" }; - yield return new object[] { "en-SH", "am" }; - yield return new object[] { "en-SI", "am" }; - yield return new object[] { "en-SL", "am" }; - yield return new object[] { "en-SS", "am" }; - yield return new object[] { "en-SX", "am" }; - yield return new object[] { "en-SZ", "am" }; - yield return new object[] { "en-TC", "am" }; - yield return new object[] { "en-TK", "am" }; - yield return new object[] { "en-TO", "am" }; - yield return new object[] { "en-TT", "am" }; - yield return new object[] { "en-TV", "am" }; - yield return new object[] { "en-TZ", "am" }; - yield return new object[] { "en-UG", "am" }; - yield return new object[] { "en-UM", "AM" }; - yield return new object[] { "en-US", "AM" }; - yield return new object[] { "en-VC", "am" }; - yield return new object[] { "en-VG", "am" }; - yield return new object[] { "en-VI", "AM" }; - yield return new object[] { "en-VU", "am" }; - yield return new object[] { "en-WS", "am" }; - yield return new object[] { "en-ZA", "am" }; - yield return new object[] { "en-ZM", "am" }; - yield return new object[] { "en-ZW", "am" }; - string latinAmericaSpanishAMDesignator = PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "a.\u00A0m." : "a.m."; - yield return new object[] { "es-419", latinAmericaSpanishAMDesignator }; - yield return new object[] { "es-ES", "a.\u00A0m." }; - yield return new object[] { "es-MX", latinAmericaSpanishAMDesignator }; - yield return new object[] { "et-EE", "AM" }; - yield return new object[] { "fa-IR", "قبل‌ازظهر" }; - yield return new object[] { "fi-FI", "ap." }; - yield return new object[] { "fil-PH", "AM" }; - yield return new object[] { "fr-BE", "AM" }; - yield return new object[] { "fr-CA", "a.m." }; - yield return new object[] { "fr-CH", "AM" }; - yield return new object[] { "fr-FR", "AM" }; - yield return new object[] { "gu-IN", "AM" }; - yield return new object[] { "he-IL", "לפנה״צ" }; - yield return new object[] { "hi-IN", "am" }; - yield return new object[] { "hr-BA", "AM" }; - yield return new object[] { "hr-HR", "AM" }; - yield return new object[] { "hu-HU", "de." }; - yield return new object[] { "id-ID", "AM" }; - yield return new object[] { "it-CH", "AM" }; - yield return new object[] { "it-IT", "AM" }; - yield return new object[] { "ja-JP", "午前" }; - yield return new object[] { "kn-IN", "ಪೂರ್ವಾಹ್ನ" }; - yield return new object[] { "ko-KR", "오전" }; - yield return new object[] { "lt-LT", "priešpiet" }; - yield return new object[] { "lv-LV", "priekšpusdienā" }; - yield return new object[] { "ml-IN", "AM" }; - yield return new object[] { "mr-IN", "AM" }; // म.पू. - yield return new object[] { "ms-BN", "PG" }; - yield return new object[] { "ms-MY", "PG" }; - yield return new object[] { "ms-SG", "PG" }; - yield return new object[] { "nb-NO", "a.m." }; - yield return new object[] { "no", "a.m." }; - yield return new object[] { "no-NO", "a.m." }; - yield return new object[] { "nl-AW", "a.m." }; - yield return new object[] { "nl-BE", "a.m." }; - yield return new object[] { "nl-NL", "a.m." }; - yield return new object[] { "pl-PL", "AM" }; - yield return new object[] { "pt-BR", "AM" }; - yield return new object[] { "pt-PT", "da manhã" }; - yield return new object[] { "ro-RO", "a.m." }; - yield return new object[] { "ru-RU", "AM" }; - yield return new object[] { "sk-SK", "AM" }; - yield return new object[] { "sl-SI", "dop." }; - yield return new object[] { "sr-Cyrl-RS", "AM" }; // пре подне - yield return new object[] { "sr-Latn-RS", "AM" }; // pre podne - yield return new object[] { "sv-AX", "fm" }; - yield return new object[] { "sv-SE", "fm" }; - yield return new object[] { "sw-CD", "AM" }; - yield return new object[] { "sw-KE", "AM" }; - yield return new object[] { "sw-TZ", "AM" }; - yield return new object[] { "sw-UG", "AM" }; - string tamilAMDesignator = PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "முற்பகல்" : "AM"; // முற்பகல் - yield return new object[] { "ta-IN", tamilAMDesignator }; - yield return new object[] { "ta-LK", tamilAMDesignator }; - yield return new object[] { "ta-MY", tamilAMDesignator }; - yield return new object[] { "ta-SG", tamilAMDesignator }; - yield return new object[] { "te-IN", "AM" }; - yield return new object[] { "th-TH", "ก่อนเที่ยง" }; - yield return new object[] { "tr-CY", "ÖÖ" }; - yield return new object[] { "tr-TR", "ÖÖ" }; - yield return new object[] { "uk-UA", "дп" }; - yield return new object[] { "vi-VN", "SA" }; - yield return new object[] { "zh-CN", "上午" }; - yield return new object[] { "zh-Hans-HK", "上午" }; - yield return new object[] { "zh-SG", "上午" }; - yield return new object[] { "zh-HK", "上午" }; - yield return new object[] { "zh-TW", "上午" }; - } - - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] - [MemberData(nameof(AMDesignator_Get_TestData_HybridGlobalization))] - public void AMDesignator_Get_ReturnsExpected_HybridGlobalization(string cultureName, string expected) - { - var format = new CultureInfo(cultureName).DateTimeFormat; - Assert.True(expected == format.AMDesignator, $"Failed for culture: {cultureName}. Expected: {expected}, Actual: {format.AMDesignator}"); - } - [Theory] [InlineData("")] [InlineData("AA")] diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoAbbreviatedDayNames.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoAbbreviatedDayNames.cs index da84d4bb51984f..1d94d9b32631a6 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoAbbreviatedDayNames.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoAbbreviatedDayNames.cs @@ -34,63 +34,6 @@ public static IEnumerable AbbreviatedDayNames_Get_TestData_ICU() yield return new object[] { CultureInfo.GetCultureInfo("fr-FR").DateTimeFormat, new string[] { "dim.", "lun.", "mar.", "mer.", "jeu.", "ven.", "sam." } }; } - public static IEnumerable AbbreviatedDayNames_Get_TestData_HybridGlobalization() - { - // see the comments on the right to check the non-Hybrid result, if it differs - yield return new object[] { "ar-SA", new string[] { "الأحد", "الاثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعة", "السبت" } }; - yield return new object[] { "am-ET", new string[] { "እሑድ", "ሰኞ", "ማክሰ", "ረቡዕ", "ሐሙስ", "ዓርብ", "ቅዳሜ" } }; - yield return new object[] { "bg-BG", new string[] { "нд", "пн", "вт", "ср", "чт", "пт", "сб" } }; - yield return new object[] { "bn-BD", new string[] { "রবি", "সোম", "মঙ্গল", "বুধ", "বৃহস্পতি", "শুক্র", "শনি" } }; - yield return new object[] { "ca-AD", new string[] { "dg.", "dl.", "dt.", "dc.", "dj.", "dv.", "ds." } }; - yield return new object[] { "cs-CZ", new string[] { "ne", "po", "út", "st", "čt", "pá", "so" } }; - yield return new object[] { "da-DK", new string[] { "søn.", "man.", "tirs.", "ons.", "tors.", "fre.", "lør." } }; - yield return new object[] { "de-DE", new string[] { "So", "Mo", "Di", "Mi", "Do", "Fr", "Sa" } }; - yield return new object[] { "el-GR", new string[] { "Κυρ", "Δευ", "Τρί", "Τετ", "Πέμ", "Παρ", "Σάβ" } }; - yield return new object[] { "en-CA", new string[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" } }; // should be with dots - yield return new object[] { "en-US", new string[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" } }; - yield return new object[] { "es-419", new string[] { "dom", "lun", "mar", "mié", "jue", "vie", "sáb" } }; // should be with dots like all "es-*" - yield return new object[] { "et-EE", new string[] { "P", "E", "T", "K", "N", "R", "L" } }; - yield return new object[] { "fa-IR", new string[] { "یکشنبه", "دوشنبه", "سه‌شنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه" } }; - yield return new object[] { "fi-FI", new string[] { "su", "ma", "ti", "ke", "to", "pe", "la" } }; - yield return new object[] { "fil-PH", new string[] { "Lin", "Lun", "Mar", "Miy", "Huw", "Biy", "Sab" } }; - yield return new object[] { "fr-BE", new string[] { "dim.", "lun.", "mar.", "mer.", "jeu.", "ven.", "sam." } }; - yield return new object[] { "gu-IN", new string[] { "રવિ", "સોમ", "મંગળ", "બુધ", "ગુરુ", "શુક્ર", "શનિ" } }; - yield return new object[] { "he-IL", new string[] { "יום א׳", "יום ב׳", "יום ג׳", "יום ד׳", "יום ה׳", "יום ו׳", "שבת" } }; - yield return new object[] { "hr-BA", new string[] { "ned", "pon", "uto", "sri", "čet", "pet", "sub" } }; - yield return new object[] { "hu-HU", new string[] { "V", "H", "K", "Sze", "Cs", "P", "Szo" } }; - yield return new object[] { "id-ID", new string[] { "Min", "Sen", "Sel", "Rab", "Kam", "Jum", "Sab" } }; - yield return new object[] { "it-CH", new string[] { "dom", "lun", "mar", "mer", "gio", "ven", "sab" } }; - yield return new object[] { "it-IT", new string[] { "dom", "lun", "mar", "mer", "gio", "ven", "sab" } }; - yield return new object[] { "ja-JP", new string[] { "日", "月", "火", "水", "木", "金", "土" } }; - yield return new object[] { "kn-IN", new string[] { "ಭಾನು", "ಸೋಮ", "ಮಂಗಳ", "ಬುಧ", "ಗುರು", "ಶುಕ್ರ", "ಶನಿ" } }; - yield return new object[] { "ko-KR", new string[] { "일", "월", "화", "수", "목", "금", "토" } }; - yield return new object[] { "lt-LT", new string[] { "sk", "pr", "an", "tr", "kt", "pn", "št" } }; - yield return new object[] { "lv-LV", new string[] { "Svētd.", "Pirmd.", "Otrd.", "Trešd.", "Ceturtd.", "Piektd.", "Sestd." } }; - yield return new object[] { "ml-IN", new string[] { "ഞായർ", "തിങ്കൾ", "ചൊവ്വ", "ബുധൻ", "വ്യാഴം", "വെള്ളി", "ശനി" } }; - yield return new object[] { "mr-IN", new string[] { "रवि", "सोम", "मंगळ", "बुध", "गुरु", "शुक्र", "शनि" } }; - yield return new object[] { "ms-BN", new string[] { "Ahd", "Isn", "Sel", "Rab", "Kha", "Jum", "Sab" } }; - yield return new object[] { "nb-NO", new string[] { "søn.", "man.", "tir.", "ons.", "tor.", "fre.", "lør." } }; - yield return new object[] { "nl-AW", new string[] { "zo", "ma", "di", "wo", "do", "vr", "za" } }; - yield return new object[] { "pl-PL", new string[] { "niedz.", "pon.", "wt.", "śr.", "czw.", "pt.", "sob." } }; - yield return new object[] { "pt-BR", new string[] { "dom.", "seg.", "ter.", "qua.", "qui.", "sex.", "sáb." } }; - yield return new object[] { "pt-PT", new string[] { "domingo", "segunda", "terça", "quarta", "quinta", "sexta", "sábado" } }; - yield return new object[] { "ro-RO", new string[] { "dum.", "lun.", "mar.", "mie.", "joi", "vin.", "sâm." } }; - yield return new object[] { "ru-RU", new string[] { "вс", "пн", "вт", "ср", "чт", "пт", "сб" } }; - yield return new object[] { "sk-SK", new string[] { "ne", "po", "ut", "st", "št", "pi", "so" } }; - yield return new object[] { "sl-SI", new string[] { "ned.", "pon.", "tor.", "sre.", "čet.", "pet.", "sob." } }; - yield return new object[] { "sr-Cyrl-RS", new string[] { "нед", "пон", "уто", "сре", "чет", "пет", "суб" } }; - yield return new object[] { "sr-Latn-RS", new string[] { "ned", "pon", "uto", "sre", "čet", "pet", "sub" } }; - yield return new object[] { "sv-AX", new string[] { "sön", "mån", "tis", "ons", "tors", "fre", "lör" } }; - yield return new object[] { "sv-SE", new string[] { "sön", "mån", "tis", "ons", "tors", "fre", "lör" } }; - yield return new object[] { "sw-CD", new string[] { "Jumapili", "Jumatatu", "Jumanne", "Jumatano", "Alhamisi", "Ijumaa", "Jumamosi" } }; - yield return new object[] { "ta-IN", new string[] { "ஞாயி.", "திங்.", "செவ்.", "புத.", "வியா.", "வெள்.", "சனி" } }; - yield return new object[] { "te-IN", new string[] { "ఆది", "సోమ", "మంగళ", "బుధ", "గురు", "శుక్ర", "శని" } }; - yield return new object[] { "th-TH", new string[] { "อา.", "จ.", "อ.", "พ.", "พฤ.", "ศ.", "ส." } }; - yield return new object[] { "tr-CY", new string[] { "Paz", "Pzt", "Sal", "Çar", "Per", "Cum", "Cmt" } }; - yield return new object[] { "uk-UA", new string[] { "нд", "пн", "вт", "ср", "чт", "пт", "сб" } }; - yield return new object[] { "vi-VN", new string[] { "CN", "Th 2", "Th 3", "Th 4", "Th 5", "Th 6", "Th 7" } }; - yield return new object[] { "zh-CN", new string[] { "周日", "周一", "周二", "周三", "周四", "周五", "周六" } }; - } [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization))] [MemberData(nameof(AbbreviatedDayNames_Get_TestData_ICU))] @@ -99,16 +42,6 @@ public void AbbreviatedDayNames_Get_ReturnsExpected_ICU(DateTimeFormatInfo forma Assert.Equal(expected, format.AbbreviatedDayNames); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] - [MemberData(nameof(AbbreviatedDayNames_Get_TestData_HybridGlobalization))] - public void AbbreviatedDayNames_Get_ReturnsExpected_HybridGlobalization(string cultureName, string[] expected) - { - var format = new CultureInfo(cultureName).DateTimeFormat; - int length = format.AbbreviatedDayNames.Length; - Assert.True(length == expected.Length, $"Length comparison failed for culture: {cultureName}. Expected: {expected.Length}, Actual: {length}"); - for (int i = 0; i AbbreviatedMonthGenitiveNames_Get_TestData_I yield return new object[] { CultureInfo.GetCultureInfo("en-US").DateTimeFormat, new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; yield return new object[] { CultureInfo.GetCultureInfo("fr-FR").DateTimeFormat, new string[] { "janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc.", "" } }; } - public static IEnumerable AbbreviatedMonthGenitiveNames_Get_TestData_HybridGlobalization() - { - // see the comments on the right to check the non-Hybrid result, if it differs - yield return new object[] { "ar-SA", new string[] { "محرم", "صفر", "ربيع الأول", "ربيع الآخر", "جمادى الأولى", "جمادى الآخرة", "رجب", "شعبان", "رمضان", "شوال", "ذو القعدة", "ذو الحجة", "" } }; - if (PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS) - { - yield return new object[] { "am-ET", new string[] { "ጃንዩ", "ፌብሩ", "ማርች", "ኤፕሪ", "ሜይ", "ጁን", "ጁላይ", "ኦገስ", "ሴፕቴ", "ኦክቶ", "ኖቬም", "ዲሴም", "" } }; - yield return new object[] { "es-MX", new string[] { "ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sept", "oct", "nov", "dic", "" } }; // "ene.", "feb.", "mar.", "abr.", "may.", "jun.", "jul.", "ago.", "sep.", "oct.", "nov.", "dic.", "" - } - else - { - yield return new object[] { "am-ET", new string[] { "ጃን", "ፌብ", "ማርች", "ኤፕሪ", "ሜይ", "ጁን", "ጁላይ", "ኦገስ", "ሴፕቴ", "ኦክቶ", "ኖቬም", "ዲሴም", "" } }; // "ጃንዩ", "ፌብሩ", "ማርች", "ኤፕሪ", "ሜይ", "ጁን", "ጁላይ", "ኦገስ", "ሴፕቴ", "ኦክቶ", "ኖቬም", "ዲሴም", "" - yield return new object[] { "es-MX", new string[] { "ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sep", "oct", "nov", "dic", "" } }; // "ene.", "feb.", "mar.", "abr.", "may.", "jun.", "jul.", "ago.", "sep.", "oct.", "nov.", "dic.", "" - } - yield return new object[] { "bg-BG", new string[] { "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "" } }; //"яну", "фев", "март", "апр", "май", "юни", "юли", "авг", "сеп", "окт", "ное", "дек", "" - yield return new object[] { "bn-BD", new string[] { "জানু", "ফেব", "মার্চ", "এপ্রি", "মে", "জুন", "জুল", "আগ", "সেপ", "অক্টো", "নভে", "ডিসে", "" } }; // "জানু", "ফেব", "মার্চ", "এপ্রিল", "মে", "জুন", "জুলাই", "আগস্ট", "সেপ্টেম্বর", "অক্টোবর", "নভেম্বর", "ডিসেম্বর", "" - yield return new object[] { "bn-IN", new string[] { "জানু", "ফেব", "মার্চ", "এপ্রি", "মে", "জুন", "জুল", "আগ", "সেপ্টেঃ", "অক্টোঃ", "নভেঃ", "ডিসেঃ", "" } }; // "জানু", "ফেব", "মার্চ", "এপ্রিল", "মে", "জুন", "জুলাই", "আগস্ট", "সেপ্টেম্বর", "অক্টোবর", "নভেম্বর", "ডিসেম্বর", "" - yield return new object[] { "ca-AD", new string[] { "gen.", "febr.", "març", "abr.", "maig", "juny", "jul.", "ag.", "set.", "oct.", "nov.", "des.", "" } }; // "de gen.", "de febr.", "de març", "d’abr.", "de maig", "de juny", "de jul.", "d’ag.", "de set.", "d’oct.", "de nov.", "de des.", "" - yield return new object[] { "ca-ES", new string[] { "gen.", "febr.", "març", "abr.", "maig", "juny", "jul.", "ag.", "set.", "oct.", "nov.", "des.", "" } }; - yield return new object[] { "cs-CZ", new string[] { "led", "úno", "bře", "dub", "kvě", "čvn", "čvc", "srp", "zář", "říj", "lis", "pro", "" } }; - yield return new object[] { "da-DK", new string[] { "jan.", "feb.", "mar.", "apr.", "maj", "jun.", "jul.", "aug.", "sep.", "okt.", "nov.", "dec.", "" } }; - yield return new object[] { "de-AT", new string[] { "Jän.", "Feb.", "März", "Apr.", "Mai", "Juni", "Juli", "Aug.", "Sep.", "Okt.", "Nov.", "Dez.", "" } }; - yield return new object[] { "de-BE", new string[] { "Jan.", "Feb.", "März", "Apr.", "Mai", "Juni", "Juli", "Aug.", "Sept.", "Okt.", "Nov.", "Dez.", "" } }; - yield return new object[] { "de-CH", new string[] { "Jan.", "Feb.", "März", "Apr.", "Mai", "Juni", "Juli", "Aug.", "Sept.", "Okt.", "Nov.", "Dez.", "" } }; - yield return new object[] { "de-DE", new string[] { "Jan.", "Feb.", "März", "Apr.", "Mai", "Juni", "Juli", "Aug.", "Sept.", "Okt.", "Nov.", "Dez.", "" } }; - yield return new object[] { "de-IT", new string[] { "Jän.", "Feb.", "März", "Apr.", "Mai", "Juni", "Juli", "Aug.", "Sep.", "Okt.", "Nov.", "Dez.", "" } }; - yield return new object[] { "de-LI", new string[] { "Jan.", "Feb.", "März", "Apr.", "Mai", "Juni", "Juli", "Aug.", "Sept.", "Okt.", "Nov.", "Dez.", "" } }; - yield return new object[] { "de-LU", new string[] { "Jan.", "Feb.", "März", "Apr.", "Mai", "Juni", "Juli", "Aug.", "Sept.", "Okt.", "Nov.", "Dez.", "" } }; - yield return new object[] { "el-CY", new string[] { "Ιαν", "Φεβ", "Μαρ", "Απρ", "Μαΐ", "Ιουν", "Ιουλ", "Αυγ", "Σεπ", "Οκτ", "Νοε", "Δεκ", "" } }; - yield return new object[] { "el-GR", new string[] { "Ιαν", "Φεβ", "Μαρ", "Απρ", "Μαΐ", "Ιουν", "Ιουλ", "Αυγ", "Σεπ", "Οκτ", "Νοε", "Δεκ", "" } }; - yield return new object[] { "en-AE", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-AG", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-AI", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-AS", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-AT", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-AU", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Jun", "Jul", .., "Sep" - yield return new object[] { "en-BB", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-BE", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-BI", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-BM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-BS", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-BW", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-BZ", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-CA", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; // "Jan.", "Feb.", "Mar.", "Apr.", "May", "Jun.", "Jul.", "Aug.", "Sep.", "Oct.", "Nov.", "Dec.", "" - yield return new object[] { "en-CC", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-CH", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-CK", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-CM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-CX", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-CY", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-DE", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-DK", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-DM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-ER", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-FI", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-FJ", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-FK", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-FM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-GB", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-GD", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-GG", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-GH", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-GI", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-GM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-GU", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-GY", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-HK", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-IE", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-IL", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-IM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-IN", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-IO", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-JE", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-JM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-KE", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-KI", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-KN", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-KY", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-LC", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-LR", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-LS", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-MG", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-MH", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-MO", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-MP", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-MS", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-MT", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-MU", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-MW", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-MY", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-NA", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-NF", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-NG", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-NL", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-NR", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-NU", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-NZ", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-PG", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-PH", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-PK", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-PN", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-PR", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-PW", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-RW", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SB", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SC", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SD", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SE", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SG", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SH", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SI", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SL", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SS", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SX", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SZ", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-TC", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-TK", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-TO", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-TT", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-TV", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-TZ", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-UG", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-UM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-US", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-VC", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-VG", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-VI", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-VU", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-WS", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-ZA", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-ZM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-ZW", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "es-419", new string[] { "ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sept", "oct", "nov", "dic", "" } }; // "ene.", "feb.", "mar.", "abr.", "may.", "jun.", "jul.", "ago.", "sep.", "oct.", "nov.", "dic.", "" - yield return new object[] { "es-ES", new string[] { "ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sept", "oct", "nov", "dic", "" } }; // "ene.", "feb.", "mar.", "abr.", "may.", "jun.", "jul.", "ago.", "sep.", "oct.", "nov.", "dic.", "" - yield return new object[] { "et-EE", new string[] { "jaan", "veebr", "märts", "apr", "mai", "juuni", "juuli", "aug", "sept", "okt", "nov", "dets", "" } }; - yield return new object[] { "fa-IR", new string[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" } }; - yield return new object[] { "fi-FI", new string[] { "tammik.", "helmik.", "maalisk.", "huhtik.", "toukok.", "kesäk.", "heinäk.", "elok.", "syysk.", "lokak.", "marrask.", "jouluk.", "" } }; - yield return new object[] { "fil-PH", new string[] { "Ene", "Peb", "Mar", "Abr", "May", "Hun", "Hul", "Ago", "Set", "Okt", "Nob", "Dis", "" } }; - yield return new object[] { "fr-BE", new string[] { "janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc.", "" } }; - yield return new object[] { "fr-CA", new string[] { "janv.", "févr.", "mars", "avr.", "mai", "juin", "juill.", "août", "sept.", "oct.", "nov.", "déc.", "" } }; - yield return new object[] { "fr-CH", new string[] { "janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc.", "" } }; - yield return new object[] { "fr-FR", new string[] { "janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc.", "" } }; - yield return new object[] { "gu-IN", new string[] { "જાન્યુ", "ફેબ્રુ", "માર્ચ", "એપ્રિલ", "મે", "જૂન", "જુલાઈ", "ઑગસ્ટ", "સપ્ટે", "ઑક્ટો", "નવે", "ડિસે", "" } }; - yield return new object[] { "he-IL", new string[] { "ינו׳", "פבר׳", "מרץ", "אפר׳", "מאי", "יוני", "יולי", "אוג׳", "ספט׳", "אוק׳", "נוב׳", "דצמ׳", "" } }; - yield return new object[] { "hi-IN", new string[] { "जन॰", "फ़र॰", "मार्च", "अप्रैल", "मई", "जून", "जुल॰", "अग॰", "सित॰", "अक्तू॰", "नव॰", "दिस॰", "" } }; - yield return new object[] { "hr-BA", new string[] { "sij", "velj", "ožu", "tra", "svi", "lip", "srp", "kol", "ruj", "lis", "stu", "pro", "" } }; - yield return new object[] { "hr-HR", new string[] { "sij", "velj", "ožu", "tra", "svi", "lip", "srp", "kol", "ruj", "lis", "stu", "pro", "" } }; - yield return new object[] { "hu-HU", new string[] { "jan.", "febr.", "márc.", "ápr.", "máj.", "jún.", "júl.", "aug.", "szept.", "okt.", "nov.", "dec.", "" } }; - yield return new object[] { "id-ID", new string[] { "Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des", "" } }; - yield return new object[] { "it-CH", new string[] { "gen", "feb", "mar", "apr", "mag", "giu", "lug", "ago", "set", "ott", "nov", "dic", "" } }; - yield return new object[] { "it-IT", new string[] { "gen", "feb", "mar", "apr", "mag", "giu", "lug", "ago", "set", "ott", "nov", "dic", "" } }; - yield return new object[] { "ja-JP", new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - yield return new object[] { "kn-IN", new string[] { "ಜನವರಿ", "ಫೆಬ್ರವರಿ", "ಮಾರ್ಚ್", "ಏಪ್ರಿ", "ಮೇ", "ಜೂನ್", "ಜುಲೈ", "ಆಗಸ್ಟ್", "ಸೆಪ್ಟೆಂ", "ಅಕ್ಟೋ", "ನವೆಂ", "ಡಿಸೆಂ", "" } }; // "ಜನವರಿ", "ಫೆಬ್ರವರಿ", "ಮಾರ್ಚ್", "ಏಪ್ರಿ", "ಮೇ", "ಜೂನ್", "ಜುಲೈ", "ಆಗ", "ಸೆಪ್ಟೆಂ", "ಅಕ್ಟೋ", "ನವೆಂ", "ಡಿಸೆಂ", "" - yield return new object[] { "ko-KR", new string[] { "1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월", "" } }; - yield return new object[] { "lt-LT", new string[] { "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "" } }; // "saus.", "vas.", "kov.", "bal.", "geg.", "birž.", "liep.", "rugp.", "rugs.", "spal.", "lapkr.", "gruod." - yield return new object[] { "lv-LV", new string[] { "janv.", "febr.", "marts", "apr.", "maijs", "jūn.", "jūl.", "aug.", "sept.", "okt.", "nov.", "dec.", "" } }; - yield return new object[] { "ml-IN", new string[] { "ജനു", "ഫെബ്രു", "മാർ", "ഏപ്രി", "മേയ്", "ജൂൺ", "ജൂലൈ", "ഓഗ", "സെപ്റ്റം", "ഒക്ടോ", "നവം", "ഡിസം", "" } }; - yield return new object[] { "mr-IN", new string[] { "जाने", "फेब्रु", "मार्च", "एप्रि", "मे", "जून", "जुलै", "ऑग", "सप्टें", "ऑक्टो", "नोव्हें", "डिसें", "" } }; - yield return new object[] { "ms-BN", new string[] { "Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ogo", "Sep", "Okt", "Nov", "Dis", "" } }; - yield return new object[] { "ms-MY", new string[] { "Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ogo", "Sep", "Okt", "Nov", "Dis", "" } }; - yield return new object[] { "ms-SG", new string[] { "Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ogo", "Sep", "Okt", "Nov", "Dis", "" } }; - string[] norwegianMonths = new string [] { "jan.", "feb.", "mars", "apr.", "mai", "juni", "juli", "aug.", "sep.", "okt.", "nov.", "des.", "" }; // "jan.", "feb.", "mar.", "apr.", "mai", "jun.", "jul.", "aug.", "sep.", "okt.", "nov.", "des.", " - yield return new object[] { "nb-NO", norwegianMonths }; - yield return new object[] { "no-NO", norwegianMonths }; - string[] dutchMonths = new string[] { "jan", "feb", "mrt", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec", "" }; // "jan.", "feb.", "mrt.", "apr.", "mei", "jun.", "jul.", "aug.", "sep.", "okt.", "nov.", "dec.", "" - yield return new object[] { "nl-AW", dutchMonths }; - yield return new object[] { "nl-BE", dutchMonths }; - yield return new object[] { "nl-NL", dutchMonths }; - yield return new object[] { "pl-PL", new string[] { "sty", "lut", "mar", "kwi", "maj", "cze", "lip", "sie", "wrz", "paź", "lis", "gru", "" } }; - yield return new object[] { "pt-BR", new string[] { "jan.", "fev.", "mar.", "abr.", "mai.", "jun.", "jul.", "ago.", "set.", "out.", "nov.", "dez.", "" } }; - yield return new object[] { "pt-PT", new string[] { "jan.", "fev.", "mar.", "abr.", "mai.", "jun.", "jul.", "ago.", "set.", "out.", "nov.", "dez.", "" } }; - yield return new object[] { "ro-RO", new string[] { "ian.", "feb.", "mar.", "apr.", "mai", "iun.", "iul.", "aug.", "sept.", "oct.", "nov.", "dec.", "" } }; - yield return new object[] { "ru-RU", new string[] { "янв.", "февр.", "мар.", "апр.", "мая", "июн.", "июл.", "авг.", "сент.", "окт.", "нояб.", "дек.", "" } }; - yield return new object[] { "sk-SK", new string[] { "jan", "feb", "mar", "apr", "máj", "jún", "júl", "aug", "sep", "okt", "nov", "dec", "" } }; - yield return new object[] { "sl-SI", new string[] { "jan.", "feb.", "mar.", "apr.", "maj", "jun.", "jul.", "avg.", "sep.", "okt.", "nov.", "dec.", "" } }; - yield return new object[] { "sr-Cyrl-RS", new string[] { "јан", "феб", "мар", "апр", "мај", "јун", "јул", "авг", "сеп", "окт", "нов", "дец", "" } }; - yield return new object[] { "sr-Latn-RS", new string[] { "jan", "feb", "mar", "apr", "maj", "jun", "jul", "avg", "sep", "okt", "nov", "dec", "" } }; - yield return new object[] { "sv-AX", new string[] { "jan.", "feb.", "mars", "apr.", "maj", "juni", "juli", "aug.", "sep.", "okt.", "nov.", "dec.", "" } }; - yield return new object[] { "sv-SE", new string[] { "jan.", "feb.", "mars", "apr.", "maj", "juni", "juli", "aug.", "sep.", "okt.", "nov.", "dec.", "" } }; - yield return new object[] { "sw-CD", new string[] { "Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ago", "Sep", "Okt", "Nov", "Des", "" } }; - yield return new object[] { "sw-KE", new string[] { "Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ago", "Sep", "Okt", "Nov", "Des", "" } }; - yield return new object[] { "sw-TZ", new string[] { "Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ago", "Sep", "Okt", "Nov", "Des", "" } }; - yield return new object[] { "sw-UG", new string[] { "Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ago", "Sep", "Okt", "Nov", "Des", "" } }; - yield return new object[] { "ta-IN", new string[] { "ஜன.", "பிப்.", "மார்.", "ஏப்.", "மே", "ஜூன்", "ஜூலை", "ஆக.", "செப்.", "அக்.", "நவ.", "டிச.", "" } }; - yield return new object[] { "ta-LK", new string[] { "ஜன.", "பிப்.", "மார்.", "ஏப்.", "மே", "ஜூன்", "ஜூலை", "ஆக.", "செப்.", "அக்.", "நவ.", "டிச.", "" } }; - yield return new object[] { "ta-MY", new string[] { "ஜன.", "பிப்.", "மார்.", "ஏப்.", "மே", "ஜூன்", "ஜூலை", "ஆக.", "செப்.", "அக்.", "நவ.", "டிச.", "" } }; - yield return new object[] { "ta-SG", new string[] { "ஜன.", "பிப்.", "மார்.", "ஏப்.", "மே", "ஜூன்", "ஜூலை", "ஆக.", "செப்.", "அக்.", "நவ.", "டிச.", "" } }; - yield return new object[] { "te-IN", new string[] { "జన", "ఫిబ్ర", "మార్చి", "ఏప్రి", "మే", "జూన్", "జులై", "ఆగ", "సెప్టెం", "అక్టో", "నవం", "డిసెం", "" } }; - yield return new object[] { "th-TH", new string[] { "ม.ค.", "ก.พ.", "มี.ค.", "เม.ย.", "พ.ค.", "มิ.ย.", "ก.ค.", "ส.ค.", "ก.ย.", "ต.ค.", "พ.ย.", "ธ.ค.", "" } }; - yield return new object[] { "tr-CY", new string[] { "Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara", "" } }; - yield return new object[] { "tr-TR", new string[] { "Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara", "" } }; - yield return new object[] { "uk-UA", new string[] { "січ.", "лют.", "бер.", "квіт.", "трав.", "черв.", "лип.", "серп.", "вер.", "жовт.", "лист.", "груд.", "" } }; - string vietnameseAbbrMonth = PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "Thg" : "Tháng"; // thg - yield return new object[] { "vi-VN", new string[] { $"{vietnameseAbbrMonth} 1", $"{vietnameseAbbrMonth} 2", $"{vietnameseAbbrMonth} 3", $"{vietnameseAbbrMonth} 4", $"{vietnameseAbbrMonth} 5", $"{vietnameseAbbrMonth} 6", $"{vietnameseAbbrMonth} 7", $"{vietnameseAbbrMonth} 8", $"{vietnameseAbbrMonth} 9", $"{vietnameseAbbrMonth} 10", $"{vietnameseAbbrMonth} 11", $"{vietnameseAbbrMonth} 12", "" } }; - yield return new object[] { "zh-CN", new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - yield return new object[] { "zh-Hans-HK", new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - yield return new object[] { "zh-SG", new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - yield return new object[] { "zh-HK", new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - yield return new object[] { "zh-TW", new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - } [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization))] [MemberData(nameof(AbbreviatedMonthGenitiveNames_Get_TestData_ICU))] @@ -221,17 +21,6 @@ public void AbbreviatedMonthGenitiveNames_Get_ReturnsExpected_ICU(DateTimeFormat Assert.Equal(expected, format.AbbreviatedMonthGenitiveNames); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] - [MemberData(nameof(AbbreviatedMonthGenitiveNames_Get_TestData_HybridGlobalization))] - public void AbbreviatedMonthGenitiveNames_Get_ReturnsExpected_HybridGlobalization(string cultureName, string[] expected) - { - var format = new CultureInfo(cultureName).DateTimeFormat; - int length = format.AbbreviatedMonthGenitiveNames.Length; - Assert.True(length == expected.Length, $"Length comparison failed for culture: {cultureName}. Expected: {expected.Length}, Actual: {length}"); - for (int i = 0; i AbbreviatedMonthNames_Get_TestData_ICU() } - public static IEnumerable AbbreviatedMonthNames_Get_TestData_HybridGlobalization() - { - // see the comments on the right to check the non-Hybrid result, if it differs - yield return new object[] { "ar-SA", new string[] { "محرم", "صفر", "ربيع الأول", "ربيع الآخر", "جمادى الأولى", "جمادى الآخرة", "رجب", "شعبان", "رمضان", "شوال", "ذو القعدة", "ذو الحجة", "" } }; - if (PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS) - { - yield return new object[] { "am-ET", new string[] { "ጃንዩ", "ፌብሩ", "ማርች", "ኤፕሪ", "ሜይ", "ጁን", "ጁላይ", "ኦገስ", "ሴፕቴ", "ኦክቶ", "ኖቬም", "ዲሴም", "" } }; - yield return new object[] { "en-AU", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "es-MX", new string[] { "ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sept", "oct", "nov", "dic", "" } }; // "ene.", "feb.", "mar.", "abr.", "may.", "jun.", "jul.", "ago.", "sep.", "oct.", "nov.", "dic.", "" - yield return new object[] { "uk-UA", new string[] { "січ", "лют", "бер", "кві", "тра", "чер", "лип", "сер", "вер", "жов", "лис", "гру", "" } }; - yield return new object[] { "vi-VN", new string[] { "Thg 1", "Thg 2", "Thg 3", "Thg 4", "Thg 5", "Thg 6", "Thg 7", "Thg 8", "Thg 9", "Thg 10", "Thg 11", "Thg 12", "" } }; - } - else - { - yield return new object[] { "am-ET", new string[] { "ጃን", "ፌብ", "ማርች", "ኤፕሪ", "ሜይ", "ጁን", "ጁላይ", "ኦገስ", "ሴፕቴ", "ኦክቶ", "ኖቬም", "ዲሴም", "" } }; // "ጃንዩ", "ፌብሩ", "ማርች", "ኤፕሪ", "ሜይ", "ጁን", "ጁላይ", "ኦገስ", "ሴፕቴ", "ኦክቶ", "ኖቬም", "ዲሴም", "" - yield return new object[] { "en-AU", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Jun", "Jul" - yield return new object[] { "es-MX", new string[] { "ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sep", "oct", "nov", "dic", "" } }; // "ene.", "feb.", "mar.", "abr.", "may.", "jun.", "jul.", "ago.", "sep.", "oct.", "nov.", "dic.", "" - yield return new object[] { "uk-UA", new string[] { "січ.", "лют.", "бер.", "квіт.", "трав.", "черв.", "лип.", "серп.", "вер.", "жовт.", "лист.", "груд.", "" } }; // "січ", "лют", "бер", "кві", "тра", "чер", "лип", "сер", "вер", "жов", "лис", "гру", "" - yield return new object[] { "vi-VN", new string[] { "Tháng 1", "Tháng 2", "Tháng 3", "Tháng 4", "Tháng 5", "Tháng 6", "Tháng 7", "Tháng 8", "Tháng 9", "Tháng 10", "Tháng 11", "Tháng 12", "" } }; - } - yield return new object[] { "bg-BG", new string[] { "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "" } }; // "яну", "фев", "март", "апр", "май", "юни", "юли", "авг", "сеп", "окт", "ное", "дек", "" - yield return new object[] { "bn-BD", new string[] { "জানু", "ফেব", "মার্চ", "এপ্রিল", "মে", "জুন", "জুলাই", "আগস্ট", "সেপ্টেম্বর", "অক্টোবর", "নভেম্বর", "ডিসেম্বর", "" } }; // "জানুয়ারী", "ফেব্রুয়ারী", "মার্চ", "এপ্রিল", "মে", "জুন", "জুলাই", "আগস্ট", "সেপ্টেম্বর", "অক্টোবর", "নভেম্বর", "ডিসেম্বর", "" - yield return new object[] { "bn-IN", new string[] { "জানু", "ফেব", "মার্চ", "এপ্রিল", "মে", "জুন", "জুলাই", "আগস্ট", "সেপ্টেঃ", "অক্টোঃ", "নভেঃ", "ডিসেঃ", "" } }; // BUG. JS returns Genitive even though we expect Nominative; "জানু", "ফেব", "মার্চ", "এপ্রিল", "মে", "জুন", "জুলাই", "আগস্ট", "সেপ্টেম্বর", "অক্টোবর", "নভেম্বর", "ডিসেম্বর", "" - yield return new object[] { "ca-AD", new string[] { "gen.", "febr.", "març", "abr.", "maig", "juny", "jul.", "ag.", "set.", "oct.", "nov.", "des.", "" } }; - yield return new object[] { "ca-ES", new string[] { "gen.", "febr.", "març", "abr.", "maig", "juny", "jul.", "ag.", "set.", "oct.", "nov.", "des.", "" } }; - yield return new object[] { "cs-CZ", new string[] { "led", "úno", "bře", "dub", "kvě", "čvn", "čvc", "srp", "zář", "říj", "lis", "pro", "" } }; - yield return new object[] { "da-DK", new string[] { "jan.", "feb.", "mar.", "apr.", "maj", "jun.", "jul.", "aug.", "sep.", "okt.", "nov.", "dec.", "" } }; - yield return new object[] { "de-AT", new string[] { "Jän", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez", "" } }; - yield return new object[] { "de-BE", new string[] { "Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez", "" } }; - yield return new object[] { "de-CH", new string[] { "Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez", "" } }; - yield return new object[] { "de-DE", new string[] { "Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez", "" } }; - yield return new object[] { "de-IT", new string[] { "Jän", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez", "" } }; - yield return new object[] { "de-LI", new string[] { "Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez", "" } }; - yield return new object[] { "de-LU", new string[] { "Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez", "" } }; - yield return new object[] { "el-CY", new string[] { "Ιαν", "Φεβ", "Μαρ", "Απρ", "Μαΐ", "Ιουν", "Ιουλ", "Αυγ", "Σεπ", "Οκτ", "Νοε", "Δεκ", "" } }; // BUG. JS returns Genitive for Greek even though we expect Nominative; "Ιαν", "Φεβ", "Μάρ", "Απρ", "Μάι", "Ιούν", "Ιούλ", "Αύγ", "Σεπ", "Οκτ", "Νοέ", "Δεκ", "" - yield return new object[] { "el-GR", new string[] { "Ιαν", "Φεβ", "Μαρ", "Απρ", "Μαΐ", "Ιουν", "Ιουλ", "Αυγ", "Σεπ", "Οκτ", "Νοε", "Δεκ", "" } }; // BUG. - yield return new object[] { "en-AE", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-AG", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-AI", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-AS", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-AT", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-BB", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-BE", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-BI", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-BM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-BS", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-BW", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-BZ", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-CA", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; // "Jan.", "Feb.", "Mar.", "Apr.", "May", "Jun.", "Jul.", "Aug.", "Sep.", "Oct.", "Nov.", "Dec.", "" - yield return new object[] { "en-CC", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-CH", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-CK", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-CM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-CX", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-CY", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-DE", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-DK", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-DM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-ER", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-FI", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-FJ", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-FK", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-FM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-GB", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-GD", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-GG", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-GH", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-GI", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-GM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-GU", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-GY", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-HK", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-IE", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-IL", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-IM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-IN", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-IO", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-JE", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-JM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-KE", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-KI", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-KN", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-KY", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-LC", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-LR", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-LS", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-MG", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-MH", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-MO", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-MP", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-MS", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-MT", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-MU", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-MW", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-MY", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-NA", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-NF", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-NG", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-NL", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-NR", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-NU", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-NZ", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-PG", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-PH", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-PK", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-PN", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-PR", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-PW", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-RW", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SB", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SC", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SD", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SE", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SG", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SH", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SI", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SL", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SS", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SX", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-SZ", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-TC", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-TK", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-TO", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-TT", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-TV", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-TZ", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-UG", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-UM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-US", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-VC", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-VG", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-VI", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" } }; - yield return new object[] { "en-VU", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-WS", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-ZA", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-ZM", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "en-ZW", new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "" } }; // "Sep" - yield return new object[] { "es-419", new string[] { "ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sept", "oct", "nov", "dic", "" } }; // "ene.", "feb.", "mar.", "abr.", "may.", "jun.", "jul.", "ago.", "sep.", "oct.", "nov.", "dic.", "" - yield return new object[] { "es-ES", new string[] { "ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sept", "oct", "nov", "dic", "" } }; // "ene.", "feb.", "mar.", "abr.", "may.", "jun.", "jul.", "ago.", "sep.", "oct.", "nov.", "dic.", "" - yield return new object[] { "et-EE", new string[] { "jaanuar", "veebruar", "märts", "aprill", "mai", "juuni", "juuli", "august", "september", "oktoober", "november", "detsember", "" } }; // "jaan", "veebr", "märts", "apr", "mai", "juuni", "juuli", "aug", "sept", "okt", "nov", "dets", "" - yield return new object[] { "fa-IR", new string[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" } }; - yield return new object[] { "fi-FI", new string[] { "tammi", "helmi", "maalis", "huhti", "touko", "kesä", "heinä", "elo", "syys", "loka", "marras", "joulu", "" } }; - yield return new object[] { "fil-PH", new string[] { "Ene", "Peb", "Mar", "Abr", "May", "Hun", "Hul", "Ago", "Set", "Okt", "Nob", "Dis", "" } }; - yield return new object[] { "fr-BE", new string[] { "janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc.", "" } }; - yield return new object[] { "fr-CA", new string[] { "janv.", "févr.", "mars", "avr.", "mai", "juin", "juill.", "août", "sept.", "oct.", "nov.", "déc.", "" } }; - yield return new object[] { "fr-CH", new string[] { "janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc.", "" } }; - yield return new object[] { "fr-FR", new string[] { "janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc.", "" } }; - yield return new object[] { "gu-IN", new string[] { "જાન્યુ", "ફેબ્રુ", "માર્ચ", "એપ્રિલ", "મે", "જૂન", "જુલાઈ", "ઑગસ્ટ", "સપ્ટે", "ઑક્ટો", "નવે", "ડિસે", "" } }; - yield return new object[] { "he-IL", new string[] { "ינו׳", "פבר׳", "מרץ", "אפר׳", "מאי", "יוני", "יולי", "אוג׳", "ספט׳", "אוק׳", "נוב׳", "דצמ׳", "" } }; - yield return new object[] { "hi-IN", new string[] { "जन॰", "फ़र॰", "मार्च", "अप्रैल", "मई", "जून", "जुल॰", "अग॰", "सित॰", "अक्तू॰", "नव॰", "दिस॰", "" } }; - yield return new object[] { "hr-BA", new string[] { "sij", "velj", "ožu", "tra", "svi", "lip", "srp", "kol", "ruj", "lis", "stu", "pro", "" } }; - yield return new object[] { "hr-HR", new string[] { "sij", "velj", "ožu", "tra", "svi", "lip", "srp", "kol", "ruj", "lis", "stu", "pro", "" } }; - yield return new object[] { "hu-HU", new string[] { "jan.", "febr.", "márc.", "ápr.", "máj.", "jún.", "júl.", "aug.", "szept.", "okt.", "nov.", "dec.", "" } }; - yield return new object[] { "id-ID", new string[] { "Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des", "" } }; - yield return new object[] { "it-CH", new string[] { "gen", "feb", "mar", "apr", "mag", "giu", "lug", "ago", "set", "ott", "nov", "dic", "" } }; - yield return new object[] { "it-IT", new string[] { "gen", "feb", "mar", "apr", "mag", "giu", "lug", "ago", "set", "ott", "nov", "dic", "" } }; - yield return new object[] { "ja-JP", new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - yield return new object[] { "kn-IN", new string[] { "ಜನ", "ಫೆಬ್ರ", "ಮಾರ್ಚ್", "ಏಪ್ರಿ", "ಮೇ", "ಜೂನ್", "ಜುಲೈ", "ಆಗ", "ಸೆಪ್ಟೆಂ", "ಅಕ್ಟೋ", "ನವೆಂ", "ಡಿಸೆಂ", "" } }; - yield return new object[] { "ko-KR", new string[] { "1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월", "" } }; - yield return new object[] { "lt-LT", new string[] { "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "" } }; // "saus.", "vas.", "kov.", "bal.", "geg.", "birž.", "liep.", "rugp.", "rugs.", "spal.", "lapkr.", "gruod." - yield return new object[] { "lv-LV", new string[] { "janv.", "febr.", "marts", "apr.", "maijs", "jūn.", "jūl.", "aug.", "sept.", "okt.", "nov.", "dec.", "" } }; - yield return new object[] { "ml-IN", new string[] { "ജനു", "ഫെബ്രു", "മാർ", "ഏപ്രി", "മേയ്", "ജൂൺ", "ജൂലൈ", "ഓഗ", "സെപ്റ്റം", "ഒക്ടോ", "നവം", "ഡിസം", "" } }; - yield return new object[] { "mr-IN", new string[] { "जाने", "फेब्रु", "मार्च", "एप्रि", "मे", "जून", "जुलै", "ऑग", "सप्टें", "ऑक्टो", "नोव्हें", "डिसें", "" } }; - yield return new object[] { "ms-BN", new string[] { "Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ogo", "Sep", "Okt", "Nov", "Dis", "" } }; - yield return new object[] { "ms-MY", new string[] { "Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ogo", "Sep", "Okt", "Nov", "Dis", "" } }; - yield return new object[] { "ms-SG", new string[] { "Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ogo", "Sep", "Okt", "Nov", "Dis", "" } }; - yield return new object[] { "nb-NO", new string[] { "jan", "feb", "mar", "apr", "mai", "jun", "jul", "aug", "sep", "okt", "nov", "des", "" } }; - yield return new object[] { "no", new string[] { "jan", "feb", "mar", "apr", "mai", "jun", "jul", "aug", "sep", "okt", "nov", "des", "" } }; - yield return new object[] { "no-NO", new string[] { "jan", "feb", "mar", "apr", "mai", "jun", "jul", "aug", "sep", "okt", "nov", "des", "" } }; - var dutchMonths = new string[] { "jan", "feb", "mrt", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec", "" }; // "jan.", "feb.", "mrt.", "apr.", "mei", "jun.", "jul.", "aug.", "sep.", "okt.", "nov.", "dec.", "" - yield return new object[] { "nl-AW", dutchMonths }; - yield return new object[] { "nl-BE", dutchMonths }; - yield return new object[] { "nl-NL", dutchMonths }; - yield return new object[] { "pl-PL", new string[] { "sty", "lut", "mar", "kwi", "maj", "cze", "lip", "sie", "wrz", "paź", "lis", "gru", "" } }; - yield return new object[] { "pt-BR", new string[] { "jan.", "fev.", "mar.", "abr.", "mai.", "jun.", "jul.", "ago.", "set.", "out.", "nov.", "dez.", "" } }; - yield return new object[] { "pt-PT", new string[] { "jan.", "fev.", "mar.", "abr.", "mai.", "jun.", "jul.", "ago.", "set.", "out.", "nov.", "dez.", "" } }; - yield return new object[] { "ro-RO", new string[] { "ian.", "feb.", "mar.", "apr.", "mai", "iun.", "iul.", "aug.", "sept.", "oct.", "nov.", "dec.", "" } }; - yield return new object[] { "ru-RU", new string[] { "янв.", "февр.", "март", "апр.", "май", "июнь", "июль", "авг.", "сент.", "окт.", "нояб.", "дек.", "" } }; - yield return new object[] { "sk-SK", new string[] { "jan", "feb", "mar", "apr", "máj", "jún", "júl", "aug", "sep", "okt", "nov", "dec", "" } }; - yield return new object[] { "sl-SI", new string[] { "jan.", "feb.", "mar.", "apr.", "maj", "jun.", "jul.", "avg.", "sep.", "okt.", "nov.", "dec.", "" } }; - yield return new object[] { "sr-Cyrl-RS", new string[] { "јан", "феб", "мар", "апр", "мај", "јун", "јул", "авг", "сеп", "окт", "нов", "дец", "" } }; - yield return new object[] { "sr-Latn-RS", new string[] { "jan", "feb", "mar", "apr", "maj", "jun", "jul", "avg", "sep", "okt", "nov", "dec", "" } }; - yield return new object[] { "sv-AX", new string[] { "jan.", "feb.", "mars", "apr.", "maj", "juni", "juli", "aug.", "sep.", "okt.", "nov.", "dec.", "" } }; - yield return new object[] { "sv-SE", new string[] { "jan.", "feb.", "mars", "apr.", "maj", "juni", "juli", "aug.", "sep.", "okt.", "nov.", "dec.", "" } }; - yield return new object[] { "sw-CD", new string[] { "Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ago", "Sep", "Okt", "Nov", "Des", "" } }; - yield return new object[] { "sw-KE", new string[] { "Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ago", "Sep", "Okt", "Nov", "Des", "" } }; - yield return new object[] { "sw-TZ", new string[] { "Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ago", "Sep", "Okt", "Nov", "Des", "" } }; - yield return new object[] { "sw-UG", new string[] { "Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ago", "Sep", "Okt", "Nov", "Des", "" } }; - yield return new object[] { "ta-IN", new string[] { "ஜன.", "பிப்.", "மார்.", "ஏப்.", "மே", "ஜூன்", "ஜூலை", "ஆக.", "செப்.", "அக்.", "நவ.", "டிச.", "" } }; - yield return new object[] { "ta-LK", new string[] { "ஜன.", "பிப்.", "மார்.", "ஏப்.", "மே", "ஜூன்", "ஜூலை", "ஆக.", "செப்.", "அக்.", "நவ.", "டிச.", "" } }; - yield return new object[] { "ta-MY", new string[] { "ஜன.", "பிப்.", "மார்.", "ஏப்.", "மே", "ஜூன்", "ஜூலை", "ஆக.", "செப்.", "அக்.", "நவ.", "டிச.", "" } }; - yield return new object[] { "ta-SG", new string[] { "ஜன.", "பிப்.", "மார்.", "ஏப்.", "மே", "ஜூன்", "ஜூலை", "ஆக.", "செப்.", "அக்.", "நவ.", "டிச.", "" } }; - yield return new object[] { "te-IN", new string[] { "జన", "ఫిబ్ర", "మార్చి", "ఏప్రి", "మే", "జూన్", "జులై", "ఆగ", "సెప్టెం", "అక్టో", "నవం", "డిసెం", "" } }; - yield return new object[] { "th-TH", new string[] { "ม.ค.", "ก.พ.", "มี.ค.", "เม.ย.", "พ.ค.", "มิ.ย.", "ก.ค.", "ส.ค.", "ก.ย.", "ต.ค.", "พ.ย.", "ธ.ค.", "" } }; - yield return new object[] { "tr-CY", new string[] { "Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara", "" } }; - yield return new object[] { "tr-TR", new string[] { "Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara", "" } }; - - yield return new object[] { "zh-CN", new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - yield return new object[] { "zh-Hans-HK", new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - yield return new object[] { "zh-SG", new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - yield return new object[] { "zh-HK", new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - yield return new object[] { "zh-TW", new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization))] [MemberData(nameof(AbbreviatedMonthNames_Get_TestData_ICU))] public void AbbreviatedMonthNames_Get_ReturnsExpected_ICU(DateTimeFormatInfo format, string[] expected) @@ -246,17 +42,6 @@ public void AbbreviatedMonthNames_Get_ReturnsExpected_ICU(DateTimeFormatInfo for Assert.Equal(expected, format.AbbreviatedMonthNames); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] - [MemberData(nameof(AbbreviatedMonthNames_Get_TestData_HybridGlobalization))] - public void AbbreviatedMonthNames_Get_ReturnsExpected_HybridGlobalization(string cultureName, string[] expected) - { - var format = new CultureInfo(cultureName).DateTimeFormat; - int length = format.AbbreviatedMonthNames.Length; - Assert.True(length == expected.Length, $"Length comparison failed for culture: {cultureName}. Expected: {expected.Length}, Actual: {length}"); - for (int i = 0; i CalendarWeekRule_Get_TestData() // "br-FR" is not presented in Browser's ICU. Let's test ru-RU instead. yield return new object[] { "ru-RU", CalendarWeekRule.FirstFourDayWeek }; } - - if (PlatformDetection.IsHybridGlobalizationOnBrowser) - { - yield return new object[] { "ar-SA", CalendarWeekRule.FirstDay }; - yield return new object[] { "am-ET", CalendarWeekRule.FirstDay }; - yield return new object[] { "bg-BG", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "bn-BD", CalendarWeekRule.FirstDay }; - yield return new object[] { "bn-IN", CalendarWeekRule.FirstDay }; - yield return new object[] { "ca-AD", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "ca-ES", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "cs-CZ", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "da-DK", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "de-AT", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "de-BE", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "de-CH", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "de-DE", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "de-IT", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "de-LI", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "de-LU", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "el-CY", CalendarWeekRule.FirstDay }; - yield return new object[] { "el-GR", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "en-AE", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-AG", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-AI", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-AS", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-AT", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "en-AU", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-BB", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-BE", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "en-BI", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-BM", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-BS", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-BW", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-BZ", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-CA", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-CC", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-CH", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "en-CK", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-CM", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-CX", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-CY", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-DE", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "en-DK", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "en-DM", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-ER", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-FI", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "en-FJ", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "en-FK", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-FM", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-GB", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "en-GD", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-GG", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "en-GH", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-GI", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "en-GM", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-GU", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-GY", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-HK", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-IE", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "en-IL", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-IM", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "en-IN", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-IO", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-JE", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "en-JM", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-KE", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-KI", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-KN", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-KY", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-LC", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-LR", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-LS", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-MG", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-MH", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-MO", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-MP", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-MS", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-MT", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-MU", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-MW", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-MY", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-NA", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-NF", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-NG", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-NL", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "en-NR", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-NU", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-NZ", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-PG", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-PH", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-PK", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-PN", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-PR", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-PW", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-RW", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-SB", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-SC", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-SD", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-SE", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "en-SG", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-SH", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-SI", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-SL", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-SS", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-SX", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-SZ", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-TC", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-TK", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-TO", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-TT", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-TV", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-TZ", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-UG", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-UM", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-US", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-VC", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-VG", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-VI", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-VU", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-WS", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-ZA", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-ZM", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-ZW", CalendarWeekRule.FirstDay }; - yield return new object[] { "en-US", CalendarWeekRule.FirstDay }; - yield return new object[] { "es-419", CalendarWeekRule.FirstDay }; - yield return new object[] { "es-ES", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "es-MX", CalendarWeekRule.FirstDay }; - yield return new object[] { "et-EE", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "fa-IR", CalendarWeekRule.FirstDay }; - yield return new object[] { "fi-FI", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "fil-PH", CalendarWeekRule.FirstDay }; - yield return new object[] { "fr-BE", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "fr-CA", CalendarWeekRule.FirstDay }; - yield return new object[] { "fr-CH", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "fr-FR", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "gu-IN", CalendarWeekRule.FirstDay }; - yield return new object[] { "he-IL", CalendarWeekRule.FirstDay }; - yield return new object[] { "hi-IN", CalendarWeekRule.FirstDay }; - yield return new object[] { "hr-BA", CalendarWeekRule.FirstDay }; - yield return new object[] { "hr-HR", CalendarWeekRule.FirstDay }; - yield return new object[] { "hu-HU", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "id-ID", CalendarWeekRule.FirstDay }; - yield return new object[] { "it-CH", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "it-IT", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "ja-JP", CalendarWeekRule.FirstDay }; - yield return new object[] { "kn-IN", CalendarWeekRule.FirstDay }; - yield return new object[] { "ko-KR", CalendarWeekRule.FirstDay }; - yield return new object[] { "lt-LT", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "lv-LV", CalendarWeekRule.FirstDay }; - yield return new object[] { "ml-IN", CalendarWeekRule.FirstDay }; - yield return new object[] { "mr-IN", CalendarWeekRule.FirstDay }; - yield return new object[] { "ms-BN", CalendarWeekRule.FirstDay }; - yield return new object[] { "ms-MY", CalendarWeekRule.FirstDay }; - yield return new object[] { "ms-SG", CalendarWeekRule.FirstDay }; - yield return new object[] { "nb-NO", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "no-NO", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "nl-AW", CalendarWeekRule.FirstDay }; - yield return new object[] { "nl-BE", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "nl-NL", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "pl-PL", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "pt-BR", CalendarWeekRule.FirstDay }; - yield return new object[] { "pt-PT", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "ro-RO", CalendarWeekRule.FirstDay }; - yield return new object[] { "ru-RU", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "sk-SK", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "sl-SI", CalendarWeekRule.FirstDay }; - yield return new object[] { "sr-Cyrl-RS", CalendarWeekRule.FirstDay }; - yield return new object[] { "sr-Latn-RS", CalendarWeekRule.FirstDay }; - yield return new object[] { "sv-AX", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "sv-SE", CalendarWeekRule.FirstFourDayWeek }; - yield return new object[] { "sw-CD", CalendarWeekRule.FirstDay }; - yield return new object[] { "sw-KE", CalendarWeekRule.FirstDay }; - yield return new object[] { "sw-TZ", CalendarWeekRule.FirstDay }; - yield return new object[] { "sw-UG", CalendarWeekRule.FirstDay }; - yield return new object[] { "ta-IN", CalendarWeekRule.FirstDay }; - yield return new object[] { "ta-LK", CalendarWeekRule.FirstDay }; - yield return new object[] { "ta-MY", CalendarWeekRule.FirstDay }; - yield return new object[] { "ta-SG", CalendarWeekRule.FirstDay }; - yield return new object[] { "te-IN", CalendarWeekRule.FirstDay }; - yield return new object[] { "th-TH", CalendarWeekRule.FirstDay }; - yield return new object[] { "tr-CY", CalendarWeekRule.FirstDay }; - yield return new object[] { "tr-TR", CalendarWeekRule.FirstDay }; - yield return new object[] { "uk-UA", CalendarWeekRule.FirstDay }; - yield return new object[] { "vi-VN", CalendarWeekRule.FirstDay }; - yield return new object[] { "zh-CN", CalendarWeekRule.FirstDay }; - yield return new object[] { "zh-Hans-HK", CalendarWeekRule.FirstDay }; - yield return new object[] { "zh-SG", CalendarWeekRule.FirstDay }; - yield return new object[] { "zh-HK", CalendarWeekRule.FirstDay }; - yield return new object[] { "zh-TW", CalendarWeekRule.FirstDay }; - } } [Theory] diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoData.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoData.cs index 253d610184750e..40de319b78c5d3 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoData.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoData.cs @@ -69,7 +69,7 @@ public static Exception GetCultureNotSupportedException(CultureInfo cultureInfo) }; public static bool HasBadIcuTimePatterns(CultureInfo culture) { - return PlatformDetection.IsIcuGlobalizationAndNotHybridOnBrowser + return PlatformDetection.IsIcuGlobalization && _badIcuTimePatterns.TryGetValue(culture.Name, out var version) && PlatformDetection.ICUVersion < version; } diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoDayNames.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoDayNames.cs index e84949d20d8c19..203e4d003acf7c 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoDayNames.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoDayNames.cs @@ -34,56 +34,6 @@ public static IEnumerable DayNames_Get_TestData_ICU() yield return new object[] { CultureInfo.GetCultureInfo("fr-FR").DateTimeFormat, new string[] { "dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi" } }; } - public static IEnumerable DayNames_Get_TestData_HybridGlobalization() - { - yield return new object[] { "ar-SA", new string[] { "الأحد", "الاثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعة", "السبت" } }; - yield return new object[] { "am-ET", new string[] { "እሑድ", "ሰኞ", "ማክሰኞ", "ረቡዕ", "ሐሙስ", "ዓርብ", "ቅዳሜ" } }; - yield return new object[] { "bg-BG", new string[] { "неделя", "понеделник", "вторник", "сряда", "четвъртък", "петък", "събота" } }; - yield return new object[] { "bn-BD", new string[] { "রবিবার", "সোমবার", "মঙ্গলবার", "বুধবার", "বৃহস্পতিবার", "শুক্রবার", "শনিবার" } }; - yield return new object[] { "ca-ES", new string[] { "diumenge", "dilluns", "dimarts", "dimecres", "dijous", "divendres", "dissabte" } }; - yield return new object[] { "cs-CZ", new string[] { "neděle", "pondělí", "úterý", "středa", "čtvrtek", "pátek", "sobota" } }; - yield return new object[] { "de-AT", new string[] { "Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag" } }; - yield return new object[] { "el-GR", new string[] { "Κυριακή", "Δευτέρα", "Τρίτη", "Τετάρτη", "Πέμπτη", "Παρασκευή", "Σάββατο" } }; - yield return new object[] { "en-US", new string[] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" } }; - yield return new object[] { "es-419", new string[] { "domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado" } }; - yield return new object[] { "es-ES", new string[] { "domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado" } }; - yield return new object[] { "es-MX", new string[] { "domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado" } }; - yield return new object[] { "et-EE", new string[] { "pühapäev", "esmaspäev", "teisipäev", "kolmapäev", "neljapäev", "reede", "laupäev" } }; - yield return new object[] { "fa-IR", new string[] { "یکشنبه", "دوشنبه", "سه‌شنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه" } }; - yield return new object[] { "fi-FI", new string[] { "sunnuntai", "maanantai", "tiistai", "keskiviikko", "torstai", "perjantai", "lauantai" } }; - yield return new object[] { "fil-PH", new string[] { "Linggo", "Lunes", "Martes", "Miyerkules", "Huwebes", "Biyernes", "Sabado" } }; - yield return new object[] { "fr-FR", new string[] { "dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi" } }; - yield return new object[] { "gu-IN", new string[] { "રવિવાર", "સોમવાર", "મંગળવાર", "બુધવાર", "ગુરુવાર", "શુક્રવાર", "શનિવાર" } }; - yield return new object[] { "he-IL", new string[] { "יום ראשון", "יום שני", "יום שלישי", "יום רביעי", "יום חמישי", "יום שישי", "יום שבת" } }; - yield return new object[] { "hi-IN", new string[] { "रविवार", "सोमवार", "मंगलवार", "बुधवार", "गुरुवार", "शुक्रवार", "शनिवार" } }; - yield return new object[] { "hr-HR", new string[] { "nedjelja", "ponedjeljak", "utorak", "srijeda", "četvrtak", "petak", "subota" } }; - yield return new object[] { "hu-HU", new string[] { "vasárnap", "hétfő", "kedd", "szerda", "csütörtök", "péntek", "szombat" } }; - yield return new object[] { "id-ID", new string[] { "Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu" } }; - yield return new object[] { "it-IT", new string[] { "domenica", "lunedì", "martedì", "mercoledì", "giovedì", "venerdì", "sabato" } }; - yield return new object[] { "ja-JP", new string[] { "日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日" } }; - yield return new object[] { "kn-IN", new string[] { "ಭಾನುವಾರ", "ಸೋಮವಾರ", "ಮಂಗಳವಾರ", "ಬುಧವಾರ", "ಗುರುವಾರ", "ಶುಕ್ರವಾರ", "ಶನಿವಾರ" } }; - yield return new object[] { "ko-KR", new string[] { "일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일" } }; - yield return new object[] { "lt-LT", new string[] { "sekmadienis", "pirmadienis", "antradienis", "trečiadienis", "ketvirtadienis", "penktadienis", "šeštadienis" } }; - yield return new object[] { "lv-LV", new string[] { "Svētdiena", "Pirmdiena", "Otrdiena", "Trešdiena", "Ceturtdiena", "Piektdiena", "Sestdiena" } }; - yield return new object[] { "ml-IN", new string[] { "ഞായറാഴ്‌ച", "തിങ്കളാഴ്‌ച", "ചൊവ്വാഴ്‌ച", "ബുധനാഴ്‌ച", "വ്യാഴാഴ്‌ച", "വെള്ളിയാഴ്‌ച", "ശനിയാഴ്‌ച" } }; - yield return new object[] { "ms-BN", new string[] { "Ahad", "Isnin", "Selasa", "Rabu", "Khamis", "Jumaat", "Sabtu" } }; - yield return new object[] { "no-NO", new string[] { "søndag", "mandag", "tirsdag", "onsdag", "torsdag", "fredag", "lørdag" } }; - yield return new object[] { "nl-AW", new string[] { "zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag" } }; - yield return new object[] { "pl-PL", new string[] { "niedziela", "poniedziałek", "wtorek", "środa", "czwartek", "piątek", "sobota" } }; - yield return new object[] { "pt-PT", new string[] { "domingo", "segunda-feira", "terça-feira", "quarta-feira", "quinta-feira", "sexta-feira", "sábado" } }; - yield return new object[] { "ro-RO", new string[] { "duminică", "luni", "marți", "miercuri", "joi", "vineri", "sâmbătă" } }; - yield return new object[] { "sk-SK", new string[] { "nedeľa", "pondelok", "utorok", "streda", "štvrtok", "piatok", "sobota" } }; - yield return new object[] { "sv-AX", new string[] { "söndag", "måndag", "tisdag", "onsdag", "torsdag", "fredag", "lördag" } }; - yield return new object[] { "sw-CD", new string[] { "Jumapili", "Jumatatu", "Jumanne", "Jumatano", "Alhamisi", "Ijumaa", "Jumamosi" } }; - yield return new object[] { "ta-IN", new string[] { "ஞாயிறு", "திங்கள்", "செவ்வாய்", "புதன்", "வியாழன்", "வெள்ளி", "சனி" } }; - yield return new object[] { "te-IN", new string[] { "ఆదివారం", "సోమవారం", "మంగళవారం", "బుధవారం", "గురువారం", "శుక్రవారం", "శనివారం" } }; - yield return new object[] { "th-TH", new string[] { "วันอาทิตย์", "วันจันทร์", "วันอังคาร", "วันพุธ", "วันพฤหัสบดี", "วันศุกร์", "วันเสาร์" } }; - yield return new object[] { "tr-CY", new string[] { "Pazar", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi" } }; - yield return new object[] { "uk-UA", new string[] { "неділя", "понеділок", "вівторок", "середа", "четвер", "пʼятниця", "субота" } }; - yield return new object[] { "vi-VN", new string[] { "Chủ Nhật", "Thứ Hai", "Thứ Ba", "Thứ Tư", "Thứ Năm", "Thứ Sáu", "Thứ Bảy" } }; - yield return new object[] { "zh-TW", new string[] { "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" } }; - } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization))] [MemberData(nameof(DayNames_Get_TestData_ICU))] public void DayNames_Get_ReturnsExpected_ICU(DateTimeFormatInfo format, string[] expected) @@ -91,17 +41,6 @@ public void DayNames_Get_ReturnsExpected_ICU(DateTimeFormatInfo format, string[] Assert.Equal(expected, format.DayNames); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] - [MemberData(nameof(DayNames_Get_TestData_HybridGlobalization))] - public void DayNames_Get_ReturnsExpected_HybridGlobalization(string cultureName, string[] expected) - { - var format = new CultureInfo(cultureName).DateTimeFormat; - int length = format.DayNames.Length; - Assert.True(length == expected.Length, $"Length comparison failed for culture: {cultureName}. Expected: {expected.Length}, Actual: {length}"); - for (int i = 0; i FirstDayOfWeek_Get_TestData() yield return new object[] { new CultureInfo("fr-FR", false).DateTimeFormat, DayOfWeek.Monday, "fr-FR" }; } - public static IEnumerable FirstDayOfWeek_Get_TestData_HybridGlobalization() - { - // see the comments on the right to check the non-Hybrid result, if it differs - yield return new object[] { "ar-SA", DayOfWeek.Sunday }; - yield return new object[] { "am-ET", DayOfWeek.Sunday }; - yield return new object[] { "bg-BG", DayOfWeek.Monday }; - yield return new object[] { "bn-BD", DayOfWeek.Sunday }; - yield return new object[] { "bn-IN", DayOfWeek.Sunday }; - yield return new object[] { "ca-AD", DayOfWeek.Monday }; - yield return new object[] { "ca-ES", DayOfWeek.Monday }; - yield return new object[] { "cs-CZ", DayOfWeek.Monday }; - yield return new object[] { "da-DK", DayOfWeek.Monday }; - yield return new object[] { "de-AT", DayOfWeek.Monday }; - yield return new object[] { "de-BE", DayOfWeek.Monday }; - yield return new object[] { "de-CH", DayOfWeek.Monday }; - yield return new object[] { "de-DE", DayOfWeek.Monday }; - yield return new object[] { "de-IT", DayOfWeek.Monday }; - yield return new object[] { "de-LI", DayOfWeek.Monday }; - yield return new object[] { "de-LU", DayOfWeek.Monday }; - yield return new object[] { "el-CY", DayOfWeek.Monday }; - yield return new object[] { "el-GR", DayOfWeek.Monday }; - yield return new object[] { "en-AE", DayOfWeek.Saturday }; - yield return new object[] { "en-AG", DayOfWeek.Sunday }; - yield return new object[] { "en-AI", DayOfWeek.Monday }; - yield return new object[] { "en-AS", DayOfWeek.Sunday }; - yield return new object[] { "en-AT", DayOfWeek.Monday }; - yield return new object[] { "en-AU", DayOfWeek.Monday }; // originally in ICU: Sunday, even though ISO 8601 states: Monday - yield return new object[] { "en-BB", DayOfWeek.Monday }; - yield return new object[] { "en-BE", DayOfWeek.Monday }; - yield return new object[] { "en-BI", DayOfWeek.Monday }; - yield return new object[] { "en-BM", DayOfWeek.Monday }; - yield return new object[] { "en-BS", DayOfWeek.Sunday }; - yield return new object[] { "en-BW", DayOfWeek.Sunday }; - yield return new object[] { "en-BZ", DayOfWeek.Sunday }; - yield return new object[] { "en-CA", DayOfWeek.Sunday }; - yield return new object[] { "en-CC", DayOfWeek.Monday }; - yield return new object[] { "en-CH", DayOfWeek.Monday }; - yield return new object[] { "en-CK", DayOfWeek.Monday }; - yield return new object[] { "en-CM", DayOfWeek.Monday }; - yield return new object[] { "en-CX", DayOfWeek.Monday }; - yield return new object[] { "en-CY", DayOfWeek.Monday }; - yield return new object[] { "en-DE", DayOfWeek.Monday }; - yield return new object[] { "en-DK", DayOfWeek.Monday }; - yield return new object[] { "en-DM", DayOfWeek.Sunday }; - yield return new object[] { "en-ER", DayOfWeek.Monday }; - yield return new object[] { "en-FI", DayOfWeek.Monday }; - yield return new object[] { "en-FJ", DayOfWeek.Monday }; - yield return new object[] { "en-FK", DayOfWeek.Monday }; - yield return new object[] { "en-FM", DayOfWeek.Monday }; - yield return new object[] { "en-GB", DayOfWeek.Monday }; - yield return new object[] { "en-GD", DayOfWeek.Monday }; - yield return new object[] { "en-GG", DayOfWeek.Monday }; - yield return new object[] { "en-GH", DayOfWeek.Monday }; - yield return new object[] { "en-GI", DayOfWeek.Monday }; - yield return new object[] { "en-GM", DayOfWeek.Monday }; - yield return new object[] { "en-GU", DayOfWeek.Sunday }; - yield return new object[] { "en-GY", DayOfWeek.Monday }; - yield return new object[] { "en-HK", DayOfWeek.Sunday }; - yield return new object[] { "en-IE", DayOfWeek.Monday }; - yield return new object[] { "en-IL", DayOfWeek.Sunday }; - yield return new object[] { "en-IM", DayOfWeek.Monday }; - yield return new object[] { "en-IN", DayOfWeek.Sunday }; - yield return new object[] { "en-IO", DayOfWeek.Monday }; - yield return new object[] { "en-JE", DayOfWeek.Monday }; - yield return new object[] { "en-JM", DayOfWeek.Sunday }; - yield return new object[] { "en-KE", DayOfWeek.Sunday }; - yield return new object[] { "en-KI", DayOfWeek.Monday }; - yield return new object[] { "en-KN", DayOfWeek.Monday }; - yield return new object[] { "en-KY", DayOfWeek.Monday }; - yield return new object[] { "en-LC", DayOfWeek.Monday }; - yield return new object[] { "en-LR", DayOfWeek.Monday }; - yield return new object[] { "en-LS", DayOfWeek.Monday }; - yield return new object[] { "en-MG", DayOfWeek.Monday }; - yield return new object[] { "en-MH", DayOfWeek.Sunday }; - yield return new object[] { "en-MO", DayOfWeek.Sunday }; - yield return new object[] { "en-MP", DayOfWeek.Monday }; - yield return new object[] { "en-MS", DayOfWeek.Monday }; - yield return new object[] { "en-MT", DayOfWeek.Sunday }; - yield return new object[] { "en-MU", DayOfWeek.Monday }; - yield return new object[] { "en-MW", DayOfWeek.Monday }; - yield return new object[] { "en-MY", DayOfWeek.Monday }; - yield return new object[] { "en-NA", DayOfWeek.Monday }; - yield return new object[] { "en-NF", DayOfWeek.Monday }; - yield return new object[] { "en-NG", DayOfWeek.Monday }; - yield return new object[] { "en-NL", DayOfWeek.Monday }; - yield return new object[] { "en-NR", DayOfWeek.Monday }; - yield return new object[] { "en-NU", DayOfWeek.Monday }; - yield return new object[] { "en-NZ", DayOfWeek.Monday }; - yield return new object[] { "en-PG", DayOfWeek.Monday }; - yield return new object[] { "en-PH", DayOfWeek.Sunday }; - yield return new object[] { "en-PK", DayOfWeek.Sunday }; - yield return new object[] { "en-PN", DayOfWeek.Monday }; - yield return new object[] { "en-PR", DayOfWeek.Sunday }; - yield return new object[] { "en-PW", DayOfWeek.Monday }; - yield return new object[] { "en-RW", DayOfWeek.Monday }; - yield return new object[] { "en-SB", DayOfWeek.Monday }; - yield return new object[] { "en-SC", DayOfWeek.Monday }; - yield return new object[] { "en-SD", DayOfWeek.Saturday }; - yield return new object[] { "en-SE", DayOfWeek.Monday }; - yield return new object[] { "en-SG", DayOfWeek.Sunday }; - yield return new object[] { "en-SH", DayOfWeek.Monday }; - yield return new object[] { "en-SI", DayOfWeek.Monday }; - yield return new object[] { "en-SL", DayOfWeek.Monday }; - yield return new object[] { "en-SS", DayOfWeek.Monday }; - yield return new object[] { "en-SX", DayOfWeek.Monday }; - yield return new object[] { "en-SZ", DayOfWeek.Monday }; - yield return new object[] { "en-TC", DayOfWeek.Monday }; - yield return new object[] { "en-TK", DayOfWeek.Monday }; - yield return new object[] { "en-TO", DayOfWeek.Monday }; - yield return new object[] { "en-TT", DayOfWeek.Sunday }; - yield return new object[] { "en-TV", DayOfWeek.Monday }; - yield return new object[] { "en-TZ", DayOfWeek.Monday }; - yield return new object[] { "en-UG", DayOfWeek.Monday }; - yield return new object[] { "en-UM", DayOfWeek.Sunday }; - yield return new object[] { "en-VC", DayOfWeek.Monday }; - yield return new object[] { "en-VG", DayOfWeek.Monday }; - yield return new object[] { "en-VI", DayOfWeek.Sunday }; - yield return new object[] { "en-VU", DayOfWeek.Monday }; - yield return new object[] { "en-WS", DayOfWeek.Sunday }; - yield return new object[] { "en-ZA", DayOfWeek.Sunday }; - yield return new object[] { "en-ZM", DayOfWeek.Monday }; - yield return new object[] { "en-ZW", DayOfWeek.Sunday }; - yield return new object[] { "en-US", DayOfWeek.Sunday }; - yield return new object[] { "es-419", DayOfWeek.Monday }; - yield return new object[] { "es-ES", DayOfWeek.Monday }; - yield return new object[] { "es-MX", DayOfWeek.Sunday }; - yield return new object[] { "et-EE", DayOfWeek.Monday }; - yield return new object[] { "fa-IR", DayOfWeek.Saturday }; - yield return new object[] { "fi-FI", DayOfWeek.Monday }; - yield return new object[] { "fil-PH", DayOfWeek.Sunday }; - yield return new object[] { "fr-BE", DayOfWeek.Monday }; - yield return new object[] { "fr-CA", DayOfWeek.Sunday }; - yield return new object[] { "fr-CH", DayOfWeek.Monday }; - yield return new object[] { "fr-FR", DayOfWeek.Monday }; - yield return new object[] { "gu-IN", DayOfWeek.Sunday }; - yield return new object[] { "he-IL", DayOfWeek.Sunday }; - yield return new object[] { "hi-IN", DayOfWeek.Sunday }; - yield return new object[] { "hr-BA", DayOfWeek.Monday }; - yield return new object[] { "hr-HR", DayOfWeek.Monday }; - yield return new object[] { "hu-HU", DayOfWeek.Monday }; - yield return new object[] { "id-ID", DayOfWeek.Sunday }; - yield return new object[] { "it-CH", DayOfWeek.Monday }; - yield return new object[] { "it-IT", DayOfWeek.Monday }; - yield return new object[] { "ja-JP", DayOfWeek.Sunday }; - yield return new object[] { "kn-IN", DayOfWeek.Sunday }; - yield return new object[] { "ko-KR", DayOfWeek.Sunday }; - yield return new object[] { "lt-LT", DayOfWeek.Monday }; - yield return new object[] { "lv-LV", DayOfWeek.Monday }; - yield return new object[] { "ml-IN", DayOfWeek.Sunday }; - yield return new object[] { "mr-IN", DayOfWeek.Sunday }; - yield return new object[] { "ms-BN", DayOfWeek.Monday }; - yield return new object[] { "ms-MY", DayOfWeek.Monday }; - yield return new object[] { "ms-SG", DayOfWeek.Sunday }; - yield return new object[] { "nb-NO", DayOfWeek.Monday }; - yield return new object[] { "no", DayOfWeek.Monday }; - yield return new object[] { "no-NO", DayOfWeek.Monday }; - yield return new object[] { "nl-AW", DayOfWeek.Monday }; - yield return new object[] { "nl-BE", DayOfWeek.Monday }; - yield return new object[] { "nl-NL", DayOfWeek.Monday }; - yield return new object[] { "pl-PL", DayOfWeek.Monday }; - yield return new object[] { "pt-BR", DayOfWeek.Sunday }; - yield return new object[] { "pt-PT", DayOfWeek.Sunday }; - yield return new object[] { "ro-RO", DayOfWeek.Monday }; - yield return new object[] { "ru-RU", DayOfWeek.Monday }; - yield return new object[] { "sk-SK", DayOfWeek.Monday }; - yield return new object[] { "sl-SI", DayOfWeek.Monday }; - yield return new object[] { "sr-Cyrl-RS", DayOfWeek.Monday }; - yield return new object[] { "sr-Latn-RS", DayOfWeek.Monday }; - yield return new object[] { "sv-AX", DayOfWeek.Monday }; - yield return new object[] { "sv-SE", DayOfWeek.Monday }; - yield return new object[] { "sw-CD", DayOfWeek.Monday }; - yield return new object[] { "sw-KE", DayOfWeek.Sunday }; - yield return new object[] { "sw-TZ", DayOfWeek.Monday }; - yield return new object[] { "sw-UG", DayOfWeek.Monday }; - yield return new object[] { "ta-IN", DayOfWeek.Sunday }; - yield return new object[] { "ta-LK", DayOfWeek.Monday }; - yield return new object[] { "ta-MY", DayOfWeek.Monday }; - yield return new object[] { "ta-SG", DayOfWeek.Sunday }; - yield return new object[] { "te-IN", DayOfWeek.Sunday }; - yield return new object[] { "th-TH", DayOfWeek.Sunday }; - yield return new object[] { "tr-CY", DayOfWeek.Monday }; - yield return new object[] { "tr-TR", DayOfWeek.Monday }; - yield return new object[] { "uk-UA", DayOfWeek.Monday }; - yield return new object[] { "vi-VN", DayOfWeek.Monday }; - yield return new object[] { "zh-CN", DayOfWeek.Monday }; - yield return new object[] { "zh-Hans-HK", DayOfWeek.Sunday }; - yield return new object[] { "zh-SG", DayOfWeek.Sunday }; - yield return new object[] { "zh-HK", DayOfWeek.Sunday }; - yield return new object[] { "zh-TW", DayOfWeek.Sunday }; - } - [Theory] [MemberData(nameof(FirstDayOfWeek_Get_TestData))] public void FirstDayOfWeek(DateTimeFormatInfo format, DayOfWeek expected, string cultureName) @@ -213,14 +22,6 @@ public void FirstDayOfWeek(DateTimeFormatInfo format, DayOfWeek expected, string Assert.True(expected == format.FirstDayOfWeek, $"Failed for culture: {cultureName}. Expected: {expected}, Actual: {format.FirstDayOfWeek}"); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] - [MemberData(nameof(FirstDayOfWeek_Get_TestData_HybridGlobalization))] - public void FirstDayOfWeekHybridGlobalization(string culture, DayOfWeek expected) - { - DateTimeFormatInfo format = new CultureInfo(culture).DateTimeFormat; - FirstDayOfWeek(format, expected, culture); - } - [Theory] [InlineData(DayOfWeek.Sunday)] [InlineData(DayOfWeek.Monday)] diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoFullDateTimePattern.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoFullDateTimePattern.cs index f84360d35dee9e..fa95f525304265 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoFullDateTimePattern.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoFullDateTimePattern.cs @@ -24,204 +24,6 @@ public static IEnumerable FullDateTimePattern_Set_TestData() yield return new object[] { "HH:mm:ss dddd, dd MMMM yyyy" }; } - public static IEnumerable FullDateTimePattern_Get_TestData_HybridGlobalization() - { - yield return new object[] { new CultureInfo("ar-SA").DateTimeFormat, "dddd، d MMMM yyyy h:mm:ss tt" }; // dddd، d MMMM yyyy g h:mm:ss tt - yield return new object[] { new CultureInfo("am-ET").DateTimeFormat, "yyyy MMMM d, dddd h:mm:ss tt" }; - yield return new object[] { new CultureInfo("bg-BG").DateTimeFormat, PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "dddd, d MMMM yyyy 'г'. H:mm:ss ч." : "dddd, d MMMM yyyy 'г'. H:mm:ss" }; // dddd, d MMMM yyyy 'г'. H:mm:ss - yield return new object[] { new CultureInfo("bn-BD").DateTimeFormat, "dddd, d MMMM, yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("bn-IN").DateTimeFormat, "dddd, d MMMM, yyyy h:mm:ss tt" }; - string catalanPattern = PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "dddd, d 'de' MMMM 'de' yyyy H:mm:ss" : "dddd, d 'de' MMMM 'del' yyyy H:mm:ss"; // dddd, d MMMM 'de' yyyy H:mm:ss - yield return new object[] { new CultureInfo("ca-AD").DateTimeFormat, catalanPattern }; - yield return new object[] { new CultureInfo("ca-ES").DateTimeFormat, catalanPattern }; - yield return new object[] { new CultureInfo("cs-CZ").DateTimeFormat, "dddd d. MMMM yyyy H:mm:ss" }; - yield return new object[] { new CultureInfo("da-DK").DateTimeFormat, "dddd 'den' d. MMMM yyyy HH.mm.ss" }; - yield return new object[] { new CultureInfo("de-AT").DateTimeFormat, "dddd, d. MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("de-BE").DateTimeFormat, "dddd, d. MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("de-CH").DateTimeFormat, "dddd, d. MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("de-DE").DateTimeFormat, "dddd, d. MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("de-IT").DateTimeFormat, "dddd, d. MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("de-LI").DateTimeFormat, "dddd, d. MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("de-LU").DateTimeFormat, "dddd, d. MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("el-CY").DateTimeFormat, "dddd d MMMM yyyy h:mm:ss tt" }; // dddd, d MMMM yyyy h:mm:ss tt - yield return new object[] { new CultureInfo("el-GR").DateTimeFormat, "dddd d MMMM yyyy h:mm:ss tt" }; // dddd, d MMMM yyyy h:mm:ss tt - yield return new object[] { new CultureInfo("en-AE").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-AG").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-AI").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-AS").DateTimeFormat, "dddd, MMMM d, yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-AT").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-AU").DateTimeFormat, PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "dddd, d MMMM yyyy h:mm:ss tt" : "dddd d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-BB").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-BE").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-BI").DateTimeFormat, "dddd, MMMM d, yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-BM").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-BS").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-BW").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; // dddd, dd MMMM yyyy HH:mm:ss - yield return new object[] { new CultureInfo("en-BZ").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; // dddd, dd MMMM yyyy HH:mm:ss - yield return new object[] { new CultureInfo("en-CA").DateTimeFormat, "dddd, MMMM d, yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-CC").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-CH").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-CK").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-CM").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-CX").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-CY").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-DE").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-DK").DateTimeFormat, "dddd, d MMMM yyyy HH.mm.ss" }; - yield return new object[] { new CultureInfo("en-DM").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-ER").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-FI").DateTimeFormat, "dddd, d MMMM yyyy H.mm.ss" }; - yield return new object[] { new CultureInfo("en-FJ").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-FK").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-FM").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-GB").DateTimeFormat, PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "dddd, d MMMM yyyy HH:mm:ss" : "dddd d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-GD").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-GG").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-GH").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-GI").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-GM").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-GU").DateTimeFormat, "dddd, MMMM d, yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-GY").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-HK").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-IE").DateTimeFormat, "dddd d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-IL").DateTimeFormat, "dddd, d MMMM yyyy H:mm:ss" }; - yield return new object[] { new CultureInfo("en-IM").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-IN").DateTimeFormat, PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "dddd, d MMMM, yyyy h:mm:ss tt" : "dddd d MMMM, yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-IO").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-JE").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-JM").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-KE").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-KI").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-KN").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-KY").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-LC").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-LR").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-LS").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-MG").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-MH").DateTimeFormat, "dddd, MMMM d, yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-MO").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-MP").DateTimeFormat, "dddd, MMMM d, yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-MS").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-MT").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-MU").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-MW").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-MY").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-NA").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-NF").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-NG").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-NL").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-NR").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-NU").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-NZ").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-PG").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-PH").DateTimeFormat, "dddd, MMMM d, yyyy h:mm:ss tt" }; // dddd, d MMMM yyyy h:mm:ss tt - yield return new object[] { new CultureInfo("en-PK").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-PN").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-PR").DateTimeFormat, "dddd, MMMM d, yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-PW").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-RW").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-SB").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-SC").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-SD").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-SE").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-SG").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-SH").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-SI").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-SL").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-SS").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-SX").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-SZ").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-TC").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-TK").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-TO").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-TT").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-TV").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-TZ").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-UG").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-UM").DateTimeFormat, "dddd, MMMM d, yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-US").DateTimeFormat, "dddd, MMMM d, yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-VC").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-VG").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-VI").DateTimeFormat, "dddd, MMMM d, yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-VU").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-WS").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-ZA").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; // dddd, dd MMMM yyyy HH:mm:ss - yield return new object[] { new CultureInfo("en-ZM").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-ZW").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; // dddd, dd MMMM yyyy HH:mm:ss - string latinAmericaSpanishFormat = PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "dddd, d 'de' MMMM 'de' yyyy HH:mm:ss" : "dddd, d 'de' MMMM 'de' yyyy h:mm:ss tt"; // dddd, d 'de' MMMM 'de' yyyy HH:mm:ss - yield return new object[] { new CultureInfo("es-419").DateTimeFormat, latinAmericaSpanishFormat }; - yield return new object[] { new CultureInfo("es-ES").DateTimeFormat, "dddd, d 'de' MMMM 'de' yyyy H:mm:ss" }; - yield return new object[] { new CultureInfo("es-MX").DateTimeFormat, latinAmericaSpanishFormat }; - yield return new object[] { new CultureInfo("et-EE").DateTimeFormat, "dddd, d. MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("fa-IR").DateTimeFormat, "yyyy MMMM d, dddd H:mm:ss" }; - yield return new object[] { new CultureInfo("fi-FI").DateTimeFormat, "dddd d. MMMM yyyy H.mm.ss" }; - yield return new object[] { new CultureInfo("fil-PH").DateTimeFormat, "dddd, MMMM d, yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("fr-BE").DateTimeFormat, "dddd d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("fr-CA").DateTimeFormat, "dddd d MMMM yyyy HH 'h' mm 'min' ss 's'" }; - yield return new object[] { new CultureInfo("fr-CH").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("fr-FR").DateTimeFormat, "dddd d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("gu-IN").DateTimeFormat, "dddd, d MMMM, yyyy hh:mm:ss tt" }; - yield return new object[] { new CultureInfo("he-IL").DateTimeFormat, "dddd, d בMMMM yyyy H:mm:ss" }; - yield return new object[] { new CultureInfo("hi-IN").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("hr-BA").DateTimeFormat, "dddd, d. MMMM yyyy. HH:mm:ss" }; - yield return new object[] { new CultureInfo("hr-HR").DateTimeFormat, "dddd, d. MMMM yyyy. HH:mm:ss" }; - yield return new object[] { new CultureInfo("hu-HU").DateTimeFormat, "yyyy. MMMM d., dddd H:mm:ss" }; - yield return new object[] { new CultureInfo("id-ID").DateTimeFormat, "dddd, d MMMM yyyy HH.mm.ss" }; // dddd, dd MMMM yyyy HH.mm.ss - yield return new object[] { new CultureInfo("it-CH").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("it-IT").DateTimeFormat, "dddd d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("ja-JP").DateTimeFormat, "yyyy年M月d日dddd H:mm:ss" }; - yield return new object[] { new CultureInfo("kn-IN").DateTimeFormat, "dddd, MMMM d, yyyy hh:mm:ss tt" }; - yield return new object[] { new CultureInfo("ko-KR").DateTimeFormat, "yyyy년 M월 d일 dddd tt h:mm:ss" }; - yield return new object[] { new CultureInfo("lt-LT").DateTimeFormat, "yyyy 'm'. MMMM d 'd'., dddd HH:mm:ss" }; - yield return new object[] { new CultureInfo("lv-LV").DateTimeFormat, "dddd, yyyy. 'gada' d. MMMM HH:mm:ss" }; - yield return new object[] { new CultureInfo("ml-IN").DateTimeFormat, "yyyy, MMMM d, dddd h:mm:ss tt" }; - yield return new object[] { new CultureInfo("mr-IN").DateTimeFormat, "dddd, d MMMM, yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("ms-BN").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; // dd MMMM yyyy h:mm:ss tt - yield return new object[] { new CultureInfo("ms-MY").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("ms-SG").DateTimeFormat, "dddd, d MMMM yyyy h:mm:ss tt" }; - yield return new object[] { new CultureInfo("nb-NO").DateTimeFormat, "dddd d. MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("no-NO").DateTimeFormat, "dddd d. MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("nl-AW").DateTimeFormat, "dddd d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("nl-BE").DateTimeFormat, "dddd d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("nl-NL").DateTimeFormat, "dddd d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("pl-PL").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("pt-BR").DateTimeFormat, "dddd, d 'de' MMMM 'de' yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("pt-PT").DateTimeFormat, "dddd, d 'de' MMMM 'de' yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("ro-RO").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("ru-RU").DateTimeFormat, "dddd, d MMMM yyyy 'г'. HH:mm:ss" }; - yield return new object[] { new CultureInfo("sk-SK").DateTimeFormat, "dddd d. MMMM yyyy H:mm:ss" }; - yield return new object[] { new CultureInfo("sl-SI").DateTimeFormat, "dddd, d. MMMM yyyy HH:mm:ss" }; // dddd, dd. MMMM yyyy HH:mm:ss - yield return new object[] { new CultureInfo("sr-Cyrl-RS").DateTimeFormat, "dddd, d. MMMM yyyy. HH:mm:ss" }; // dddd, dd. MMMM yyyy. HH:mm:ss - yield return new object[] { new CultureInfo("sr-Latn-RS").DateTimeFormat, "dddd, d. MMMM yyyy. HH:mm:ss" }; // dddd, dd. MMMM yyyy. HH:mm:ss - yield return new object[] { new CultureInfo("sv-AX").DateTimeFormat, "dddd d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("sv-SE").DateTimeFormat, "dddd d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("sw-CD").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("sw-KE").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("sw-TZ").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("sw-UG").DateTimeFormat, "dddd, d MMMM yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("ta-IN").DateTimeFormat, "dddd, d MMMM, yyyy tt h:mm:ss" }; - yield return new object[] { new CultureInfo("ta-LK").DateTimeFormat, "dddd, d MMMM, yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("ta-MY").DateTimeFormat, "dddd, d MMMM, yyyy tt h:mm:ss" }; - yield return new object[] { new CultureInfo("ta-SG").DateTimeFormat, "dddd, d MMMM, yyyy tt h:mm:ss" }; - yield return new object[] { new CultureInfo("te-IN").DateTimeFormat, "d, MMMM yyyy, dddd h:mm:ss tt" }; - yield return new object[] { new CultureInfo("th-TH").DateTimeFormat, "ddddที่ d MMMM g yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("tr-CY").DateTimeFormat, "d MMMM yyyy dddd h:mm:ss tt" }; - yield return new object[] { new CultureInfo("tr-TR").DateTimeFormat, "d MMMM yyyy dddd HH:mm:ss" }; - yield return new object[] { new CultureInfo("uk-UA").DateTimeFormat, "dddd, d MMMM yyyy 'р'. HH:mm:ss" }; - yield return new object[] { new CultureInfo("vi-VN").DateTimeFormat, "dddd, d MMMM, yyyy HH:mm:ss" }; - yield return new object[] { new CultureInfo("zh-CN").DateTimeFormat, "yyyy年M月d日dddd HH:mm:ss" }; // yyyy年M月d日dddd tth:mm:ss - yield return new object[] { new CultureInfo("zh-Hans-HK").DateTimeFormat, "yyyy年M月d日dddd tth:mm:ss" }; - yield return new object[] { new CultureInfo("zh-SG").DateTimeFormat, "yyyy年M月d日dddd tth:mm:ss" }; - yield return new object[] { new CultureInfo("zh-HK").DateTimeFormat, "yyyy年M月d日dddd tth:mm:ss" }; - yield return new object[] { new CultureInfo("zh-TW").DateTimeFormat, "yyyy年M月d日 dddd tth:mm:ss" }; - } - - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] - [MemberData(nameof(FullDateTimePattern_Get_TestData_HybridGlobalization))] - public void FullDateTimePattern_Get_ReturnsExpected_HybridGlobalization(DateTimeFormatInfo format, string value) - { - Assert.Equal(value, format.FullDateTimePattern); - } - [Theory] [MemberData(nameof(FullDateTimePattern_Set_TestData))] public void FullDateTimePattern_Set_GetReturnsExpected(string value) diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoGetAbbreviatedEraName.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoGetAbbreviatedEraName.cs index 18de097fff17ee..bff773b9038c2f 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoGetAbbreviatedEraName.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoGetAbbreviatedEraName.cs @@ -15,197 +15,6 @@ public static IEnumerable GetAbbreviatedEraName_TestData() yield return new object[] { "invariant", 0, "AD" }; yield return new object[] { "invariant", 1, "AD" }; yield return new object[] { "ja-JP", 1, DateTimeFormatInfoData.JaJPAbbreviatedEraName() }; - - if (PlatformDetection.IsHybridGlobalizationOnBrowser) - { - // see the comments on the right to check the non-Hybrid result, if it differs - yield return new object[] { "ar-SA", 1, "هـ" }; - yield return new object[] { "am-ET", 1, "ዓ/ም" }; - yield return new object[] { "bg-BG", 1, "сл.Хр." }; - yield return new object[] { "bn-BD", 1, "খৃষ্টাব্দ" }; - yield return new object[] { "bn-IN", 1, "খ্রিঃ" }; // "খৃষ্টাব্দ" - yield return new object[] { "ca-AD", 1, "dC" }; - yield return new object[] { "ca-ES", 1, "dC" }; - yield return new object[] { "cs-CZ", 1, "n.l." }; - yield return new object[] { "da-DK", 1, "eKr" }; - yield return new object[] { "de-AT", 1, "n. Chr." }; - yield return new object[] { "de-BE", 1, "n. Chr." }; - yield return new object[] { "de-CH", 1, "n. Chr." }; - yield return new object[] { "de-DE", 1, "n. Chr." }; - yield return new object[] { "de-IT", 1, "n. Chr." }; - yield return new object[] { "de-LI", 1, "n. Chr." }; - yield return new object[] { "de-LU", 1, "n. Chr." }; - yield return new object[] { "el-CY", 1, "μ.Χ." }; - yield return new object[] { "el-GR", 1, "μ.Χ." }; - yield return new object[] { "en-AE", 1, "A" }; // AD - yield return new object[] { "en-AG", 1, "A" }; // AD - yield return new object[] { "en-AI", 1, "A" }; // AD - yield return new object[] { "en-AS", 1, "A" }; - yield return new object[] { "en-AT", 1, "A" }; // AD - yield return new object[] { "en-AU", 1, "A" }; // AD - yield return new object[] { "en-BB", 1, "A" }; // AD - yield return new object[] { "en-BE", 1, "A" }; // AD - yield return new object[] { "en-BI", 1, "A" }; // AD - yield return new object[] { "en-BM", 1, "A" }; // AD - yield return new object[] { "en-BS", 1, "A" }; // AD - yield return new object[] { "en-BW", 1, "A" }; // AD - yield return new object[] { "en-BZ", 1, "A" }; // AD - yield return new object[] { "en-CA", 1, "A" }; // AD - yield return new object[] { "en-CC", 1, "A" }; // AD - yield return new object[] { "en-CH", 1, "A" }; // AD - yield return new object[] { "en-CK", 1, "A" }; // AD - yield return new object[] { "en-CM", 1, "A" }; // AD - yield return new object[] { "en-CX", 1, "A" }; // AD - yield return new object[] { "en-CY", 1, "A" }; // AD - yield return new object[] { "en-DE", 1, "A" }; // AD - yield return new object[] { "en-DK", 1, "A" }; // AD - yield return new object[] { "en-DM", 1, "A" }; // AD - yield return new object[] { "en-ER", 1, "A" }; // AD - yield return new object[] { "en-FI", 1, "A" }; // AD - yield return new object[] { "en-FJ", 1, "A" }; // AD - yield return new object[] { "en-FK", 1, "A" }; // AD - yield return new object[] { "en-FM", 1, "A" }; // AD - yield return new object[] { "en-GB", 1, "A" }; // AD - yield return new object[] { "en-GD", 1, "A" }; // AD - yield return new object[] { "en-GG", 1, "A" }; // AD - yield return new object[] { "en-GH", 1, "A" }; // AD - yield return new object[] { "en-GI", 1, "A" }; // AD - yield return new object[] { "en-GM", 1, "A" }; // AD - yield return new object[] { "en-GU", 1, "A" }; - yield return new object[] { "en-GY", 1, "A" }; // AD - yield return new object[] { "en-HK", 1, "A" }; // AD - yield return new object[] { "en-IE", 1, "A" }; // AD - yield return new object[] { "en-IL", 1, "A" }; // AD - yield return new object[] { "en-IM", 1, "A" }; // AD - yield return new object[] { "en-IN", 1, "A" }; // AD - yield return new object[] { "en-IO", 1, "A" }; // AD - yield return new object[] { "en-JE", 1, "A" }; // AD - yield return new object[] { "en-JM", 1, "A" }; // AD - yield return new object[] { "en-KE", 1, "A" }; // AD - yield return new object[] { "en-KI", 1, "A" }; // AD - yield return new object[] { "en-KN", 1, "A" }; // AD - yield return new object[] { "en-KY", 1, "A" }; // AD - yield return new object[] { "en-LC", 1, "A" }; // AD - yield return new object[] { "en-LR", 1, "A" }; // AD - yield return new object[] { "en-LS", 1, "A" }; // AD - yield return new object[] { "en-MG", 1, "A" }; // AD - yield return new object[] { "en-MH", 1, "A" }; - yield return new object[] { "en-MO", 1, "A" }; // AD - yield return new object[] { "en-MP", 1, "A" }; - yield return new object[] { "en-MS", 1, "A" }; // AD - yield return new object[] { "en-MT", 1, "A" }; // AD - yield return new object[] { "en-MU", 1, "A" }; // AD - yield return new object[] { "en-MW", 1, "A" }; // AD - yield return new object[] { "en-MY", 1, "A" }; // AD - yield return new object[] { "en-NA", 1, "A" }; // AD - yield return new object[] { "en-NF", 1, "A" }; // AD - yield return new object[] { "en-NG", 1, "A" }; // AD - yield return new object[] { "en-NL", 1, "A" }; // AD - yield return new object[] { "en-NR", 1, "A" }; // AD - yield return new object[] { "en-NU", 1, "A" }; // AD - yield return new object[] { "en-NZ", 1, "A" }; // AD - yield return new object[] { "en-PG", 1, "A" }; // AD - yield return new object[] { "en-PH", 1, "A" }; // AD - yield return new object[] { "en-PK", 1, "A" }; // AD - yield return new object[] { "en-PN", 1, "A" }; // AD - yield return new object[] { "en-PR", 1, "A" }; - yield return new object[] { "en-PW", 1, "A" }; // AD - yield return new object[] { "en-RW", 1, "A" }; // AD - yield return new object[] { "en-SB", 1, "A" }; // AD - yield return new object[] { "en-SC", 1, "A" }; // AD - yield return new object[] { "en-SD", 1, "A" }; // AD - yield return new object[] { "en-SE", 1, "A" }; // AD - yield return new object[] { "en-SG", 1, "A" }; // AD - yield return new object[] { "en-SH", 1, "A" }; // AD - yield return new object[] { "en-SI", 1, "A" }; // AD - yield return new object[] { "en-SL", 1, "A" }; // AD - yield return new object[] { "en-SS", 1, "A" }; // AD - yield return new object[] { "en-SX", 1, "A" }; // AD - yield return new object[] { "en-SZ", 1, "A" }; // AD - yield return new object[] { "en-TC", 1, "A" }; // AD - yield return new object[] { "en-TK", 1, "A" }; // AD - yield return new object[] { "en-TO", 1, "A" }; // AD - yield return new object[] { "en-TT", 1, "A" }; // AD - yield return new object[] { "en-TV", 1, "A" }; // AD - yield return new object[] { "en-TZ", 1, "A" }; // AD - yield return new object[] { "en-UG", 1, "A" }; // AD - yield return new object[] { "en-UM", 1, "A" }; - yield return new object[] { "en-US", 1, "A" }; - yield return new object[] { "en-VC", 1, "A" }; // AD - yield return new object[] { "en-VG", 1, "A" }; // AD - yield return new object[] { "en-VI", 1, "A" }; - yield return new object[] { "en-VU", 1, "A" }; // AD - yield return new object[] { "en-WS", 1, "A" }; // AD - yield return new object[] { "en-ZA", 1, "A" }; // AD - yield return new object[] { "en-ZM", 1, "A" }; // AD - yield return new object[] { "en-ZW", 1, "A" }; // AD - yield return new object[] { "es-ES", 1, "d. C." }; - yield return new object[] { "es-419", 1, "d.C." }; // "d. C." - yield return new object[] { "es-MX", 1, "d.C." }; // "d. C." - yield return new object[] { "et-EE", 1, "pKr" }; - yield return new object[] { "fa-IR", 1, "ه.ش" }; // ه‍.ش. - yield return new object[] { "fi-FI", 1, "jKr" }; - yield return new object[] { "fil-PH", 1, "AD" }; - yield return new object[] { "fr-BE", 1, "ap. J.-C." }; - yield return new object[] { "fr-CA", 1, "ap. J.-C." }; - yield return new object[] { "fr-CH", 1, "ap. J.-C." }; - yield return new object[] { "fr-FR", 1, "ap. J.-C." }; - yield return new object[] { "gu-IN", 1, "ઇસ" }; - yield return new object[] { "he-IL", 1, "אחריי" }; // לספירה - yield return new object[] { "hi-IN", 1, "ईस्वी" }; - yield return new object[] { "hr-BA", 1, "AD" }; // po. Kr. - yield return new object[] { "hr-HR", 1, "AD" }; - yield return new object[] { "hu-HU", 1, "isz." }; - yield return new object[] { "id-ID", 1, "M" }; - yield return new object[] { "it-CH", 1, "dC" }; // d.C. - yield return new object[] { "it-IT", 1, "dC" }; - yield return new object[] { "ja-JP", 1, "AD" }; - yield return new object[] { "kn-IN", 1, "ಕ್ರಿ.ಶ" }; - yield return new object[] { "ko-KR", 1, "AD" }; - yield return new object[] { "lt-LT", 1, "po Kr." }; - yield return new object[] { "lv-LV", 1, "m.ē." }; - yield return new object[] { "ml-IN", 1, "എഡി" }; - yield return new object[] { "mr-IN", 1, "इ. स." }; - yield return new object[] { "ms-BN", 1, "TM" }; - yield return new object[] { "ms-MY", 1, "TM" }; - yield return new object[] { "ms-SG", 1, "TM" }; - yield return new object[] { "nb-NO", 1, "e.Kr." }; - yield return new object[] { "no", 1, "e.Kr." }; - yield return new object[] { "no-NO", 1, "e.Kr." }; - yield return new object[] { "nl-AW", 1, "n.C." }; - yield return new object[] { "nl-BE", 1, "n.C." }; // n.Chr. - yield return new object[] { "nl-NL", 1, "n.C." }; - yield return new object[] { "pl-PL", 1, "n.e." }; - yield return new object[] { "pt-BR", 1, "d.C." }; - yield return new object[] { "pt-PT", 1, "d.C." }; - yield return new object[] { "ro-RO", 1, "d.Hr." }; - yield return new object[] { "ru-RU", 1, "н.э." }; - yield return new object[] { "sk-SK", 1, "po Kr." }; - yield return new object[] { "sl-SI", 1, "po Kr." }; - yield return new object[] { "sr-Cyrl-RS", 1, "н.е." }; - yield return new object[] { "sr-Latn-RS", 1, "n.e." }; - yield return new object[] { "sv-AX", 1, "e.Kr." }; - yield return new object[] { "sv-SE", 1, "e.Kr." }; - yield return new object[] { "sw-CD", 1, "BK" }; - yield return new object[] { "sw-KE", 1, "BK" }; - yield return new object[] { "sw-TZ", 1, "BK" }; - yield return new object[] { "sw-UG", 1, "BK" }; - yield return new object[] { "ta-IN", 1, "கி.பி." }; - yield return new object[] { "ta-LK", 1, "கி.பி." }; - yield return new object[] { "ta-MY", 1, "கி.பி." }; - yield return new object[] { "ta-SG", 1, "கி.பி." }; - yield return new object[] { "te-IN", 1, "క్రీశ" }; - yield return new object[] { "th-TH", 1, "พ.ศ." }; - yield return new object[] { "tr-CY", 1, "MS" }; - yield return new object[] { "tr-TR", 1, "MS" }; - yield return new object[] { "uk-UA", 1, "н.е." }; - yield return new object[] { "vi-VN", 1, PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "sau CN" : "CN" }; // sau CN - yield return new object[] { "zh-CN", 1, "公元" }; - yield return new object[] { "zh-Hans-HK", 1, "公元" }; - yield return new object[] { "zh-SG", 1, "公元" }; - yield return new object[] { "zh-HK", 1, "公元" }; - yield return new object[] { "zh-TW", 1, "西元" }; - } } [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform))] diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoGetEraName.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoGetEraName.cs index c007c7e11059a0..dd47204ea5d2a1 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoGetEraName.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoGetEraName.cs @@ -12,394 +12,10 @@ public static IEnumerable GetEraName_TestData() { yield return new object[] { "invariant", 1, "A.D." }; yield return new object[] { "invariant", 0, "A.D." }; - - if (!PlatformDetection.IsHybridGlobalizationOnBrowser) - { - var enUS = "en-US"; - yield return new object[] { enUS, 1, DateTimeFormatInfoData.EnUSEraName() }; - yield return new object[] { enUS, 0, DateTimeFormatInfoData.EnUSEraName() }; - - var frFR = "fr-FR"; - yield return new object[] { frFR, 1, "ap. J.-C." }; - yield return new object[] { frFR, 0, "ap. J.-C." }; - } - else - { - yield return new object[] { "ar-SA", 0, "بعد الهجرة" }; - yield return new object[] { "ar-SA", 1, "بعد الهجرة" }; - yield return new object[] { "am-ET", 0, "ዓ/ም" }; - yield return new object[] { "am-ET", 1, "ዓ/ም" }; - yield return new object[] { "bg-BG", 0, "сл.Хр." }; - yield return new object[] { "bg-BG", 1, "сл.Хр." }; - yield return new object[] { "bn-BD", 0, "খৃষ্টাব্দ" }; - yield return new object[] { "bn-BD", 1, "খৃষ্টাব্দ" }; - yield return new object[] { "bn-IN", 0, "খ্রিঃ" }; - yield return new object[] { "bn-IN", 1, "খ্রিঃ" }; - yield return new object[] { "ca-AD", 0, "dC" }; - yield return new object[] { "ca-AD", 1, "dC" }; - yield return new object[] { "ca-ES", 0, "dC" }; - yield return new object[] { "ca-ES", 1, "dC" }; - yield return new object[] { "cs-CZ", 0, "n. l." }; - yield return new object[] { "cs-CZ", 1, "n. l." }; - yield return new object[] { "da-DK", 0, "e.Kr." }; - yield return new object[] { "da-DK", 1, "e.Kr." }; - yield return new object[] { "de-AT", 0, "n. Chr." }; - yield return new object[] { "de-AT", 1, "n. Chr." }; - yield return new object[] { "de-BE", 0, "n. Chr." }; - yield return new object[] { "de-BE", 1, "n. Chr." }; - yield return new object[] { "de-CH", 0, "n. Chr." }; - yield return new object[] { "de-CH", 1, "n. Chr." }; - yield return new object[] { "de-DE", 0, "n. Chr." }; - yield return new object[] { "de-DE", 1, "n. Chr." }; - yield return new object[] { "de-IT", 0, "n. Chr." }; - yield return new object[] { "de-IT", 1, "n. Chr." }; - yield return new object[] { "de-LI", 0, "n. Chr." }; - yield return new object[] { "de-LI", 1, "n. Chr." }; - yield return new object[] { "de-LU", 0, "n. Chr." }; - yield return new object[] { "de-LU", 1, "n. Chr." }; - yield return new object[] { "el-CY", 0, "μ.Χ." }; - yield return new object[] { "el-CY", 1, "μ.Χ." }; - yield return new object[] { "el-GR", 0, "μ.Χ." }; - yield return new object[] { "el-GR", 1, "μ.Χ." }; - yield return new object[] { "en-AE", 0, "AD" }; - yield return new object[] { "en-AE", 1, "AD" }; - yield return new object[] { "en-AG", 0, "AD" }; - yield return new object[] { "en-AG", 1, "AD" }; - yield return new object[] { "en-AI", 0, "AD" }; - yield return new object[] { "en-AI", 1, "AD" }; - yield return new object[] { "en-AS", 0, "AD" }; - yield return new object[] { "en-AS", 1, "AD" }; - yield return new object[] { "en-AT", 0, "AD" }; - yield return new object[] { "en-AT", 1, "AD" }; - yield return new object[] { "en-AU", 0, "AD" }; - yield return new object[] { "en-AU", 1, "AD" }; - yield return new object[] { "en-BB", 0, "AD" }; - yield return new object[] { "en-BB", 1, "AD" }; - yield return new object[] { "en-BE", 0, "AD" }; - yield return new object[] { "en-BE", 1, "AD" }; - yield return new object[] { "en-BI", 0, "AD" }; - yield return new object[] { "en-BI", 1, "AD" }; - yield return new object[] { "en-BM", 0, "AD" }; - yield return new object[] { "en-BM", 1, "AD" }; - yield return new object[] { "en-BS", 0, "AD" }; - yield return new object[] { "en-BS", 1, "AD" }; - yield return new object[] { "en-BW", 0, "AD" }; - yield return new object[] { "en-BW", 1, "AD" }; - yield return new object[] { "en-BZ", 0, "AD" }; - yield return new object[] { "en-BZ", 1, "AD" }; - yield return new object[] { "en-CA", 0, "AD" }; - yield return new object[] { "en-CA", 1, "AD" }; - yield return new object[] { "en-CC", 0, "AD" }; - yield return new object[] { "en-CC", 1, "AD" }; - yield return new object[] { "en-CH", 0, "AD" }; - yield return new object[] { "en-CH", 1, "AD" }; - yield return new object[] { "en-CK", 0, "AD" }; - yield return new object[] { "en-CK", 1, "AD" }; - yield return new object[] { "en-CM", 0, "AD" }; - yield return new object[] { "en-CM", 1, "AD" }; - yield return new object[] { "en-CX", 0, "AD" }; - yield return new object[] { "en-CX", 1, "AD" }; - yield return new object[] { "en-CY", 0, "AD" }; - yield return new object[] { "en-CY", 1, "AD" }; - yield return new object[] { "en-DE", 0, "AD" }; - yield return new object[] { "en-DE", 1, "AD" }; - yield return new object[] { "en-DK", 0, "AD" }; - yield return new object[] { "en-DK", 1, "AD" }; - yield return new object[] { "en-DM", 0, "AD" }; - yield return new object[] { "en-DM", 1, "AD" }; - yield return new object[] { "en-ER", 0, "AD" }; - yield return new object[] { "en-ER", 1, "AD" }; - yield return new object[] { "en-FI", 0, "AD" }; - yield return new object[] { "en-FI", 1, "AD" }; - yield return new object[] { "en-FJ", 0, "AD" }; - yield return new object[] { "en-FJ", 1, "AD" }; - yield return new object[] { "en-FK", 0, "AD" }; - yield return new object[] { "en-FK", 1, "AD" }; - yield return new object[] { "en-FM", 0, "AD" }; - yield return new object[] { "en-FM", 1, "AD" }; - yield return new object[] { "en-GB", 0, "AD" }; - yield return new object[] { "en-GB", 1, "AD" }; - yield return new object[] { "en-GD", 0, "AD" }; - yield return new object[] { "en-GD", 1, "AD" }; - yield return new object[] { "en-GG", 0, "AD" }; - yield return new object[] { "en-GG", 1, "AD" }; - yield return new object[] { "en-GH", 0, "AD" }; - yield return new object[] { "en-GH", 1, "AD" }; - yield return new object[] { "en-GI", 0, "AD" }; - yield return new object[] { "en-GI", 1, "AD" }; - yield return new object[] { "en-GM", 0, "AD" }; - yield return new object[] { "en-GM", 1, "AD" }; - yield return new object[] { "en-GU", 0, "AD" }; - yield return new object[] { "en-GU", 1, "AD" }; - yield return new object[] { "en-GY", 0, "AD" }; - yield return new object[] { "en-GY", 1, "AD" }; - yield return new object[] { "en-HK", 0, "AD" }; - yield return new object[] { "en-HK", 1, "AD" }; - yield return new object[] { "en-IE", 0, "AD" }; - yield return new object[] { "en-IE", 1, "AD" }; - yield return new object[] { "en-IL", 0, "AD" }; - yield return new object[] { "en-IL", 1, "AD" }; - yield return new object[] { "en-IM", 0, "AD" }; - yield return new object[] { "en-IM", 1, "AD" }; - yield return new object[] { "en-IN", 0, "AD" }; - yield return new object[] { "en-IN", 1, "AD" }; - yield return new object[] { "en-IO", 0, "AD" }; - yield return new object[] { "en-IO", 1, "AD" }; - yield return new object[] { "en-JE", 0, "AD" }; - yield return new object[] { "en-JE", 1, "AD" }; - yield return new object[] { "en-JM", 0, "AD" }; - yield return new object[] { "en-JM", 1, "AD" }; - yield return new object[] { "en-KE", 0, "AD" }; - yield return new object[] { "en-KE", 1, "AD" }; - yield return new object[] { "en-KI", 0, "AD" }; - yield return new object[] { "en-KI", 1, "AD" }; - yield return new object[] { "en-KN", 0, "AD" }; - yield return new object[] { "en-KN", 1, "AD" }; - yield return new object[] { "en-KY", 0, "AD" }; - yield return new object[] { "en-KY", 1, "AD" }; - yield return new object[] { "en-LC", 0, "AD" }; - yield return new object[] { "en-LC", 1, "AD" }; - yield return new object[] { "en-LR", 0, "AD" }; - yield return new object[] { "en-LR", 1, "AD" }; - yield return new object[] { "en-LS", 0, "AD" }; - yield return new object[] { "en-LS", 1, "AD" }; - yield return new object[] { "en-MG", 0, "AD" }; - yield return new object[] { "en-MG", 1, "AD" }; - yield return new object[] { "en-MH", 0, "AD" }; - yield return new object[] { "en-MH", 1, "AD" }; - yield return new object[] { "en-MO", 0, "AD" }; - yield return new object[] { "en-MO", 1, "AD" }; - yield return new object[] { "en-MP", 0, "AD" }; - yield return new object[] { "en-MP", 1, "AD" }; - yield return new object[] { "en-MS", 0, "AD" }; - yield return new object[] { "en-MS", 1, "AD" }; - yield return new object[] { "en-MT", 0, "AD" }; - yield return new object[] { "en-MT", 1, "AD" }; - yield return new object[] { "en-MU", 0, "AD" }; - yield return new object[] { "en-MU", 1, "AD" }; - yield return new object[] { "en-MW", 0, "AD" }; - yield return new object[] { "en-MW", 1, "AD" }; - yield return new object[] { "en-MY", 0, "AD" }; - yield return new object[] { "en-MY", 1, "AD" }; - yield return new object[] { "en-NA", 0, "AD" }; - yield return new object[] { "en-NA", 1, "AD" }; - yield return new object[] { "en-NF", 0, "AD" }; - yield return new object[] { "en-NF", 1, "AD" }; - yield return new object[] { "en-NG", 0, "AD" }; - yield return new object[] { "en-NG", 1, "AD" }; - yield return new object[] { "en-NL", 0, "AD" }; - yield return new object[] { "en-NL", 1, "AD" }; - yield return new object[] { "en-NR", 0, "AD" }; - yield return new object[] { "en-NR", 1, "AD" }; - yield return new object[] { "en-NU", 0, "AD" }; - yield return new object[] { "en-NU", 1, "AD" }; - yield return new object[] { "en-NZ", 0, "AD" }; - yield return new object[] { "en-NZ", 1, "AD" }; - yield return new object[] { "en-PG", 0, "AD" }; - yield return new object[] { "en-PG", 1, "AD" }; - yield return new object[] { "en-PH", 0, "AD" }; - yield return new object[] { "en-PH", 1, "AD" }; - yield return new object[] { "en-PK", 0, "AD" }; - yield return new object[] { "en-PK", 1, "AD" }; - yield return new object[] { "en-PN", 0, "AD" }; - yield return new object[] { "en-PN", 1, "AD" }; - yield return new object[] { "en-PR", 0, "AD" }; - yield return new object[] { "en-PR", 1, "AD" }; - yield return new object[] { "en-PW", 0, "AD" }; - yield return new object[] { "en-PW", 1, "AD" }; - yield return new object[] { "en-RW", 0, "AD" }; - yield return new object[] { "en-RW", 1, "AD" }; - yield return new object[] { "en-SB", 0, "AD" }; - yield return new object[] { "en-SB", 1, "AD" }; - yield return new object[] { "en-SC", 0, "AD" }; - yield return new object[] { "en-SC", 1, "AD" }; - yield return new object[] { "en-SD", 0, "AD" }; - yield return new object[] { "en-SD", 1, "AD" }; - yield return new object[] { "en-SE", 0, "AD" }; - yield return new object[] { "en-SE", 1, "AD" }; - yield return new object[] { "en-SG", 0, "AD" }; - yield return new object[] { "en-SG", 1, "AD" }; - yield return new object[] { "en-SH", 0, "AD" }; - yield return new object[] { "en-SH", 1, "AD" }; - yield return new object[] { "en-SI", 0, "AD" }; - yield return new object[] { "en-SI", 1, "AD" }; - yield return new object[] { "en-SL", 0, "AD" }; - yield return new object[] { "en-SL", 1, "AD" }; - yield return new object[] { "en-SS", 0, "AD" }; - yield return new object[] { "en-SS", 1, "AD" }; - yield return new object[] { "en-SX", 0, "AD" }; - yield return new object[] { "en-SX", 1, "AD" }; - yield return new object[] { "en-SZ", 0, "AD" }; - yield return new object[] { "en-SZ", 1, "AD" }; - yield return new object[] { "en-TC", 0, "AD" }; - yield return new object[] { "en-TC", 1, "AD" }; - yield return new object[] { "en-TK", 0, "AD" }; - yield return new object[] { "en-TK", 1, "AD" }; - yield return new object[] { "en-TO", 0, "AD" }; - yield return new object[] { "en-TO", 1, "AD" }; - yield return new object[] { "en-TT", 0, "AD" }; - yield return new object[] { "en-TT", 1, "AD" }; - yield return new object[] { "en-TV", 0, "AD" }; - yield return new object[] { "en-TV", 1, "AD" }; - yield return new object[] { "en-TZ", 0, "AD" }; - yield return new object[] { "en-TZ", 1, "AD" }; - yield return new object[] { "en-UG", 0, "AD" }; - yield return new object[] { "en-UG", 1, "AD" }; - yield return new object[] { "en-UM", 0, "AD" }; - yield return new object[] { "en-UM", 1, "AD" }; - yield return new object[] { "en-US", 0, "AD" }; - yield return new object[] { "en-US", 1, "AD" }; - yield return new object[] { "en-VC", 0, "AD" }; - yield return new object[] { "en-VC", 1, "AD" }; - yield return new object[] { "en-VG", 0, "AD" }; - yield return new object[] { "en-VG", 1, "AD" }; - yield return new object[] { "en-VI", 0, "AD" }; - yield return new object[] { "en-VI", 1, "AD" }; - yield return new object[] { "en-VU", 0, "AD" }; - yield return new object[] { "en-VU", 1, "AD" }; - yield return new object[] { "en-WS", 0, "AD" }; - yield return new object[] { "en-WS", 1, "AD" }; - yield return new object[] { "en-ZA", 0, "AD" }; - yield return new object[] { "en-ZA", 1, "AD" }; - yield return new object[] { "en-ZM", 0, "AD" }; - yield return new object[] { "en-ZM", 1, "AD" }; - yield return new object[] { "en-ZW", 0, "AD" }; - yield return new object[] { "en-ZW", 1, "AD" }; - yield return new object[] { "en-US", 0, "AD" }; - yield return new object[] { "en-US", 1, "AD" }; - yield return new object[] { "es-ES", 0, "d. C." }; - yield return new object[] { "es-ES", 1, "d. C." }; - yield return new object[] { "es-419", 0, "d.C." }; - yield return new object[] { "es-419", 1, "d.C." }; - yield return new object[] { "es-MX", 0, "d.C." }; - yield return new object[] { "es-MX", 1, "d.C." }; - yield return new object[] { "et-EE", 0, "pKr" }; - yield return new object[] { "et-EE", 1, "pKr" }; - yield return new object[] { "fa-IR", 0, "ه.ش" }; // ه‍.ش. - yield return new object[] { "fa-IR", 1, "ه.ش" }; - yield return new object[] { "fi-FI", 0, "jKr." }; - yield return new object[] { "fi-FI", 1, "jKr." }; - yield return new object[] { "fil-PH", 0, "AD" }; - yield return new object[] { "fil-PH", 1, "AD" }; - yield return new object[] { "fr-BE", 0, "ap. J.-C." }; - yield return new object[] { "fr-BE", 1, "ap. J.-C." }; - yield return new object[] { "fr-CA", 0, "ap. J.-C." }; - yield return new object[] { "fr-CA", 1, "ap. J.-C." }; - yield return new object[] { "fr-CH", 0, "ap. J.-C." }; - yield return new object[] { "fr-CH", 1, "ap. J.-C." }; - yield return new object[] { "fr-FR", 0, "ap. J.-C." }; - yield return new object[] { "fr-FR", 1, "ap. J.-C." }; - yield return new object[] { "gu-IN", 0, "ઈ.સ." }; - yield return new object[] { "gu-IN", 1, "ઈ.સ." }; - yield return new object[] { "he-IL", 0, "לספירה" }; - yield return new object[] { "he-IL", 1, "לספירה" }; - yield return new object[] { "hi-IN", 0, "ईस्वी" }; - yield return new object[] { "hi-IN", 1, "ईस्वी" }; - yield return new object[] { "hr-BA", 0, "po. Kr." }; - yield return new object[] { "hr-BA", 1, "po. Kr." }; - yield return new object[] { "hr-HR", 0, "po. Kr." }; - yield return new object[] { "hr-HR", 1, "po. Kr." }; - yield return new object[] { "hu-HU", 0, "i. sz." }; - yield return new object[] { "hu-HU", 1, "i. sz." }; - yield return new object[] { "id-ID", 0, "M" }; - yield return new object[] { "id-ID", 1, "M" }; - yield return new object[] { "it-CH", 0, "d.C." }; - yield return new object[] { "it-CH", 1, "d.C." }; - yield return new object[] { "it-IT", 0, "d.C." }; - yield return new object[] { "it-IT", 1, "d.C." }; - yield return new object[] { "ja-JP", 0, "西暦" }; - yield return new object[] { "ja-JP", 1, "西暦" }; - yield return new object[] { "kn-IN", 0, "ಕ್ರಿ.ಶ" }; - yield return new object[] { "kn-IN", 1, "ಕ್ರಿ.ಶ" }; - yield return new object[] { "ko-KR", 0, "AD" }; - yield return new object[] { "ko-KR", 1, "AD" }; - yield return new object[] { "lt-LT", 0, "po Kr." }; - yield return new object[] { "lt-LT", 1, "po Kr." }; - yield return new object[] { "lv-LV", 0, "m.ē." }; - yield return new object[] { "lv-LV", 1, "m.ē." }; - yield return new object[] { "ml-IN", 0, "എഡി" }; - yield return new object[] { "ml-IN", 1, "എഡി" }; - yield return new object[] { "mr-IN", 0, "इ. स." }; - yield return new object[] { "mr-IN", 1, "इ. स." }; - yield return new object[] { "ms-BN", 0, "TM" }; - yield return new object[] { "ms-BN", 1, "TM" }; - yield return new object[] { "ms-MY", 0, "TM" }; - yield return new object[] { "ms-MY", 1, "TM" }; - yield return new object[] { "ms-SG", 0, "TM" }; - yield return new object[] { "ms-SG", 1, "TM" }; - yield return new object[] { "nb-NO", 0, "e.Kr." }; - yield return new object[] { "nb-NO", 1, "e.Kr." }; - yield return new object[] { "no", 0, "e.Kr." }; - yield return new object[] { "no", 1, "e.Kr." }; - yield return new object[] { "no-NO", 0, "e.Kr." }; - yield return new object[] { "no-NO", 1, "e.Kr." }; - yield return new object[] { "nl-AW", 0, "n.Chr." }; - yield return new object[] { "nl-AW", 1, "n.Chr." }; - yield return new object[] { "nl-BE", 0, "n.Chr." }; - yield return new object[] { "nl-BE", 1, "n.Chr." }; - yield return new object[] { "nl-NL", 0, "n.Chr." }; - yield return new object[] { "nl-NL", 1, "n.Chr." }; - yield return new object[] { "pl-PL", 0, "n.e." }; - yield return new object[] { "pl-PL", 1, "n.e." }; - yield return new object[] { "pt-BR", 0, "d.C." }; - yield return new object[] { "pt-BR", 1, "d.C." }; - yield return new object[] { "pt-PT", 0, "d.C." }; - yield return new object[] { "pt-PT", 1, "d.C." }; - yield return new object[] { "ro-RO", 0, "d.Hr." }; - yield return new object[] { "ro-RO", 1, "d.Hr." }; - yield return new object[] { "ru-RU", 0, "н. э." }; - yield return new object[] { "ru-RU", 1, "н. э." }; - yield return new object[] { "sk-SK", 0, "po Kr." }; - yield return new object[] { "sk-SK", 1, "po Kr." }; - yield return new object[] { "sl-SI", 0, "po Kr." }; - yield return new object[] { "sl-SI", 1, "po Kr." }; - yield return new object[] { "sr-Cyrl-RS", 0, "н. е." }; - yield return new object[] { "sr-Cyrl-RS", 1, "н. е." }; - yield return new object[] { "sr-Latn-RS", 0, "n. e." }; - yield return new object[] { "sr-Latn-RS", 1, "n. e." }; - yield return new object[] { "sv-AX", 0, "e.Kr." }; - yield return new object[] { "sv-AX", 1, "e.Kr." }; - yield return new object[] { "sv-SE", 0, "e.Kr." }; - yield return new object[] { "sv-SE", 1, "e.Kr." }; - yield return new object[] { "sw-CD", 0, "BK" }; - yield return new object[] { "sw-CD", 1, "BK" }; - yield return new object[] { "sw-KE", 0, "BK" }; - yield return new object[] { "sw-KE", 1, "BK" }; - yield return new object[] { "sw-TZ", 0, "BK" }; - yield return new object[] { "sw-TZ", 1, "BK" }; - yield return new object[] { "sw-UG", 0, "BK" }; - yield return new object[] { "sw-UG", 1, "BK" }; - yield return new object[] { "ta-IN", 0, "கி.பி." }; - yield return new object[] { "ta-IN", 1, "கி.பி." }; - yield return new object[] { "ta-LK", 0, "கி.பி." }; - yield return new object[] { "ta-LK", 1, "கி.பி." }; - yield return new object[] { "ta-MY", 0, "கி.பி." }; - yield return new object[] { "ta-MY", 1, "கி.பி." }; - yield return new object[] { "ta-SG", 0, "கி.பி." }; - yield return new object[] { "ta-SG", 1, "கி.பி." }; - yield return new object[] { "te-IN", 0, "క్రీశ" }; - yield return new object[] { "te-IN", 1, "క్రీశ" }; - yield return new object[] { "th-TH", 0, "พ.ศ." }; - yield return new object[] { "th-TH", 1, "พ.ศ." }; - yield return new object[] { "tr-CY", 0, "MS" }; - yield return new object[] { "tr-CY", 1, "MS" }; - yield return new object[] { "tr-TR", 0, "MS" }; - yield return new object[] { "tr-TR", 1, "MS" }; - yield return new object[] { "uk-UA", 0, "н. е." }; - yield return new object[] { "uk-UA", 1, "н. е." }; - yield return new object[] { "vi-VN", 0, PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "CN" : "SCN" }; // sau CN - yield return new object[] { "vi-VN", 1, PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "CN" : "SCN" }; // sau CN - yield return new object[] { "zh-CN", 0, "公元" }; - yield return new object[] { "zh-CN", 1, "公元" }; - yield return new object[] { "zh-Hans-HK", 0, "公元" }; - yield return new object[] { "zh-Hans-HK", 1, "公元" }; - yield return new object[] { "zh-SG", 0, "公元" }; - yield return new object[] { "zh-SG", 1, "公元" }; - yield return new object[] { "zh-HK", 0, "公元" }; - yield return new object[] { "zh-HK", 1, "公元" }; - yield return new object[] { "zh-TW", 0, "西元" }; - yield return new object[] { "zh-TW", 1, "西元" }; - } + yield return new object[] { "en-US", 1, DateTimeFormatInfoData.EnUSEraName() }; + yield return new object[] { "en-US", 0, DateTimeFormatInfoData.EnUSEraName() }; + yield return new object[] { "fr-FR", 1, "ap. J.-C." }; + yield return new object[] { "fr-FR", 0, "ap. J.-C." }; } [Theory] diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoLongDatePattern.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoLongDatePattern.cs index 53720bf5bffbfd..20be2cf3f22112 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoLongDatePattern.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoLongDatePattern.cs @@ -31,199 +31,6 @@ public static IEnumerable LongDatePattern_Get_TestData_ICU() yield return new object[] { CultureInfo.GetCultureInfo("fr-FR").DateTimeFormat, "dddd d MMMM yyyy" }; } - public static IEnumerable LongDatePattern_Get_TestData_HybridGlobalization() - { - // see the comments on the right to check the non-Hybrid result, if it differs - yield return new object[] {"ar-SA", "dddd، d MMMM yyyy" }; // dddd، d MMMM yyyy g - yield return new object[] {"am-ET", "yyyy MMMM d, dddd" }; - yield return new object[] {"bg-BG", "dddd, d MMMM yyyy 'г'." }; - yield return new object[] {"bn-BD", "dddd, d MMMM, yyyy" }; - yield return new object[] {"bn-IN", "dddd, d MMMM, yyyy" }; - string catalanianPattern = PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "dddd, d 'de' MMMM 'de' yyyy" : "dddd, d 'de' MMMM 'del' yyyy"; // "dddd, d MMMM 'de' yyyy" - yield return new object[] {"ca-AD", catalanianPattern }; - yield return new object[] {"ca-ES", catalanianPattern }; - yield return new object[] {"cs-CZ", "dddd d. MMMM yyyy" }; - yield return new object[] {"da-DK", "dddd 'den' d. MMMM yyyy" }; - yield return new object[] {"de-AT", "dddd, d. MMMM yyyy" }; - yield return new object[] {"de-BE", "dddd, d. MMMM yyyy" }; - yield return new object[] {"de-CH", "dddd, d. MMMM yyyy" }; - yield return new object[] {"de-DE", "dddd, d. MMMM yyyy" }; - yield return new object[] {"de-IT", "dddd, d. MMMM yyyy" }; - yield return new object[] {"de-LI", "dddd, d. MMMM yyyy" }; - yield return new object[] {"de-LU", "dddd, d. MMMM yyyy" }; - yield return new object[] {"el-CY", "dddd d MMMM yyyy" }; // "dddd, d MMMM yyyy" - yield return new object[] {"el-GR", "dddd d MMMM yyyy" }; // "dddd, d MMMM yyyy" - yield return new object[] {"en-AE", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-AG", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-AI", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-AS", "dddd, MMMM d, yyyy" }; - yield return new object[] {"en-AT", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-AU", PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "dddd, d MMMM yyyy" : "dddd d MMMM yyyy" }; - yield return new object[] {"en-BB", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-BE", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-BI", "dddd, MMMM d, yyyy" }; - yield return new object[] {"en-BM", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-BS", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-BW", "dddd, d MMMM yyyy" }; // "dddd, dd MMMM yyyy" - yield return new object[] {"en-BZ", "dddd, d MMMM yyyy" }; // "dddd, dd MMMM yyyy" - yield return new object[] {"en-CA", "dddd, MMMM d, yyyy" }; - yield return new object[] {"en-CC", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-CH", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-CK", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-CM", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-CX", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-CY", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-DE", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-DK", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-DM", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-ER", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-FI", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-FJ", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-FK", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-FM", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-GB", PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "dddd, d MMMM yyyy" :"dddd d MMMM yyyy" }; - yield return new object[] {"en-GD", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-GG", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-GH", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-GI", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-GM", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-GU", "dddd, MMMM d, yyyy" }; - yield return new object[] {"en-GY", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-HK", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-IE", "dddd d MMMM yyyy" }; - yield return new object[] {"en-IL", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-IM", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-IN", PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "dddd, d MMMM, yyyy" : "dddd d MMMM, yyyy" }; // dddd, d MMMM, yyyy - yield return new object[] {"en-IO", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-JE", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-JM", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-KE", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-KI", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-KN", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-KY", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-LC", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-LR", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-LS", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-MG", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-MH", "dddd, MMMM d, yyyy" }; - yield return new object[] {"en-MO", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-MP", "dddd, MMMM d, yyyy" }; - yield return new object[] {"en-MS", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-MT", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-MU", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-MW", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-MY", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-NA", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-NF", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-NG", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-NL", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-NR", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-NU", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-NZ", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-PG", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-PH", "dddd, MMMM d, yyyy" }; // "dddd, d MMMM yyyy" - yield return new object[] {"en-PK", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-PN", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-PR", "dddd, MMMM d, yyyy" }; - yield return new object[] {"en-PW", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-RW", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-SB", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-SC", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-SD", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-SE", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-SG", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-SH", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-SI", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-SL", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-SS", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-SX", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-SZ", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-TC", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-TK", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-TO", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-TT", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-TV", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-TZ", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-UG", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-UM", "dddd, MMMM d, yyyy" }; - yield return new object[] {"en-US", "dddd, MMMM d, yyyy" }; - yield return new object[] {"en-VC", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-VG", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-VI", "dddd, MMMM d, yyyy" }; - yield return new object[] {"en-VU", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-WS", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-ZA", "dddd, d MMMM yyyy" }; // "dddd, dd MMMM yyyy" - yield return new object[] {"en-ZM", "dddd, d MMMM yyyy" }; - yield return new object[] {"en-ZW", "dddd, d MMMM yyyy" }; // "dddd, dd MMMM yyyy" - yield return new object[] {"en-US", "dddd, MMMM d, yyyy" }; - string spanishPattern = "dddd, d 'de' MMMM 'de' yyyy"; - yield return new object[] {"es-419", spanishPattern }; - yield return new object[] {"es-ES", spanishPattern }; - yield return new object[] {"es-MX", spanishPattern }; - yield return new object[] {"et-EE", "dddd, d. MMMM yyyy" }; - yield return new object[] {"fa-IR", "yyyy MMMM d, dddd" }; - yield return new object[] {"fi-FI", "dddd d. MMMM yyyy" }; - yield return new object[] {"fil-PH", "dddd, MMMM d, yyyy" }; - yield return new object[] {"fr-BE", "dddd d MMMM yyyy" }; - yield return new object[] {"fr-CA", "dddd d MMMM yyyy" }; - yield return new object[] {"fr-CH", "dddd, d MMMM yyyy" }; - yield return new object[] {"fr-FR", "dddd d MMMM yyyy" }; - yield return new object[] {"gu-IN", "dddd, d MMMM, yyyy" }; - yield return new object[] {"he-IL", "dddd, d בMMMM yyyy" }; - yield return new object[] {"hi-IN", "dddd, d MMMM yyyy" }; - yield return new object[] {"hr-BA", "dddd, d. MMMM yyyy." }; - yield return new object[] {"hr-HR", "dddd, d. MMMM yyyy." }; - yield return new object[] {"hu-HU", "yyyy. MMMM d., dddd" }; - yield return new object[] {"id-ID", "dddd, d MMMM yyyy" }; // "dddd, dd MMMM yyyy" - yield return new object[] {"it-CH", "dddd, d MMMM yyyy" }; - yield return new object[] {"it-IT", "dddd d MMMM yyyy" }; - yield return new object[] {"ja-JP", "yyyy年M月d日dddd" }; - yield return new object[] {"kn-IN", "dddd, MMMM d, yyyy" }; - yield return new object[] {"ko-KR", "yyyy년 M월 d일 dddd" }; - yield return new object[] {"lt-LT", "yyyy 'm'. MMMM d 'd'., dddd" }; - yield return new object[] {"lv-LV", "dddd, yyyy. 'gada' d. MMMM" }; - yield return new object[] {"ml-IN", "yyyy, MMMM d, dddd" }; - yield return new object[] {"mr-IN", "dddd, d MMMM, yyyy" }; - yield return new object[] {"ms-BN", "dddd, d MMMM yyyy" }; // "dd MMMM yyyy" - yield return new object[] {"ms-MY", "dddd, d MMMM yyyy" }; - yield return new object[] {"ms-SG", "dddd, d MMMM yyyy" }; - yield return new object[] {"nb-NO", "dddd d. MMMM yyyy" }; - yield return new object[] {"no-NO", "dddd d. MMMM yyyy" }; - yield return new object[] {"nl-AW", "dddd d MMMM yyyy" }; - yield return new object[] {"nl-BE", "dddd d MMMM yyyy" }; - yield return new object[] {"nl-NL", "dddd d MMMM yyyy" }; - yield return new object[] {"pl-PL", "dddd, d MMMM yyyy" }; - yield return new object[] {"pt-BR", "dddd, d 'de' MMMM 'de' yyyy" }; - yield return new object[] {"pt-PT", "dddd, d 'de' MMMM 'de' yyyy" }; - yield return new object[] {"ro-RO", "dddd, d MMMM yyyy" }; - yield return new object[] {"ru-RU", "dddd, d MMMM yyyy 'г'." }; - yield return new object[] {"sk-SK", "dddd d. MMMM yyyy" }; - yield return new object[] {"sl-SI", "dddd, d. MMMM yyyy" }; // "dddd, dd. MMMM yyyy" - yield return new object[] {"sr-Cyrl-RS", "dddd, d. MMMM yyyy." }; // "dddd, dd. MMMM yyyy" - yield return new object[] {"sr-Latn-RS", "dddd, d. MMMM yyyy." }; // "dddd, dd. MMMM yyyy" - yield return new object[] {"sv-AX", "dddd d MMMM yyyy" }; - yield return new object[] {"sv-SE", "dddd d MMMM yyyy" }; - yield return new object[] {"sw-CD", "dddd, d MMMM yyyy" }; - yield return new object[] {"sw-KE", "dddd, d MMMM yyyy" }; - yield return new object[] {"sw-TZ", "dddd, d MMMM yyyy" }; - yield return new object[] {"sw-UG", "dddd, d MMMM yyyy" }; - yield return new object[] {"ta-IN", "dddd, d MMMM, yyyy" }; - yield return new object[] {"ta-LK", "dddd, d MMMM, yyyy" }; - yield return new object[] {"ta-MY", "dddd, d MMMM, yyyy" }; - yield return new object[] {"ta-SG", "dddd, d MMMM, yyyy" }; - yield return new object[] {"te-IN", "d, MMMM yyyy, dddd" }; - yield return new object[] {"th-TH", "ddddที่ d MMMM g yyyy" }; - yield return new object[] {"tr-CY", "d MMMM yyyy dddd" }; - yield return new object[] {"tr-TR", "d MMMM yyyy dddd" }; - yield return new object[] {"uk-UA", "dddd, d MMMM yyyy 'р'." }; - yield return new object[] {"vi-VN", "dddd, d MMMM, yyyy" }; - yield return new object[] {"zh-CN", "yyyy年M月d日dddd" }; - yield return new object[] {"zh-Hans-HK", "yyyy年M月d日dddd" }; - yield return new object[] {"zh-SG", "yyyy年M月d日dddd" }; - yield return new object[] {"zh-HK", "yyyy年M月d日dddd" }; - yield return new object[] {"zh-TW", "yyyy年M月d日 dddd" }; - } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization))] [MemberData(nameof(LongDatePattern_Get_TestData_ICU))] public void LongDatePattern_Get_ReturnsExpected_ICU(DateTimeFormatInfo format, string expected) @@ -231,14 +38,6 @@ public void LongDatePattern_Get_ReturnsExpected_ICU(DateTimeFormatInfo format, s Assert.Equal(expected, format.LongDatePattern); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] - [MemberData(nameof(LongDatePattern_Get_TestData_HybridGlobalization))] - public void LongDatePattern_Get_ReturnsExpected_HybridGlobalization(string cultureName, string expected) - { - var format = new CultureInfo(cultureName).DateTimeFormat; - Assert.True(expected == format.LongDatePattern, $"Failed for culture: {cultureName}. Expected: {expected}, Actual: {format.LongDatePattern}"); - } - [Theory] [MemberData(nameof(LongDatePattern_Set_TestData))] public void LongDatePattern_Set_GetReturnsExpected(string value) diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoLongTimePattern.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoLongTimePattern.cs index dc02bcba5869dd..455726b844d51a 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoLongTimePattern.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoLongTimePattern.cs @@ -14,205 +14,6 @@ public void LongTimePattern_GetInvariantInfo_ReturnsExpected() Assert.Equal("HH:mm:ss", DateTimeFormatInfo.InvariantInfo.LongTimePattern); } - public static IEnumerable LongTimePattern_Get_TestData_HybridGlobalization() - { - // see the comments on the right to check the non-Hybrid result, if it differs - yield return new object[] { new CultureInfo("ar-SA").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("am-ET").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("bg-BG").DateTimeFormat, PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "H:mm:ss ч." : "H:mm:ss" }; // H:mm:ss ч. - yield return new object[] { new CultureInfo("bn-BD").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("bn-IN").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("ca-AD").DateTimeFormat, "H:mm:ss" }; - yield return new object[] { new CultureInfo("ca-ES").DateTimeFormat, "H:mm:ss" }; - yield return new object[] { new CultureInfo("cs-CZ").DateTimeFormat, "H:mm:ss" }; - yield return new object[] { new CultureInfo("da-DK").DateTimeFormat, "HH.mm.ss" }; - yield return new object[] { new CultureInfo("de-AT").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("de-BE").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("de-CH").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("de-DE").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("de-IT").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("de-LI").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("de-LU").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("el-CY").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("el-GR").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-AE").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-AG").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-AI").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-AS").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-AT").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-AU").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-BB").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-BE").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-BI").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-BM").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-BS").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-BW").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-BZ").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-CA").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-CC").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-CH").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-CK").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-CM").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-CX").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-CY").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-DE").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-DK").DateTimeFormat, "HH.mm.ss" }; - yield return new object[] { new CultureInfo("en-DM").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-ER").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-FI").DateTimeFormat, "H.mm.ss" }; - yield return new object[] { new CultureInfo("en-FJ").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-FK").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-FM").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-GB").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-GD").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-GG").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-GH").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-GI").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-GM").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-GU").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-GY").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-HK").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-IE").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-IL").DateTimeFormat, "H:mm:ss" }; - yield return new object[] { new CultureInfo("en-IM").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-IN").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-IO").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-JE").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-JM").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-KE").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-KI").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-KN").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-KY").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-LC").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-LR").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-LS").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-MG").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-MH").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-MO").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-MP").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-MS").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-MT").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-MU").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-MW").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-MY").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-NA").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-NF").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-NG").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-NL").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-NR").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-NU").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-NZ").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-PG").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-PH").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-PK").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-PN").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-PR").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-PW").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-RW").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-SB").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-SC").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-SD").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-SE").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-SG").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-SH").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-SI").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-SL").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-SS").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-SX").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-SZ").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-TC").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-TK").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-TO").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-TT").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-TV").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-TZ").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-UG").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-UM").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-US").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-VC").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-VG").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-VI").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-VU").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-WS").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-ZA").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("en-ZM").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("en-ZW").DateTimeFormat, "HH:mm:ss" }; - string latinAmericaSpanishPattern = PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "HH:mm:ss" : "h:mm:ss tt"; // H:mm:ss - yield return new object[] { new CultureInfo("es-419").DateTimeFormat, latinAmericaSpanishPattern }; - yield return new object[] { new CultureInfo("es-ES").DateTimeFormat, "H:mm:ss" }; - yield return new object[] { new CultureInfo("es-MX").DateTimeFormat, latinAmericaSpanishPattern }; - yield return new object[] { new CultureInfo("et-EE").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("fa-IR").DateTimeFormat, "H:mm:ss" }; - yield return new object[] { new CultureInfo("fi-FI").DateTimeFormat, "H.mm.ss" }; - yield return new object[] { new CultureInfo("fil-PH").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("fr-BE").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("fr-CA").DateTimeFormat, "HH 'h' mm 'min' ss 's'" }; - yield return new object[] { new CultureInfo("fr-CH").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("fr-FR").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("gu-IN").DateTimeFormat, "hh:mm:ss tt" }; - yield return new object[] { new CultureInfo("he-IL").DateTimeFormat, "H:mm:ss" }; - yield return new object[] { new CultureInfo("hi-IN").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("hr-BA").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("hr-HR").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("hu-HU").DateTimeFormat, "H:mm:ss" }; - yield return new object[] { new CultureInfo("id-ID").DateTimeFormat, "HH.mm.ss" }; - yield return new object[] { new CultureInfo("it-CH").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("it-IT").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("ja-JP").DateTimeFormat, "H:mm:ss" }; - yield return new object[] { new CultureInfo("kn-IN").DateTimeFormat, "hh:mm:ss tt" }; - yield return new object[] { new CultureInfo("ko-KR").DateTimeFormat, "tt h:mm:ss" }; - yield return new object[] { new CultureInfo("lt-LT").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("lv-LV").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("ml-IN").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("mr-IN").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("ms-BN").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("ms-MY").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("ms-SG").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("nb-NO").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("no").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("no-NO").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("nl-AW").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("nl-BE").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("nl-NL").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("pl-PL").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("pt-BR").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("pt-PT").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("ro-RO").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("ru-RU").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("sk-SK").DateTimeFormat, "H:mm:ss" }; - yield return new object[] { new CultureInfo("sl-SI").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("sr-Cyrl-RS").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("sr-Latn-RS").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("sv-AX").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("sv-SE").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("sw-CD").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("sw-KE").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("sw-TZ").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("sw-UG").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("ta-IN").DateTimeFormat, "tt h:mm:ss" }; - yield return new object[] { new CultureInfo("ta-LK").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("ta-MY").DateTimeFormat, "tt h:mm:ss" }; - yield return new object[] { new CultureInfo("ta-SG").DateTimeFormat, "tt h:mm:ss" }; - yield return new object[] { new CultureInfo("te-IN").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("th-TH").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("tr-CY").DateTimeFormat, "h:mm:ss tt" }; - yield return new object[] { new CultureInfo("tr-TR").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("uk-UA").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("vi-VN").DateTimeFormat, "HH:mm:ss" }; - yield return new object[] { new CultureInfo("zh-CN").DateTimeFormat, "HH:mm:ss" }; // tth:mm:ss - yield return new object[] { new CultureInfo("zh-Hans-HK").DateTimeFormat, "tth:mm:ss" }; - yield return new object[] { new CultureInfo("zh-SG").DateTimeFormat, "tth:mm:ss" }; - yield return new object[] { new CultureInfo("zh-HK").DateTimeFormat, "tth:mm:ss" }; - yield return new object[] { new CultureInfo("zh-TW").DateTimeFormat, "tth:mm:ss" }; - } - - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] - [MemberData(nameof(LongTimePattern_Get_TestData_HybridGlobalization))] - public void LongTimePattern_Get_ReturnsExpected_HybridGlobalization(DateTimeFormatInfo format, string value) - { - Assert.Equal(value, format.LongTimePattern); - } - public static IEnumerable LongTimePattern_Set_TestData() { yield return new object[] { string.Empty }; diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoMonthDayPattern.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoMonthDayPattern.cs index 4cce8b20864987..8708cc49417dad 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoMonthDayPattern.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoMonthDayPattern.cs @@ -31,196 +31,6 @@ public static IEnumerable MonthDayPattern_Get_TestData_ICU() yield return new object[] { CultureInfo.GetCultureInfo("en-US").DateTimeFormat, "MMMM d" }; yield return new object[] { CultureInfo.GetCultureInfo("fr-FR").DateTimeFormat, "d MMMM" }; } - - public static IEnumerable MonthDayPattern_Get_TestData_HybridGlobalization() - { - // see the comments on the right to check the non-Hybrid result, if it differs - yield return new object[] { new CultureInfo("ar-SA").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("am-ET").DateTimeFormat, "MMMM d" }; - yield return new object[] { new CultureInfo("bg-BG").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("bn-BD").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("bn-IN").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("ca-AD").DateTimeFormat, "d de MMMM" }; // d MMMM - yield return new object[] { new CultureInfo("ca-ES").DateTimeFormat, "d de MMMM" }; // d MMMM - yield return new object[] { new CultureInfo("cs-CZ").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("da-DK").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("de-AT").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("de-BE").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("de-CH").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("de-DE").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("de-IT").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("de-LI").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("de-LU").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("el-CY").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("el-GR").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-AE").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-AG").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-AI").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-AS").DateTimeFormat, "MMMM d" }; - yield return new object[] { new CultureInfo("en-AT").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-AU").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-BB").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-BE").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-BI").DateTimeFormat, "MMMM d" }; - yield return new object[] { new CultureInfo("en-BM").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-BS").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-BW").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-BZ").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-CA").DateTimeFormat, "MMMM d" }; - yield return new object[] { new CultureInfo("en-CC").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-CH").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-CK").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-CM").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-CX").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-CY").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-DE").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-DK").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-DM").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-ER").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-FI").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-FJ").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-FK").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-FM").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-GB").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-GD").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-GG").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-GH").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-GI").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-GM").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-GU").DateTimeFormat, "MMMM d" }; - yield return new object[] { new CultureInfo("en-GY").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-HK").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-IE").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-IL").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-IM").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-IN").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-IO").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-JE").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-JM").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-KE").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-KI").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-KN").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-KY").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-LC").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-LR").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-LS").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-MG").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-MH").DateTimeFormat, "MMMM d" }; - yield return new object[] { new CultureInfo("en-MO").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-MP").DateTimeFormat, "MMMM d" }; - yield return new object[] { new CultureInfo("en-MS").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-MT").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-MU").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-MW").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-MY").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-NA").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-NF").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-NG").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-NL").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-NR").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-NU").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-NZ").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-PG").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-PH").DateTimeFormat, "MMMM d" }; // "d MMMM" - yield return new object[] { new CultureInfo("en-PK").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-PN").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-PR").DateTimeFormat, "MMMM d" }; - yield return new object[] { new CultureInfo("en-PW").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-RW").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-SB").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-SC").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-SD").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-SE").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-SG").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-SH").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-SI").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-SL").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-SS").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-SX").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-SZ").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-TC").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-TK").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-TO").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-TT").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-TV").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-TZ").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-UG").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-UM").DateTimeFormat, "MMMM d" }; - yield return new object[] { new CultureInfo("en-US").DateTimeFormat, "MMMM d" }; - yield return new object[] { new CultureInfo("en-VC").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-VG").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-VI").DateTimeFormat, "MMMM d" }; - yield return new object[] { new CultureInfo("en-VU").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-WS").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-ZA").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-ZM").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("en-ZW").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("es-419").DateTimeFormat, "d de MMMM" }; // d 'de' MMMM - yield return new object[] { new CultureInfo("es-ES").DateTimeFormat, "d de MMMM" }; // d 'de' MMMM - yield return new object[] { new CultureInfo("es-MX").DateTimeFormat, "d de MMMM" }; // d 'de' MMMM - yield return new object[] { new CultureInfo("et-EE").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("fa-IR").DateTimeFormat, "d MMMM" }; // "MMMM d" - yield return new object[] { new CultureInfo("fi-FI").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("fil-PH").DateTimeFormat, "MMMM d" }; - yield return new object[] { new CultureInfo("fr-BE").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("fr-CA").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("fr-CH").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("fr-FR").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("gu-IN").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("he-IL").DateTimeFormat, "d בMMMM" }; - yield return new object[] { new CultureInfo("hi-IN").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("hr-BA").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("hr-HR").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("hu-HU").DateTimeFormat, "MMMM d." }; - yield return new object[] { new CultureInfo("id-ID").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("it-CH").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("it-IT").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("ja-JP").DateTimeFormat, "M月d日" }; - yield return new object[] { new CultureInfo("kn-IN").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("ko-KR").DateTimeFormat, "MMMM d일" }; - yield return new object[] { new CultureInfo("lt-LT").DateTimeFormat, "MMMM d d." }; // MMMM d 'd'. - yield return new object[] { new CultureInfo("lv-LV").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("ml-IN").DateTimeFormat, "MMMM d" }; - yield return new object[] { new CultureInfo("mr-IN").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("ms-BN").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("ms-MY").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("ms-SG").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("nb-NO").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("no").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("no-NO").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("nl-AW").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("nl-BE").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("nl-NL").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("pl-PL").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("pt-BR").DateTimeFormat, "d de MMMM" }; // d 'de' MMMM - yield return new object[] { new CultureInfo("pt-PT").DateTimeFormat, "d de MMMM" }; // d 'de' MMMM - yield return new object[] { new CultureInfo("ro-RO").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("ru-RU").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("sk-SK").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("sl-SI").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("sr-Cyrl-RS").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("sr-Latn-RS").DateTimeFormat, "d. MMMM" }; - yield return new object[] { new CultureInfo("sv-AX").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("sv-SE").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("sw-CD").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("sw-KE").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("sw-TZ").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("sw-UG").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("ta-IN").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("ta-LK").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("ta-MY").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("ta-SG").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("te-IN").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("th-TH").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("tr-CY").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("tr-TR").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("uk-UA").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("vi-VN").DateTimeFormat, "d MMMM" }; - yield return new object[] { new CultureInfo("zh-Hans-HK").DateTimeFormat, "M月d日" }; - yield return new object[] { new CultureInfo("zh-SG").DateTimeFormat, "M月d日" }; - yield return new object[] { new CultureInfo("zh-HK").DateTimeFormat, "M月d日" }; - yield return new object[] { new CultureInfo("zh-TW").DateTimeFormat, "M月d日" }; - } [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization))] [MemberData(nameof(MonthDayPattern_Get_TestData_ICU))] @@ -229,13 +39,6 @@ public void MonthDayPattern_Get_ReturnsExpected_ICU(DateTimeFormatInfo format, s Assert.Equal(expected, format.MonthDayPattern); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] - [MemberData(nameof(MonthDayPattern_Get_TestData_HybridGlobalization))] - public void MonthDayPattern_Get_ReturnsExpected_HybridGlobalization(DateTimeFormatInfo format, string expected) - { - Assert.Equal(expected, format.MonthDayPattern); - } - [Theory] [MemberData(nameof(MonthDayPattern_Set_TestData))] public void MonthDayPattern_Set_GetReturnsExpected(string value) diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoMonthGenitiveNames.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoMonthGenitiveNames.cs index 5167679d8cb2bf..68fcfd6e08a62e 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoMonthGenitiveNames.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoMonthGenitiveNames.cs @@ -36,204 +36,6 @@ public static IEnumerable MonthGenitiveNames_Get_TestData() yield return new object[] { CultureInfo.GetCultureInfo("en-US").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; yield return new object[] { CultureInfo.GetCultureInfo("fr-FR").DateTimeFormat, new string[] { "janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre", "" } }; } - if (PlatformDetection.IsHybridGlobalizationOnBrowser) - { - // see the comments on the right to check the non-Hybrid result, if it differs - yield return new object[] { new CultureInfo("ar-SA").DateTimeFormat, new string[] { "محرم", "صفر", "ربيع الأول", "ربيع الآخر", "جمادى الأولى", "جمادى الآخرة", "رجب", "شعبان", "رمضان", "شوال", "ذو القعدة", "ذو الحجة", "" } }; - if (PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS) - { - yield return new object[] { new CultureInfo("am-ET").DateTimeFormat, new string[] { "ጃንዩወሪ", "ፌብሩወሪ", "ማርች", "ኤፕሪል", "ሜይ", "ጁን", "ጁላይ", "ኦገስት", "ሴፕቴምበር", "ኦክቶበር", "ኖቬምበር", "ዲሴምበር", "" } }; - } - else - { - yield return new object[] { new CultureInfo("am-ET").DateTimeFormat, new string[] { "ጃንዋሪ", "ፌብሩዋሪ", "ማርች", "ኤፕሪል", "ሜይ", "ጁን", "ጁላይ", "ኦገስት", "ሴፕቴምበር", "ኦክቶበር", "ኖቬምበር", "ዲሴምበር", "" } }; // "ጃንዩወሪ", "ፌብሩወሪ", "ማርች", "ኤፕሪል", "ሜይ", "ጁን", "ጁላይ", "ኦገስት", "ሴፕቴምበር", "ኦክቶበር", "ኖቬምበር", "ዲሴምበር", "" - } - yield return new object[] { new CultureInfo("bg-BG").DateTimeFormat, new string[] { "януари", "февруари", "март", "април", "май", "юни", "юли", "август", "септември", "октомври", "ноември", "декември", "" } }; - yield return new object[] { new CultureInfo("bn-BD").DateTimeFormat, new string[] { "জানুয়ারী", "ফেব্রুয়ারী", "মার্চ", "এপ্রিল", "মে", "জুন", "জুলাই", "আগস্ট", "সেপ্টেম্বর", "অক্টোবর", "নভেম্বর", "ডিসেম্বর", "" } }; - yield return new object[] { new CultureInfo("bn-IN").DateTimeFormat, new string[] { "জানুয়ারী", "ফেব্রুয়ারী", "মার্চ", "এপ্রিল", "মে", "জুন", "জুলাই", "আগস্ট", "সেপ্টেম্বর", "অক্টোবর", "নভেম্বর", "ডিসেম্বর", "" } }; - yield return new object[] { new CultureInfo("ca-AD").DateTimeFormat, new string[] { "gener", "febrer", "març", "abril", "maig", "juny", "juliol", "agost", "setembre", "octubre", "novembre", "desembre", "" } }; // "de gener", "de febrer", "de març", "d’abril", "de maig", "de juny", "de juliol", "d’agost", "de setembre", "d’octubre", "de novembre", "de desembre", "" - yield return new object[] { new CultureInfo("ca-ES").DateTimeFormat, new string[] { "gener", "febrer", "març", "abril", "maig", "juny", "juliol", "agost", "setembre", "octubre", "novembre", "desembre", "" } }; // "de gener", "de febrer", "de març", "d’abril", "de maig", "de juny", "de juliol", "d’agost", "de setembre", "d’octubre", "de novembre", "de desembre", "" - yield return new object[] { new CultureInfo("cs-CZ").DateTimeFormat, new string[] { "ledna", "února", "března", "dubna", "května", "června", "července", "srpna", "září", "října", "listopadu", "prosince", "" } }; - yield return new object[] { new CultureInfo("da-DK").DateTimeFormat, new string[] { "januar", "februar", "marts", "april", "maj", "juni", "juli", "august", "september", "oktober", "november", "december", "" } }; - yield return new object[] { new CultureInfo("de-AT").DateTimeFormat, new string[] { "Jänner", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", "" } }; - yield return new object[] { new CultureInfo("de-BE").DateTimeFormat, new string[] { "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", "" } }; - yield return new object[] { new CultureInfo("de-CH").DateTimeFormat, new string[] { "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", "" } }; - yield return new object[] { new CultureInfo("de-DE").DateTimeFormat, new string[] { "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", "" } }; - yield return new object[] { new CultureInfo("de-IT").DateTimeFormat, new string[] { "Jänner", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", "" } }; - yield return new object[] { new CultureInfo("de-LI").DateTimeFormat, new string[] { "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", "" } }; - yield return new object[] { new CultureInfo("de-LU").DateTimeFormat, new string[] { "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", "" } }; - yield return new object[] { new CultureInfo("el-CY").DateTimeFormat, new string[] { "Ιανουαρίου", "Φεβρουαρίου", "Μαρτίου", "Απριλίου", "Μαΐου", "Ιουνίου", "Ιουλίου", "Αυγούστου", "Σεπτεμβρίου", "Οκτωβρίου", "Νοεμβρίου", "Δεκεμβρίου", "" } }; - yield return new object[] { new CultureInfo("el-GR").DateTimeFormat, new string[] { "Ιανουαρίου", "Φεβρουαρίου", "Μαρτίου", "Απριλίου", "Μαΐου", "Ιουνίου", "Ιουλίου", "Αυγούστου", "Σεπτεμβρίου", "Οκτωβρίου", "Νοεμβρίου", "Δεκεμβρίου", "" } }; - yield return new object[] { new CultureInfo("en-AE").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-AG").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-AI").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-AS").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-AT").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-AU").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-BB").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-BE").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-BI").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-BM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-BS").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-BW").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-BZ").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-CA").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-CC").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-CH").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-CK").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-CM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-CX").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-CY").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-DE").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-DK").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-DM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-ER").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-FI").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-FJ").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-FK").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-FM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-GB").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-GD").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-GG").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-GH").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-GI").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-GM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-GU").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-GY").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-HK").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-IE").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-IL").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-IM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-IN").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-IO").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-JE").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-JM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-KE").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-KI").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-KN").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-KY").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-LC").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-LR").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-LS").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MG").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MH").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MO").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MP").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MS").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MT").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MU").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MW").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MY").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-NA").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-NF").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-NG").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-NL").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-NR").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-NU").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-NZ").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-PG").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-PH").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-PK").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-PN").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-PR").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-PW").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-RW").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SB").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SC").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SD").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SE").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SG").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SH").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SI").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SL").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SS").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SX").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SZ").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-TC").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-TK").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-TO").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-TT").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-TV").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-TZ").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-UG").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-UM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-US").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-VC").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-VG").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-VI").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-VU").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-WS").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-ZA").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-ZM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-ZW").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-US").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("es-419").DateTimeFormat, new string[] { "enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre", "" } }; - yield return new object[] { new CultureInfo("es-ES").DateTimeFormat, new string[] { "enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre", "" } }; - yield return new object[] { new CultureInfo("es-MX").DateTimeFormat, new string[] { "enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre", "" } }; - yield return new object[] { new CultureInfo("et-EE").DateTimeFormat, new string[] { "jaanuar", "veebruar", "märts", "aprill", "mai", "juuni", "juuli", "august", "september", "oktoober", "november", "detsember", "" } }; - yield return new object[] { new CultureInfo("fa-IR").DateTimeFormat, new string[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" } }; - yield return new object[] { new CultureInfo("fi-FI").DateTimeFormat, new string[] { "tammikuuta", "helmikuuta", "maaliskuuta", "huhtikuuta", "toukokuuta", "kesäkuuta", "heinäkuuta", "elokuuta", "syyskuuta", "lokakuuta", "marraskuuta", "joulukuuta", "" } }; - yield return new object[] { new CultureInfo("fil-PH").DateTimeFormat, new string[] { "Enero", "Pebrero", "Marso", "Abril", "Mayo", "Hunyo", "Hulyo", "Agosto", "Setyembre", "Oktubre", "Nobyembre", "Disyembre", "" } }; - yield return new object[] { new CultureInfo("fr-BE").DateTimeFormat, new string[] { "janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre", "" } }; - yield return new object[] { new CultureInfo("fr-CA").DateTimeFormat, new string[] { "janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre", "" } }; - yield return new object[] { new CultureInfo("fr-CH").DateTimeFormat, new string[] { "janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre", "" } }; - yield return new object[] { new CultureInfo("fr-FR").DateTimeFormat, new string[] { "janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre", "" } }; - yield return new object[] { new CultureInfo("gu-IN").DateTimeFormat, new string[] { "જાન્યુઆરી", "ફેબ્રુઆરી", "માર્ચ", "એપ્રિલ", "મે", "જૂન", "જુલાઈ", "ઑગસ્ટ", "સપ્ટેમ્બર", "ઑક્ટોબર", "નવેમ્બર", "ડિસેમ્બર", "" } }; - yield return new object[] { new CultureInfo("he-IL").DateTimeFormat, new string[] { "ינואר", "פברואר", "מרץ", "אפריל", "מאי", "יוני", "יולי", "אוגוסט", "ספטמבר", "אוקטובר", "נובמבר", "דצמבר", "" } }; - yield return new object[] { new CultureInfo("hi-IN").DateTimeFormat, new string[] { "जनवरी", "फ़रवरी", "मार्च", "अप्रैल", "मई", "जून", "जुलाई", "अगस्त", "सितंबर", "अक्तूबर", "नवंबर", "दिसंबर", "" } }; - yield return new object[] { new CultureInfo("hr-BA").DateTimeFormat, new string[] { "siječnja", "veljače", "ožujka", "travnja", "svibnja", "lipnja", "srpnja", "kolovoza", "rujna", "listopada", "studenoga", "prosinca", "" } }; - yield return new object[] { new CultureInfo("hr-HR").DateTimeFormat, new string[] { "siječnja", "veljače", "ožujka", "travnja", "svibnja", "lipnja", "srpnja", "kolovoza", "rujna", "listopada", "studenoga", "prosinca", "" } }; - yield return new object[] { new CultureInfo("hu-HU").DateTimeFormat, new string[] { "január", "február", "március", "április", "május", "június", "július", "augusztus", "szeptember", "október", "november", "december", "" } }; - yield return new object[] { new CultureInfo("id-ID").DateTimeFormat, new string[] { "Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember", "" } }; - yield return new object[] { new CultureInfo("it-CH").DateTimeFormat, new string[] { "gennaio", "febbraio", "marzo", "aprile", "maggio", "giugno", "luglio", "agosto", "settembre", "ottobre", "novembre", "dicembre", "" } }; - yield return new object[] { new CultureInfo("it-IT").DateTimeFormat, new string[] { "gennaio", "febbraio", "marzo", "aprile", "maggio", "giugno", "luglio", "agosto", "settembre", "ottobre", "novembre", "dicembre", "" } }; - yield return new object[] { new CultureInfo("ja-JP").DateTimeFormat, new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - yield return new object[] { new CultureInfo("kn-IN").DateTimeFormat, new string[] { "ಜನವರಿ", "ಫೆಬ್ರವರಿ", "ಮಾರ್ಚ್", "ಏಪ್ರಿಲ್", "ಮೇ", "ಜೂನ್", "ಜುಲೈ", "ಆಗಸ್ಟ್", "ಸೆಪ್ಟೆಂಬರ್", "ಅಕ್ಟೋಬರ್", "ನವೆಂಬರ್", "ಡಿಸೆಂಬರ್", "" } }; - yield return new object[] { new CultureInfo("ko-KR").DateTimeFormat, new string[] { "1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월", "" } }; - yield return new object[] { new CultureInfo("lt-LT").DateTimeFormat, new string[] { "sausio", "vasario", "kovo", "balandžio", "gegužės", "birželio", "liepos", "rugpjūčio", "rugsėjo", "spalio", "lapkričio", "gruodžio", "" } }; - yield return new object[] { new CultureInfo("lv-LV").DateTimeFormat, new string[] { "janvāris", "februāris", "marts", "aprīlis", "maijs", "jūnijs", "jūlijs", "augusts", "septembris", "oktobris", "novembris", "decembris", "" } }; - yield return new object[] { new CultureInfo("ml-IN").DateTimeFormat, new string[] { "ജനുവരി", "ഫെബ്രുവരി", "മാർച്ച്", "ഏപ്രിൽ", "മേയ്", "ജൂൺ", "ജൂലൈ", "ഓഗസ്റ്റ്", "സെപ്റ്റംബർ", "ഒക്‌ടോബർ", "നവംബർ", "ഡിസംബർ", "" } }; - yield return new object[] { new CultureInfo("mr-IN").DateTimeFormat, new string[] { "जानेवारी", "फेब्रुवारी", "मार्च", "एप्रिल", "मे", "जून", "जुलै", "ऑगस्ट", "सप्टेंबर", "ऑक्टोबर", "नोव्हेंबर", "डिसेंबर", "" } }; - yield return new object[] { new CultureInfo("ms-BN").DateTimeFormat, new string[] { "Januari", "Februari", "Mac", "April", "Mei", "Jun", "Julai", "Ogos", "September", "Oktober", "November", "Disember", "" } }; - yield return new object[] { new CultureInfo("ms-MY").DateTimeFormat, new string[] { "Januari", "Februari", "Mac", "April", "Mei", "Jun", "Julai", "Ogos", "September", "Oktober", "November", "Disember", "" } }; - yield return new object[] { new CultureInfo("ms-SG").DateTimeFormat, new string[] { "Januari", "Februari", "Mac", "April", "Mei", "Jun", "Julai", "Ogos", "September", "Oktober", "November", "Disember", "" } }; - yield return new object[] { new CultureInfo("nb-NO").DateTimeFormat, new string[] { "januar", "februar", "mars", "april", "mai", "juni", "juli", "august", "september", "oktober", "november", "desember", "" } }; - yield return new object[] { new CultureInfo("no").DateTimeFormat, new string[] { "januar", "februar", "mars", "april", "mai", "juni", "juli", "august", "september", "oktober", "november", "desember", "" } }; - yield return new object[] { new CultureInfo("no-NO").DateTimeFormat, new string[] { "januar", "februar", "mars", "april", "mai", "juni", "juli", "august", "september", "oktober", "november", "desember", "" } }; - yield return new object[] { new CultureInfo("nl-AW").DateTimeFormat, new string[] { "januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december", "" } }; - yield return new object[] { new CultureInfo("nl-BE").DateTimeFormat, new string[] { "januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december", "" } }; - yield return new object[] { new CultureInfo("nl-NL").DateTimeFormat, new string[] { "januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december", "" } }; - yield return new object[] { new CultureInfo("pl-PL").DateTimeFormat, new string[] { "stycznia", "lutego", "marca", "kwietnia", "maja", "czerwca", "lipca", "sierpnia", "września", "października", "listopada", "grudnia", "" } }; - yield return new object[] { new CultureInfo("pt-BR").DateTimeFormat, new string[] { "janeiro", "fevereiro", "março", "abril", "maio", "junho", "julho", "agosto", "setembro", "outubro", "novembro", "dezembro", "" } }; - yield return new object[] { new CultureInfo("pt-PT").DateTimeFormat, new string[] { "janeiro", "fevereiro", "março", "abril", "maio", "junho", "julho", "agosto", "setembro", "outubro", "novembro", "dezembro", "" } }; - yield return new object[] { new CultureInfo("ro-RO").DateTimeFormat, new string[] { "ianuarie", "februarie", "martie", "aprilie", "mai", "iunie", "iulie", "august", "septembrie", "octombrie", "noiembrie", "decembrie", "" } }; - yield return new object[] { new CultureInfo("ru-RU").DateTimeFormat, new string[] { "января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря", "" } }; - yield return new object[] { new CultureInfo("sk-SK").DateTimeFormat, new string[] { "januára", "februára", "marca", "apríla", "mája", "júna", "júla", "augusta", "septembra", "októbra", "novembra", "decembra", "" } }; - yield return new object[] { new CultureInfo("sl-SI").DateTimeFormat, new string[] { "januar", "februar", "marec", "april", "maj", "junij", "julij", "avgust", "september", "oktober", "november", "december", "" } }; - yield return new object[] { new CultureInfo("sr-Cyrl-RS").DateTimeFormat, new string[] { "јануар", "фебруар", "март", "април", "мај", "јун", "јул", "август", "септембар", "октобар", "новембар", "децембар", "" } }; - yield return new object[] { new CultureInfo("sr-Latn-RS").DateTimeFormat, new string[] { "januar", "februar", "mart", "april", "maj", "jun", "jul", "avgust", "septembar", "oktobar", "novembar", "decembar", "" } }; - yield return new object[] { new CultureInfo("sv-AX").DateTimeFormat, new string[] { "januari", "februari", "mars", "april", "maj", "juni", "juli", "augusti", "september", "oktober", "november", "december", "" } }; - yield return new object[] { new CultureInfo("sv-SE").DateTimeFormat, new string[] { "januari", "februari", "mars", "april", "maj", "juni", "juli", "augusti", "september", "oktober", "november", "december", "" } }; - yield return new object[] { new CultureInfo("sw-CD").DateTimeFormat, new string[] { "Januari", "Februari", "Machi", "Aprili", "Mei", "Juni", "Julai", "Agosti", "Septemba", "Oktoba", "Novemba", "Desemba", "" } }; - yield return new object[] { new CultureInfo("sw-KE").DateTimeFormat, new string[] { "Januari", "Februari", "Machi", "Aprili", "Mei", "Juni", "Julai", "Agosti", "Septemba", "Oktoba", "Novemba", "Desemba", "" } }; - yield return new object[] { new CultureInfo("sw-TZ").DateTimeFormat, new string[] { "Januari", "Februari", "Machi", "Aprili", "Mei", "Juni", "Julai", "Agosti", "Septemba", "Oktoba", "Novemba", "Desemba", "" } }; - yield return new object[] { new CultureInfo("sw-UG").DateTimeFormat, new string[] { "Januari", "Februari", "Machi", "Aprili", "Mei", "Juni", "Julai", "Agosti", "Septemba", "Oktoba", "Novemba", "Desemba", "" } }; - yield return new object[] { new CultureInfo("ta-IN").DateTimeFormat, new string[] { "ஜனவரி", "பிப்ரவரி", "மார்ச்", "ஏப்ரல்", "மே", "ஜூன்", "ஜூலை", "ஆகஸ்ட்", "செப்டம்பர்", "அக்டோபர்", "நவம்பர்", "டிசம்பர்", "" } }; - yield return new object[] { new CultureInfo("ta-LK").DateTimeFormat, new string[] { "ஜனவரி", "பிப்ரவரி", "மார்ச்", "ஏப்ரல்", "மே", "ஜூன்", "ஜூலை", "ஆகஸ்ட்", "செப்டம்பர்", "அக்டோபர்", "நவம்பர்", "டிசம்பர்", "" } }; - yield return new object[] { new CultureInfo("ta-MY").DateTimeFormat, new string[] { "ஜனவரி", "பிப்ரவரி", "மார்ச்", "ஏப்ரல்", "மே", "ஜூன்", "ஜூலை", "ஆகஸ்ட்", "செப்டம்பர்", "அக்டோபர்", "நவம்பர்", "டிசம்பர்", "" } }; - yield return new object[] { new CultureInfo("ta-SG").DateTimeFormat, new string[] { "ஜனவரி", "பிப்ரவரி", "மார்ச்", "ஏப்ரல்", "மே", "ஜூன்", "ஜூலை", "ஆகஸ்ட்", "செப்டம்பர்", "அக்டோபர்", "நவம்பர்", "டிசம்பர்", "" } }; - yield return new object[] { new CultureInfo("te-IN").DateTimeFormat, new string[] { "జనవరి", "ఫిబ్రవరి", "మార్చి", "ఏప్రిల్", "మే", "జూన్", "జులై", "ఆగస్టు", "సెప్టెంబర్", "అక్టోబర్", "నవంబర్", "డిసెంబర్", "" } }; - yield return new object[] { new CultureInfo("th-TH").DateTimeFormat, new string[] { "มกราคม", "กุมภาพันธ์", "มีนาคม", "เมษายน", "พฤษภาคม", "มิถุนายน", "กรกฎาคม", "สิงหาคม", "กันยายน", "ตุลาคม", "พฤศจิกายน", "ธันวาคม", "" } }; - yield return new object[] { new CultureInfo("tr-CY").DateTimeFormat, new string[] { "Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık", "" } }; - yield return new object[] { new CultureInfo("tr-TR").DateTimeFormat, new string[] { "Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık", "" } }; - yield return new object[] { new CultureInfo("uk-UA").DateTimeFormat, new string[] { "січня", "лютого", "березня", "квітня", "травня", "червня", "липня", "серпня", "вересня", "жовтня", "листопада", "грудня", "" } }; - yield return new object[] { new CultureInfo("vi-VN").DateTimeFormat, new string[] { "Tháng 1", "Tháng 2", "Tháng 3", "Tháng 4", "Tháng 5", "Tháng 6", "Tháng 7", "Tháng 8", "Tháng 9", "Tháng 10", "Tháng 11", "Tháng 12", "" } }; // ICU: tháng - yield return new object[] { new CultureInfo("zh-CN").DateTimeFormat, new string[] { "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月", "" } }; - yield return new object[] { new CultureInfo("zh-Hans-HK").DateTimeFormat, new string[] { "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月", "" } }; - yield return new object[] { new CultureInfo("zh-SG").DateTimeFormat, new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; // "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月", "" - yield return new object[] { new CultureInfo("zh-HK").DateTimeFormat, new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - yield return new object[] { new CultureInfo("zh-TW").DateTimeFormat, new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - } } [Theory] diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoMonthNames.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoMonthNames.cs index e4d1b6084dd1c8..aa4c05574b54bc 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoMonthNames.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoMonthNames.cs @@ -34,205 +34,6 @@ public static IEnumerable MonthNames_Get_TestData_ICU() yield return new object[] { CultureInfo.GetCultureInfo("fr-FR").DateTimeFormat, new string[] { "janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre", "" } }; } - public static IEnumerable MonthNames_Get_TestData_HybridGlobalization() - { - // see the comments on the right to check the non-Hybrid result, if it differs - yield return new object[] { new CultureInfo("ar-SA").DateTimeFormat, new string[] { "محرم", "صفر", "ربيع الأول", "ربيع الآخر", "جمادى الأولى", "جمادى الآخرة", "رجب", "شعبان", "رمضان", "شوال", "ذو القعدة", "ذو الحجة", "" } }; - if (PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS) - { - yield return new object[] { new CultureInfo("am-ET").DateTimeFormat, new string[] { "ጃንዩወሪ", "ፌብሩወሪ", "ማርች", "ኤፕሪል", "ሜይ", "ጁን", "ጁላይ", "ኦገስት", "ሴፕቴምበር", "ኦክቶበር", "ኖቬምበር", "ዲሴምበር", "" } }; - } - else - { - yield return new object[] { new CultureInfo("am-ET").DateTimeFormat, new string[] { "ጃንዋሪ", "ፌብሩዋሪ", "ማርች", "ኤፕሪል", "ሜይ", "ጁን", "ጁላይ", "ኦገስት", "ሴፕቴምበር", "ኦክቶበር", "ኖቬምበር", "ዲሴምበር", "" } }; // "ጃንዩወሪ", "ፌብሩወሪ", "ማርች", "ኤፕሪል", "ሜይ", "ጁን", "ጁላይ", "ኦገስት", "ሴፕቴምበር", "ኦክቶበር", "ኖቬምበር", "ዲሴምበር", "" - } - yield return new object[] { new CultureInfo("bg-BG").DateTimeFormat, new string[] { "януари", "февруари", "март", "април", "май", "юни", "юли", "август", "септември", "октомври", "ноември", "декември", "" } }; - yield return new object[] { new CultureInfo("bn-BD").DateTimeFormat, new string[] { "জানুয়ারী", "ফেব্রুয়ারী", "মার্চ", "এপ্রিল", "মে", "জুন", "জুলাই", "আগস্ট", "সেপ্টেম্বর", "অক্টোবর", "নভেম্বর", "ডিসেম্বর", "" } }; - yield return new object[] { new CultureInfo("bn-IN").DateTimeFormat, new string[] { "জানুয়ারী", "ফেব্রুয়ারী", "মার্চ", "এপ্রিল", "মে", "জুন", "জুলাই", "আগস্ট", "সেপ্টেম্বর", "অক্টোবর", "নভেম্বর", "ডিসেম্বর", "" } }; - yield return new object[] { new CultureInfo("ca-AD").DateTimeFormat, new string[] { "gener", "febrer", "març", "abril", "maig", "juny", "juliol", "agost", "setembre", "octubre", "novembre", "desembre", "" } }; - yield return new object[] { new CultureInfo("ca-ES").DateTimeFormat, new string[] { "gener", "febrer", "març", "abril", "maig", "juny", "juliol", "agost", "setembre", "octubre", "novembre", "desembre", "" } }; - yield return new object[] { new CultureInfo("cs-CZ").DateTimeFormat, new string[] { "leden", "únor", "březen", "duben", "květen", "červen", "červenec", "srpen", "září", "říjen", "listopad", "prosinec", "" } }; - yield return new object[] { new CultureInfo("da-DK").DateTimeFormat, new string[] { "januar", "februar", "marts", "april", "maj", "juni", "juli", "august", "september", "oktober", "november", "december", "" } }; - yield return new object[] { new CultureInfo("de-AT").DateTimeFormat, new string[] { "Jänner", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", "" } }; - yield return new object[] { new CultureInfo("de-BE").DateTimeFormat, new string[] { "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", "" } }; - yield return new object[] { new CultureInfo("de-CH").DateTimeFormat, new string[] { "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", "" } }; - yield return new object[] { new CultureInfo("de-DE").DateTimeFormat, new string[] { "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", "" } }; - yield return new object[] { new CultureInfo("de-IT").DateTimeFormat, new string[] { "Jänner", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", "" } }; - yield return new object[] { new CultureInfo("de-LI").DateTimeFormat, new string[] { "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", "" } }; - yield return new object[] { new CultureInfo("de-LU").DateTimeFormat, new string[] { "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", "" } }; - yield return new object[] { new CultureInfo("el-CY").DateTimeFormat, new string[] { "Ιανουαρίου", "Φεβρουαρίου", "Μαρτίου", "Απριλίου", "Μαΐου", "Ιουνίου", "Ιουλίου", "Αυγούστου", "Σεπτεμβρίου", "Οκτωβρίου", "Νοεμβρίου", "Δεκεμβρίου", "" } }; // BUG!!! JS returns Genitive for Greek even though we expect Nominative; "Ιανουάριος", "Φεβρουάριος", "Μάρτιος", "Απρίλιος", "Μάιος", "Ιούνιος", "Ιούλιος", "Αύγουστος", "Σεπτέμβριος", "Οκτώβριος", "Νοέμβριος", "Δεκέμβριος" - yield return new object[] { new CultureInfo("el-GR").DateTimeFormat, new string[] { "Ιανουαρίου", "Φεβρουαρίου", "Μαρτίου", "Απριλίου", "Μαΐου", "Ιουνίου", "Ιουλίου", "Αυγούστου", "Σεπτεμβρίου", "Οκτωβρίου", "Νοεμβρίου", "Δεκεμβρίου", "" } }; // BUG!!! JS returns Genitive for Greek even though we expect Nominative; "Ιανουάριος", "Φεβρουάριος", "Μάρτιος", "Απρίλιος", "Μάιος", "Ιούνιος", "Ιούλιος", "Αύγουστος", "Σεπτέμβριος", "Οκτώβριος", "Νοέμβριος", "Δεκέμβριος" - yield return new object[] { new CultureInfo("en-AE").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-AG").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-AI").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-AS").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-AT").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-AU").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-BB").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-BE").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-BI").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-BM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-BS").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-BW").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-BZ").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-CA").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-CC").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-CH").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-CK").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-CM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-CX").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-CY").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-DE").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-DK").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-DM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-ER").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-FI").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-FJ").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-FK").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-FM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-GB").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-GD").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-GG").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-GH").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-GI").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-GM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-GU").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-GY").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-HK").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-IE").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-IL").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-IM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-IN").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-IO").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-JE").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-JM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-KE").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-KI").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-KN").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-KY").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-LC").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-LR").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-LS").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MG").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MH").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MO").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MP").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MS").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MT").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MU").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MW").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-MY").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-NA").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-NF").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-NG").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-NL").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-NR").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-NU").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-NZ").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-PG").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-PH").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-PK").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-PN").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-PR").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-PW").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-RW").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SB").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SC").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SD").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SE").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SG").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SH").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SI").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SL").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SS").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SX").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-SZ").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-TC").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-TK").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-TO").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-TT").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-TV").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-TZ").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-UG").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-UM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-US").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-VC").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-VG").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-VI").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-VU").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-WS").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-ZA").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-ZM").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-ZW").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("en-US").DateTimeFormat, new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" } }; - yield return new object[] { new CultureInfo("es-419").DateTimeFormat, new string[] { "enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre", "" } }; - yield return new object[] { new CultureInfo("es-ES").DateTimeFormat, new string[] { "enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre", "" } }; - yield return new object[] { new CultureInfo("es-MX").DateTimeFormat, new string[] { "enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre", "" } }; - yield return new object[] { new CultureInfo("et-EE").DateTimeFormat, new string[] { "jaanuar", "veebruar", "märts", "aprill", "mai", "juuni", "juuli", "august", "september", "oktoober", "november", "detsember", "" } }; - yield return new object[] { new CultureInfo("fa-IR").DateTimeFormat, new string[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" } }; - yield return new object[] { new CultureInfo("fi-FI").DateTimeFormat, new string[] { "tammikuu", "helmikuu", "maaliskuu", "huhtikuu", "toukokuu", "kesäkuu", "heinäkuu", "elokuu", "syyskuu", "lokakuu", "marraskuu", "joulukuu", "" } }; - yield return new object[] { new CultureInfo("fil-PH").DateTimeFormat, new string[] { "Enero", "Pebrero", "Marso", "Abril", "Mayo", "Hunyo", "Hulyo", "Agosto", "Setyembre", "Oktubre", "Nobyembre", "Disyembre", "" } }; - yield return new object[] { new CultureInfo("fr-BE").DateTimeFormat, new string[] { "janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre", "" } }; - yield return new object[] { new CultureInfo("fr-CA").DateTimeFormat, new string[] { "janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre", "" } }; - yield return new object[] { new CultureInfo("fr-CH").DateTimeFormat, new string[] { "janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre", "" } }; - yield return new object[] { new CultureInfo("fr-FR").DateTimeFormat, new string[] { "janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre", "" } }; - yield return new object[] { new CultureInfo("gu-IN").DateTimeFormat, new string[] { "જાન્યુઆરી", "ફેબ્રુઆરી", "માર્ચ", "એપ્રિલ", "મે", "જૂન", "જુલાઈ", "ઑગસ્ટ", "સપ્ટેમ્બર", "ઑક્ટોબર", "નવેમ્બર", "ડિસેમ્બર", "" } }; - yield return new object[] { new CultureInfo("he-IL").DateTimeFormat, new string[] { "ינואר", "פברואר", "מרץ", "אפריל", "מאי", "יוני", "יולי", "אוגוסט", "ספטמבר", "אוקטובר", "נובמבר", "דצמבר", "" } }; - yield return new object[] { new CultureInfo("hi-IN").DateTimeFormat, new string[] { "जनवरी", "फ़रवरी", "मार्च", "अप्रैल", "मई", "जून", "जुलाई", "अगस्त", "सितंबर", "अक्तूबर", "नवंबर", "दिसंबर", "" } }; - yield return new object[] { new CultureInfo("hr-BA").DateTimeFormat, new string[] { "siječanj", "veljača", "ožujak", "travanj", "svibanj", "lipanj", "srpanj", "kolovoz", "rujan", "listopad", "studeni", "prosinac", "" } }; - yield return new object[] { new CultureInfo("hr-HR").DateTimeFormat, new string[] { "siječanj", "veljača", "ožujak", "travanj", "svibanj", "lipanj", "srpanj", "kolovoz", "rujan", "listopad", "studeni", "prosinac", "" } }; - yield return new object[] { new CultureInfo("hu-HU").DateTimeFormat, new string[] { "január", "február", "március", "április", "május", "június", "július", "augusztus", "szeptember", "október", "november", "december", "" } }; - yield return new object[] { new CultureInfo("id-ID").DateTimeFormat, new string[] { "Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember", "" } }; - yield return new object[] { new CultureInfo("it-CH").DateTimeFormat, new string[] { "gennaio", "febbraio", "marzo", "aprile", "maggio", "giugno", "luglio", "agosto", "settembre", "ottobre", "novembre", "dicembre", "" } }; - yield return new object[] { new CultureInfo("it-IT").DateTimeFormat, new string[] { "gennaio", "febbraio", "marzo", "aprile", "maggio", "giugno", "luglio", "agosto", "settembre", "ottobre", "novembre", "dicembre", "" } }; - yield return new object[] { new CultureInfo("ja-JP").DateTimeFormat, new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - yield return new object[] { new CultureInfo("kn-IN").DateTimeFormat, new string[] { "ಜನವರಿ", "ಫೆಬ್ರವರಿ", "ಮಾರ್ಚ್", "ಏಪ್ರಿಲ್", "ಮೇ", "ಜೂನ್", "ಜುಲೈ", "ಆಗಸ್ಟ್", "ಸೆಪ್ಟೆಂಬರ್", "ಅಕ್ಟೋಬರ್", "ನವೆಂಬರ್", "ಡಿಸೆಂಬರ್", "" } }; - yield return new object[] { new CultureInfo("ko-KR").DateTimeFormat, new string[] { "1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월", "" } }; - yield return new object[] { new CultureInfo("lt-LT").DateTimeFormat, new string[] { "sausis", "vasaris", "kovas", "balandis", "gegužė", "birželis", "liepa", "rugpjūtis", "rugsėjis", "spalis", "lapkritis", "gruodis", "" } }; - yield return new object[] { new CultureInfo("lv-LV").DateTimeFormat, new string[] { "janvāris", "februāris", "marts", "aprīlis", "maijs", "jūnijs", "jūlijs", "augusts", "septembris", "oktobris", "novembris", "decembris", "" } }; - yield return new object[] { new CultureInfo("ml-IN").DateTimeFormat, new string[] { "ജനുവരി", "ഫെബ്രുവരി", "മാർച്ച്", "ഏപ്രിൽ", "മേയ്", "ജൂൺ", "ജൂലൈ", "ഓഗസ്റ്റ്", "സെപ്റ്റംബർ", "ഒക്‌ടോബർ", "നവംബർ", "ഡിസംബർ", "" } }; - yield return new object[] { new CultureInfo("mr-IN").DateTimeFormat, new string[] { "जानेवारी", "फेब्रुवारी", "मार्च", "एप्रिल", "मे", "जून", "जुलै", "ऑगस्ट", "सप्टेंबर", "ऑक्टोबर", "नोव्हेंबर", "डिसेंबर", "" } }; - yield return new object[] { new CultureInfo("ms-BN").DateTimeFormat, new string[] { "Januari", "Februari", "Mac", "April", "Mei", "Jun", "Julai", "Ogos", "September", "Oktober", "November", "Disember", "" } }; - yield return new object[] { new CultureInfo("ms-MY").DateTimeFormat, new string[] { "Januari", "Februari", "Mac", "April", "Mei", "Jun", "Julai", "Ogos", "September", "Oktober", "November", "Disember", "" } }; - yield return new object[] { new CultureInfo("ms-SG").DateTimeFormat, new string[] { "Januari", "Februari", "Mac", "April", "Mei", "Jun", "Julai", "Ogos", "September", "Oktober", "November", "Disember", "" } }; - yield return new object[] { new CultureInfo("nb-NO").DateTimeFormat, new string[] { "januar", "februar", "mars", "april", "mai", "juni", "juli", "august", "september", "oktober", "november", "desember", "" } }; - yield return new object[] { new CultureInfo("no").DateTimeFormat, new string[] { "januar", "februar", "mars", "april", "mai", "juni", "juli", "august", "september", "oktober", "november", "desember", "" } }; - yield return new object[] { new CultureInfo("no-NO").DateTimeFormat, new string[] { "januar", "februar", "mars", "april", "mai", "juni", "juli", "august", "september", "oktober", "november", "desember", "" } }; - yield return new object[] { new CultureInfo("nl-AW").DateTimeFormat, new string[] { "januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december", "" } }; - yield return new object[] { new CultureInfo("nl-BE").DateTimeFormat, new string[] { "januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december", "" } }; - yield return new object[] { new CultureInfo("nl-NL").DateTimeFormat, new string[] { "januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december", "" } }; - yield return new object[] { new CultureInfo("pl-PL").DateTimeFormat, new string[] { "styczeń", "luty", "marzec", "kwiecień", "maj", "czerwiec", "lipiec", "sierpień", "wrzesień", "październik", "listopad", "grudzień", "" } }; - yield return new object[] { new CultureInfo("pt-BR").DateTimeFormat, new string[] { "janeiro", "fevereiro", "março", "abril", "maio", "junho", "julho", "agosto", "setembro", "outubro", "novembro", "dezembro", "" } }; - yield return new object[] { new CultureInfo("pt-PT").DateTimeFormat, new string[] { "janeiro", "fevereiro", "março", "abril", "maio", "junho", "julho", "agosto", "setembro", "outubro", "novembro", "dezembro", "" } }; - yield return new object[] { new CultureInfo("ro-RO").DateTimeFormat, new string[] { "ianuarie", "februarie", "martie", "aprilie", "mai", "iunie", "iulie", "august", "septembrie", "octombrie", "noiembrie", "decembrie", "" } }; - yield return new object[] { new CultureInfo("ru-RU").DateTimeFormat, new string[] { "январь", "февраль", "март", "апрель", "май", "июнь", "июль", "август", "сентябрь", "октябрь", "ноябрь", "декабрь", "" } }; - yield return new object[] { new CultureInfo("sk-SK").DateTimeFormat, new string[] { "január", "február", "marec", "apríl", "máj", "jún", "júl", "august", "september", "október", "november", "december", "" } }; - yield return new object[] { new CultureInfo("sl-SI").DateTimeFormat, new string[] { "januar", "februar", "marec", "april", "maj", "junij", "julij", "avgust", "september", "oktober", "november", "december", "" } }; - yield return new object[] { new CultureInfo("sr-Cyrl-RS").DateTimeFormat, new string[] { "јануар", "фебруар", "март", "април", "мај", "јун", "јул", "август", "септембар", "октобар", "новембар", "децембар", "" } }; - yield return new object[] { new CultureInfo("sr-Latn-RS").DateTimeFormat, new string[] { "januar", "februar", "mart", "april", "maj", "jun", "jul", "avgust", "septembar", "oktobar", "novembar", "decembar", "" } }; - yield return new object[] { new CultureInfo("sv-AX").DateTimeFormat, new string[] { "januari", "februari", "mars", "april", "maj", "juni", "juli", "augusti", "september", "oktober", "november", "december", "" } }; - yield return new object[] { new CultureInfo("sv-SE").DateTimeFormat, new string[] { "januari", "februari", "mars", "april", "maj", "juni", "juli", "augusti", "september", "oktober", "november", "december", "" } }; - yield return new object[] { new CultureInfo("sw-CD").DateTimeFormat, new string[] { "Januari", "Februari", "Machi", "Aprili", "Mei", "Juni", "Julai", "Agosti", "Septemba", "Oktoba", "Novemba", "Desemba", "" } }; - yield return new object[] { new CultureInfo("sw-KE").DateTimeFormat, new string[] { "Januari", "Februari", "Machi", "Aprili", "Mei", "Juni", "Julai", "Agosti", "Septemba", "Oktoba", "Novemba", "Desemba", "" } }; - yield return new object[] { new CultureInfo("sw-TZ").DateTimeFormat, new string[] { "Januari", "Februari", "Machi", "Aprili", "Mei", "Juni", "Julai", "Agosti", "Septemba", "Oktoba", "Novemba", "Desemba", "" } }; - yield return new object[] { new CultureInfo("sw-UG").DateTimeFormat, new string[] { "Januari", "Februari", "Machi", "Aprili", "Mei", "Juni", "Julai", "Agosti", "Septemba", "Oktoba", "Novemba", "Desemba", "" } }; - yield return new object[] { new CultureInfo("ta-IN").DateTimeFormat, new string[] { "ஜனவரி", "பிப்ரவரி", "மார்ச்", "ஏப்ரல்", "மே", "ஜூன்", "ஜூலை", "ஆகஸ்ட்", "செப்டம்பர்", "அக்டோபர்", "நவம்பர்", "டிசம்பர்", "" } }; - yield return new object[] { new CultureInfo("ta-LK").DateTimeFormat, new string[] { "ஜனவரி", "பிப்ரவரி", "மார்ச்", "ஏப்ரல்", "மே", "ஜூன்", "ஜூலை", "ஆகஸ்ட்", "செப்டம்பர்", "அக்டோபர்", "நவம்பர்", "டிசம்பர்", "" } }; - yield return new object[] { new CultureInfo("ta-MY").DateTimeFormat, new string[] { "ஜனவரி", "பிப்ரவரி", "மார்ச்", "ஏப்ரல்", "மே", "ஜூன்", "ஜூலை", "ஆகஸ்ட்", "செப்டம்பர்", "அக்டோபர்", "நவம்பர்", "டிசம்பர்", "" } }; - yield return new object[] { new CultureInfo("ta-SG").DateTimeFormat, new string[] { "ஜனவரி", "பிப்ரவரி", "மார்ச்", "ஏப்ரல்", "மே", "ஜூன்", "ஜூலை", "ஆகஸ்ட்", "செப்டம்பர்", "அக்டோபர்", "நவம்பர்", "டிசம்பர்", "" } }; - yield return new object[] { new CultureInfo("te-IN").DateTimeFormat, new string[] { "జనవరి", "ఫిబ్రవరి", "మార్చి", "ఏప్రిల్", "మే", "జూన్", "జులై", "ఆగస్టు", "సెప్టెంబర్", "అక్టోబర్", "నవంబర్", "డిసెంబర్", "" } }; - yield return new object[] { new CultureInfo("th-TH").DateTimeFormat, new string[] { "มกราคม", "กุมภาพันธ์", "มีนาคม", "เมษายน", "พฤษภาคม", "มิถุนายน", "กรกฎาคม", "สิงหาคม", "กันยายน", "ตุลาคม", "พฤศจิกายน", "ธันวาคม", "" } }; - yield return new object[] { new CultureInfo("tr-CY").DateTimeFormat, new string[] { "Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık", "" } }; - yield return new object[] { new CultureInfo("tr-TR").DateTimeFormat, new string[] { "Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık", "" } }; - yield return new object[] { new CultureInfo("uk-UA").DateTimeFormat, new string[] { "січень", "лютий", "березень", "квітень", "травень", "червень", "липень", "серпень", "вересень", "жовтень", "листопад", "грудень", "" } }; - yield return new object[] { new CultureInfo("vi-VN").DateTimeFormat, new string[] { "Tháng 1", "Tháng 2", "Tháng 3", "Tháng 4", "Tháng 5", "Tháng 6", "Tháng 7", "Tháng 8", "Tháng 9", "Tháng 10", "Tháng 11", "Tháng 12", "" } }; - yield return new object[] { new CultureInfo("zh-CN").DateTimeFormat, new string[] { "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月", "" } }; - yield return new object[] { new CultureInfo("zh-Hans-HK").DateTimeFormat, new string[] { "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月", "" } }; - yield return new object[] { new CultureInfo("zh-SG").DateTimeFormat, new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; // "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月", "" - yield return new object[] { new CultureInfo("zh-HK").DateTimeFormat, new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - yield return new object[] { new CultureInfo("zh-TW").DateTimeFormat, new string[] { "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", "" } }; - } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization))] [MemberData(nameof(MonthNames_Get_TestData_ICU))] public void MonthNames_Get_ReturnsExpected_ICU(DateTimeFormatInfo format, string[] expected) @@ -240,13 +41,6 @@ public void MonthNames_Get_ReturnsExpected_ICU(DateTimeFormatInfo format, string Assert.Equal(expected, format.MonthNames); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] - [MemberData(nameof(MonthNames_Get_TestData_HybridGlobalization))] - public void MonthNames_Get_ReturnsExpected(DateTimeFormatInfo format, string[] expected) - { - Assert.Equal(expected, format.MonthNames); - } - [Theory] [MemberData(nameof(MonthNames_Set_TestData))] public void MonthNames_Set_GetReturnsExpected(string[] value) diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoNativeCalendarName.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoNativeCalendarName.cs deleted file mode 100644 index 377e6d30d597bc..00000000000000 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoNativeCalendarName.cs +++ /dev/null @@ -1,214 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Xunit; -using System.Collections.Generic; - -namespace System.Globalization.Tests -{ - public class DateTimeFormatInfoNativeCalendarName - { - public static IEnumerable NativeCalendarName_Get_TestData_HybridGlobalization() - { - // see the comments on the right to check the non-Hybrid result, in this collection it always differs - string islamicName = PlatformDetection.IsFirefox ? "UMALQURA" : "islamic-umalqura"; - string gregorianName = PlatformDetection.IsFirefox ? "GREGORIAN" : "gregory"; - string persianName = PlatformDetection.IsFirefox ? "PERSIAN" : "persian"; - string bhuddistName = PlatformDetection.IsFirefox ? "THAI" : "buddhist"; - yield return new object[] { new CultureInfo("ar-SA").DateTimeFormat, islamicName }; // التقويم الإسلامي (أم القرى) - yield return new object[] { new CultureInfo("am-ET").DateTimeFormat, gregorianName }; // የግሪጎሪያን የቀን አቆጣጠር - yield return new object[] { new CultureInfo("bg-BG").DateTimeFormat, gregorianName }; // григориански календар - yield return new object[] { new CultureInfo("bn-BD").DateTimeFormat, gregorianName }; // গ্রিগোরিয়ান ক্যালেন্ডার - yield return new object[] { new CultureInfo("bn-IN").DateTimeFormat, gregorianName }; // গ্রিগোরিয়ান ক্যালেন্ডার - yield return new object[] { new CultureInfo("ca-AD").DateTimeFormat, gregorianName }; // calendari gregorià - yield return new object[] { new CultureInfo("ca-ES").DateTimeFormat, gregorianName }; // calendari gregorià - yield return new object[] { new CultureInfo("cs-CZ").DateTimeFormat, gregorianName }; // Gregoriánský kalendář - yield return new object[] { new CultureInfo("da-DK").DateTimeFormat, gregorianName }; // gregoriansk kalender - yield return new object[] { new CultureInfo("de-AT").DateTimeFormat, gregorianName }; // Gregorianischer Kalender - yield return new object[] { new CultureInfo("de-BE").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("de-CH").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("de-DE").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("de-IT").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("de-LI").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("de-LU").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("el-CY").DateTimeFormat, gregorianName }; // Γρηγοριανό ημερολόγιο - yield return new object[] { new CultureInfo("el-GR").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-AE").DateTimeFormat, gregorianName }; // Gregorian Calendar - yield return new object[] { new CultureInfo("en-AG").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-AI").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-AS").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-AT").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-AU").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-BB").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-BE").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-BI").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-BM").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-BS").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-BW").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-BZ").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-CA").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-CC").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-CH").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-CK").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-CM").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-CX").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-CY").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-DE").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-DK").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-DM").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-ER").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-FI").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-FJ").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-FK").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-FM").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-GB").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-GD").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-GG").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-GH").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-GI").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-GM").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-GU").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-GY").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-HK").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-IE").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-IL").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-IM").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-IN").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-IO").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-JE").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-JM").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-KE").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-KI").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-KN").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-KY").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-LC").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-LR").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-LS").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-MG").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-MH").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-MO").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-MP").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-MS").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-MT").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-MU").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-MW").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-MY").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-NA").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-NF").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-NG").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-NL").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-NR").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-NU").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-NZ").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-PG").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-PH").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-PK").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-PN").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-PR").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-PW").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-RW").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-SB").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-SC").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-SD").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-SE").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-SG").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-SH").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-SI").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-SL").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-SS").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-SX").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-SZ").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-TC").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-TK").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-TO").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-TT").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-TV").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-TZ").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-UG").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-UM").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-US").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-VC").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-VG").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-VI").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-VU").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-WS").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-ZA").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-ZM").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-ZW").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("en-US").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("es-419").DateTimeFormat, gregorianName }; // calendario gregoriano - yield return new object[] { new CultureInfo("es-ES").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("es-MX").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("et-EE").DateTimeFormat, gregorianName }; // Gregoriuse kalender - yield return new object[] { new CultureInfo("fa-IR").DateTimeFormat, persianName }; // تقویم هجری شمسی - yield return new object[] { new CultureInfo("fi-FI").DateTimeFormat, gregorianName }; // gregoriaaninen kalenteri - yield return new object[] { new CultureInfo("fil-PH").DateTimeFormat, gregorianName }; // Gregorian na Kalendaryo - yield return new object[] { new CultureInfo("fr-BE").DateTimeFormat, gregorianName }; // calendrier grégorien - yield return new object[] { new CultureInfo("fr-CA").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("fr-CH").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("fr-FR").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("gu-IN").DateTimeFormat, gregorianName }; // ગ્રેગોરિઅન કેલેન્ડર - yield return new object[] { new CultureInfo("he-IL").DateTimeFormat, gregorianName }; // לוח השנה הגרגוריאני - yield return new object[] { new CultureInfo("hi-IN").DateTimeFormat, gregorianName }; // ग्रेगोरियन कैलेंडर" - yield return new object[] { new CultureInfo("hr-BA").DateTimeFormat, gregorianName }; // gregorijanski kalendar - yield return new object[] { new CultureInfo("hr-HR").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("hu-HU").DateTimeFormat, gregorianName }; // Gergely-naptár - yield return new object[] { new CultureInfo("id-ID").DateTimeFormat, gregorianName }; // Kalender Gregorian" - yield return new object[] { new CultureInfo("it-CH").DateTimeFormat, gregorianName }; // Calendario gregoriano - yield return new object[] { new CultureInfo("it-IT").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("ja-JP").DateTimeFormat, gregorianName }; // 西暦(グレゴリオ暦) - yield return new object[] { new CultureInfo("kn-IN").DateTimeFormat, gregorianName }; // ಗ್ರೆಗೋರಿಯನ್ ಕ್ಯಾಲೆಂಡರ್ - yield return new object[] { new CultureInfo("ko-KR").DateTimeFormat, gregorianName }; // 양력 - yield return new object[] { new CultureInfo("lt-LT").DateTimeFormat, gregorianName }; // Grigaliaus kalendorius - yield return new object[] { new CultureInfo("lv-LV").DateTimeFormat, gregorianName }; // Gregora kalendārs - yield return new object[] { new CultureInfo("ml-IN").DateTimeFormat, gregorianName }; // ഇംഗ്ലീഷ് കലണ്ടർ - yield return new object[] { new CultureInfo("mr-IN").DateTimeFormat, gregorianName }; // ग्रेगोरियन दिनदर्शिका - yield return new object[] { new CultureInfo("ms-BN").DateTimeFormat, gregorianName }; // Kalendar Gregory - yield return new object[] { new CultureInfo("ms-MY").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("ms-SG").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("nb-NO").DateTimeFormat, gregorianName }; // gregoriansk kalender - yield return new object[] { new CultureInfo("no").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("no-NO").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("nl-AW").DateTimeFormat, gregorianName }; // Gregoriaanse kalender - yield return new object[] { new CultureInfo("nl-BE").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("nl-NL").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("pl-PL").DateTimeFormat, gregorianName }; // kalendarz gregoriański - yield return new object[] { new CultureInfo("pt-BR").DateTimeFormat, gregorianName }; // Calendário Gregoriano - yield return new object[] { new CultureInfo("pt-PT").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("ro-RO").DateTimeFormat, gregorianName }; // calendar gregory - yield return new object[] { new CultureInfo("ru-RU").DateTimeFormat, gregorianName }; // григорианский календарь - yield return new object[] { new CultureInfo("sk-SK").DateTimeFormat, gregorianName }; // gregoriánsky kalendár - yield return new object[] { new CultureInfo("sl-SI").DateTimeFormat, gregorianName }; // gregorijanski koledar - yield return new object[] { new CultureInfo("sr-Cyrl-RS").DateTimeFormat, gregorianName }; // грегоријански календар - yield return new object[] { new CultureInfo("sr-Latn-RS").DateTimeFormat, gregorianName }; // gregorijanski kalendar - yield return new object[] { new CultureInfo("sv-AX").DateTimeFormat, gregorianName }; // gregoriansk kalender - yield return new object[] { new CultureInfo("sv-SE").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("sw-CD").DateTimeFormat, gregorianName }; // Kalenda ya Kigregori - yield return new object[] { new CultureInfo("sw-KE").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("sw-TZ").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("sw-UG").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("ta-IN").DateTimeFormat, gregorianName }; // கிரிகோரியன் நாள்காட்டி - yield return new object[] { new CultureInfo("ta-LK").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("ta-MY").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("ta-SG").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("te-IN").DateTimeFormat, gregorianName };// గ్రేగోరియన్ క్యాలెండర్ - yield return new object[] { new CultureInfo("th-TH").DateTimeFormat, bhuddistName }; // ปฏิทินพุทธ - yield return new object[] { new CultureInfo("tr-CY").DateTimeFormat, gregorianName }; // Miladi Takvim - yield return new object[] { new CultureInfo("tr-TR").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("uk-UA").DateTimeFormat, gregorianName }; // григоріанський календар - yield return new object[] { new CultureInfo("vi-VN").DateTimeFormat, gregorianName }; // Lịch Gregory - yield return new object[] { new CultureInfo("zh-CN").DateTimeFormat, gregorianName }; // 公历 - yield return new object[] { new CultureInfo("zh-Hans-HK").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("zh-SG").DateTimeFormat, gregorianName }; - yield return new object[] { new CultureInfo("zh-HK").DateTimeFormat, gregorianName }; // 公曆 - yield return new object[] { new CultureInfo("zh-TW").DateTimeFormat, gregorianName }; - } - - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] - [MemberData(nameof(NativeCalendarName_Get_TestData_HybridGlobalization))] - public void NativeCalendarName_Get_ReturnsExpected_HybridGlobalization(DateTimeFormatInfo format, string expected) - { - Assert.Equal(expected, format.NativeCalendarName); - } - } -} diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoPMDesignator.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoPMDesignator.cs index c85029aabfc4e2..cf0504082c5430 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoPMDesignator.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoPMDesignator.cs @@ -14,206 +14,6 @@ public void PMDesignator_GetInvariantInfo_ReturnsExpected() Assert.Equal("PM", DateTimeFormatInfo.InvariantInfo.PMDesignator); } - public static IEnumerable PMDesignator_Get_TestData_HybridGlobalization() - { - // see the comments on the right to check the non-Hybrid result, if it differs - yield return new object[] { new CultureInfo("ar-SA").DateTimeFormat, "م" }; - yield return new object[] { new CultureInfo("am-ET").DateTimeFormat, "ከሰዓት" }; - yield return new object[] { new CultureInfo("bg-BG").DateTimeFormat, "сл.об." }; - yield return new object[] { new CultureInfo("bn-BD").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("bn-IN").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("ca-AD").DateTimeFormat, "p.\u00A0m." }; - yield return new object[] { new CultureInfo("ca-ES").DateTimeFormat, "p.\u00A0m." }; - yield return new object[] { new CultureInfo("cs-CZ").DateTimeFormat, "odp." }; - yield return new object[] { new CultureInfo("da-DK").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("de-AT").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("de-BE").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("de-CH").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("de-DE").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("de-IT").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("de-LI").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("de-LU").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("el-CY").DateTimeFormat, "μ.μ." }; - yield return new object[] { new CultureInfo("el-GR").DateTimeFormat, "μ.μ." }; - yield return new object[] { new CultureInfo("en-AE").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("en-AG").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-AI").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-AS").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("en-AT").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-AU").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-BB").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-BE").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-BI").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("en-BM").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-BS").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-BW").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-BZ").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-CA").DateTimeFormat, "p.m." }; - yield return new object[] { new CultureInfo("en-CC").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-CH").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-CK").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-CM").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-CX").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-CY").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-DE").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-DK").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-DM").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-ER").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-FI").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-FJ").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-FK").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-FM").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-GB").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-GD").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-GG").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-GH").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-GI").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-GM").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-GU").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("en-GY").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-HK").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-IE").DateTimeFormat, "p.m." }; - yield return new object[] { new CultureInfo("en-IL").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-IM").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-IN").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-IO").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-JE").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-JM").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-KE").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-KI").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-KN").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-KY").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-LC").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-LR").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-LS").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-MG").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-MH").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("en-MO").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-MP").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("en-MS").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-MT").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-MU").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-MW").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-MY").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-NA").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-NF").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-NG").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-NL").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-NR").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-NU").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-NZ").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-PG").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-PH").DateTimeFormat, "PM" }; // "pm" - yield return new object[] { new CultureInfo("en-PK").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-PN").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-PR").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("en-PW").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-RW").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-SB").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-SC").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-SD").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-SE").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-SG").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-SH").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-SI").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-SL").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-SS").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-SX").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-SZ").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-TC").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-TK").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-TO").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-TT").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-TV").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-TZ").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-UG").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-UM").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("en-US").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("en-VC").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-VG").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-VI").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("en-VU").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-WS").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-ZA").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-ZM").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("en-ZW").DateTimeFormat, "pm" }; - string latinAmericaSpanishDesignator = PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "p.\u00A0m." : "p.m."; // p.m. - yield return new object[] { new CultureInfo("es-419").DateTimeFormat, latinAmericaSpanishDesignator }; - yield return new object[] { new CultureInfo("es-ES").DateTimeFormat, "p.\u00A0m." }; // p.m. - yield return new object[] { new CultureInfo("es-MX").DateTimeFormat, latinAmericaSpanishDesignator }; - yield return new object[] { new CultureInfo("et-EE").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("fa-IR").DateTimeFormat, "بعدازظهر" }; - yield return new object[] { new CultureInfo("fi-FI").DateTimeFormat, "ip." }; - yield return new object[] { new CultureInfo("fil-PH").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("fr-BE").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("fr-CA").DateTimeFormat, "p.m." }; - yield return new object[] { new CultureInfo("fr-CH").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("fr-FR").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("gu-IN").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("he-IL").DateTimeFormat, "אחה״צ" }; - yield return new object[] { new CultureInfo("hi-IN").DateTimeFormat, "pm" }; - yield return new object[] { new CultureInfo("hr-BA").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("hr-HR").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("hu-HU").DateTimeFormat, "du." }; - yield return new object[] { new CultureInfo("id-ID").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("it-CH").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("it-IT").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("ja-JP").DateTimeFormat, "午後" }; - yield return new object[] { new CultureInfo("kn-IN").DateTimeFormat, "ಅಪರಾಹ್ನ" }; - yield return new object[] { new CultureInfo("ko-KR").DateTimeFormat, "오후" }; - yield return new object[] { new CultureInfo("lt-LT").DateTimeFormat, "popiet" }; - yield return new object[] { new CultureInfo("lv-LV").DateTimeFormat, "pēcpusdienā" }; - yield return new object[] { new CultureInfo("ml-IN").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("mr-IN").DateTimeFormat, "PM" }; // म.उ. - yield return new object[] { new CultureInfo("ms-BN").DateTimeFormat, "PTG" }; - yield return new object[] { new CultureInfo("ms-MY").DateTimeFormat, "PTG" }; - yield return new object[] { new CultureInfo("ms-SG").DateTimeFormat, "PTG" }; - yield return new object[] { new CultureInfo("nb-NO").DateTimeFormat, "p.m." }; - yield return new object[] { new CultureInfo("no").DateTimeFormat, "p.m." }; - yield return new object[] { new CultureInfo("no-NO").DateTimeFormat, "p.m." }; - yield return new object[] { new CultureInfo("nl-AW").DateTimeFormat, "p.m." }; - yield return new object[] { new CultureInfo("nl-BE").DateTimeFormat, "p.m." }; - yield return new object[] { new CultureInfo("nl-NL").DateTimeFormat, "p.m." }; - yield return new object[] { new CultureInfo("pl-PL").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("pt-BR").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("pt-PT").DateTimeFormat, "da tarde" }; - yield return new object[] { new CultureInfo("ro-RO").DateTimeFormat, "p.m." }; - yield return new object[] { new CultureInfo("ru-RU").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("sk-SK").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("sl-SI").DateTimeFormat, "pop." }; - yield return new object[] { new CultureInfo("sr-Cyrl-RS").DateTimeFormat, "PM" }; // по подне - yield return new object[] { new CultureInfo("sr-Latn-RS").DateTimeFormat, "PM" }; // po podne - yield return new object[] { new CultureInfo("sv-AX").DateTimeFormat, "em" }; - yield return new object[] { new CultureInfo("sv-SE").DateTimeFormat, "em" }; - yield return new object[] { new CultureInfo("sw-CD").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("sw-KE").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("sw-TZ").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("sw-UG").DateTimeFormat, "PM" }; - string tamilDesignator = PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "பிற்பகல்" : "PM"; // பிற்பகல் - yield return new object[] { new CultureInfo("ta-IN").DateTimeFormat, tamilDesignator }; - yield return new object[] { new CultureInfo("ta-LK").DateTimeFormat, tamilDesignator }; - yield return new object[] { new CultureInfo("ta-MY").DateTimeFormat, tamilDesignator }; - yield return new object[] { new CultureInfo("ta-SG").DateTimeFormat, tamilDesignator }; - yield return new object[] { new CultureInfo("te-IN").DateTimeFormat, "PM" }; - yield return new object[] { new CultureInfo("th-TH").DateTimeFormat, "หลังเที่ยง" }; - yield return new object[] { new CultureInfo("tr-CY").DateTimeFormat, "ÖS" }; - yield return new object[] { new CultureInfo("tr-TR").DateTimeFormat, "ÖS" }; - yield return new object[] { new CultureInfo("uk-UA").DateTimeFormat, "пп" }; - yield return new object[] { new CultureInfo("vi-VN").DateTimeFormat, "CH" }; - yield return new object[] { new CultureInfo("zh-CN").DateTimeFormat, "下午" }; - yield return new object[] { new CultureInfo("zh-Hans-HK").DateTimeFormat, "下午" }; - yield return new object[] { new CultureInfo("zh-SG").DateTimeFormat, "下午" }; - yield return new object[] { new CultureInfo("zh-HK").DateTimeFormat, "下午" }; - yield return new object[] { new CultureInfo("zh-TW").DateTimeFormat, "下午" }; - } - - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] - [MemberData(nameof(PMDesignator_Get_TestData_HybridGlobalization))] - public void PMDesignator_Get_ReturnsExpected_HybridGlobalization(DateTimeFormatInfo format, string value) - { - Assert.Equal(value, format.PMDesignator); - } - [Theory] [InlineData("")] [InlineData("PP")] diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoShortDatePattern.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoShortDatePattern.cs index 8922b19610e436..a15f1196481315 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoShortDatePattern.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoShortDatePattern.cs @@ -14,197 +14,6 @@ public static IEnumerable ShortDatePattern_Get_TestData_ICU() yield return new object[] { CultureInfo.GetCultureInfo("fr-FR").DateTimeFormat, "dd/MM/yyyy" }; } - public static IEnumerable ShortDatePattern_Get_TestData_HybridGlobalization() - { - // see the comments on the right to check the non-Hybrid result, if it differs - yield return new object[] { "ar-SA", "d\u200f/M\u200f/yyyy" }; // "d\u200f/M\u200f/yyyy g" - yield return new object[] { "am-ET", "dd/MM/yyyy" }; - yield return new object[] { "bg-BG", "d.MM.yyyy г." }; // "d.MM.yyyy 'г'." - yield return new object[] { "bn-BD", "d/M/yyyy" }; - yield return new object[] { "bn-IN", "d/M/yyyy" }; - yield return new object[] { "ca-AD", "d/M/yyyy" }; - yield return new object[] { "ca-ES", "d/M/yyyy" }; - yield return new object[] { "cs-CZ", "dd.MM.yyyy" }; - yield return new object[] { "da-DK", "dd.MM.yyyy" }; - yield return new object[] { "de-AT", "dd.MM.yyyy" }; - yield return new object[] { "de-BE", "dd.MM.yyyy" }; - yield return new object[] { "de-CH", "dd.MM.yyyy" }; - yield return new object[] { "de-DE", "dd.MM.yyyy" }; - yield return new object[] { "de-IT", "dd.MM.yyyy" }; - yield return new object[] { "de-LI", "dd.MM.yyyy" }; - yield return new object[] { "de-LU", "dd.MM.yyyy" }; - yield return new object[] { "el-CY", "d/M/yyyy" }; - yield return new object[] { "el-GR", "d/M/yyyy" }; - yield return new object[] { "en-AE", "dd/MM/yyyy" }; - yield return new object[] { "en-AG", "dd/MM/yyyy" }; - yield return new object[] { "en-AI", "dd/MM/yyyy" }; - yield return new object[] { "en-AS", "M/d/yyyy" }; - yield return new object[] { "en-AT", "dd/MM/yyyy" }; - yield return new object[] { "en-AU", "d/M/yyyy" }; - yield return new object[] { "en-BB", "dd/MM/yyyy" }; - yield return new object[] { "en-BE", "dd/MM/yyyy" }; - yield return new object[] { "en-BI", "M/d/yyyy" }; - yield return new object[] { "en-BM", "dd/MM/yyyy" }; - yield return new object[] { "en-BS", "dd/MM/yyyy" }; - yield return new object[] { "en-BW", "dd/MM/yyyy" }; - yield return new object[] { "en-BZ", "dd/MM/yyyy" }; - yield return new object[] { "en-CA", "yyyy-MM-dd" }; - yield return new object[] { "en-CC", "dd/MM/yyyy" }; - yield return new object[] { "en-CH", "dd.MM.yyyy" }; // "dd/MM/yyyy" - yield return new object[] { "en-CK", "dd/MM/yyyy" }; - yield return new object[] { "en-CM", "dd/MM/yyyy" }; - yield return new object[] { "en-CX", "dd/MM/yyyy" }; - yield return new object[] { "en-CY", "dd/MM/yyyy" }; - yield return new object[] { "en-DE", "dd/MM/yyyy" }; - yield return new object[] { "en-DK", "dd/MM/yyyy" }; - yield return new object[] { "en-DM", "dd/MM/yyyy" }; - yield return new object[] { "en-ER", "dd/MM/yyyy" }; - yield return new object[] { "en-FI", "dd/MM/yyyy" }; - yield return new object[] { "en-FJ", "dd/MM/yyyy" }; - yield return new object[] { "en-FK", "dd/MM/yyyy" }; - yield return new object[] { "en-FM", "dd/MM/yyyy" }; - yield return new object[] { "en-GB", "dd/MM/yyyy" }; - yield return new object[] { "en-GD", "dd/MM/yyyy" }; - yield return new object[] { "en-GG", "dd/MM/yyyy" }; - yield return new object[] { "en-GH", "dd/MM/yyyy" }; - yield return new object[] { "en-GI", "dd/MM/yyyy" }; - yield return new object[] { "en-GM", "dd/MM/yyyy" }; - yield return new object[] { "en-GU", "M/d/yyyy" }; - yield return new object[] { "en-GY", "dd/MM/yyyy" }; - yield return new object[] { "en-HK", "d/M/yyyy" }; - yield return new object[] { "en-IE", "dd/MM/yyyy" }; - yield return new object[] { "en-IL", "dd/MM/yyyy" }; - yield return new object[] { "en-IM", "dd/MM/yyyy" }; - yield return new object[] { "en-IN", "dd/MM/yyyy" }; - yield return new object[] { "en-IO", "dd/MM/yyyy" }; - yield return new object[] { "en-JE", "dd/MM/yyyy" }; - yield return new object[] { "en-JM", "dd/MM/yyyy" }; - yield return new object[] { "en-KE", "dd/MM/yyyy" }; - yield return new object[] { "en-KI", "dd/MM/yyyy" }; - yield return new object[] { "en-KN", "dd/MM/yyyy" }; - yield return new object[] { "en-KY", "dd/MM/yyyy" }; - yield return new object[] { "en-LC", "dd/MM/yyyy" }; - yield return new object[] { "en-LR", "dd/MM/yyyy" }; - yield return new object[] { "en-LS", "dd/MM/yyyy" }; - yield return new object[] { "en-MG", "dd/MM/yyyy" }; - yield return new object[] { "en-MH", "M/d/yyyy" }; - yield return new object[] { "en-MO", "dd/MM/yyyy" }; - yield return new object[] { "en-MP", "M/d/yyyy" }; - yield return new object[] { "en-MS", "dd/MM/yyyy" }; - yield return new object[] { "en-MT", "dd/MM/yyyy" }; - yield return new object[] { "en-MU", "dd/MM/yyyy" }; - yield return new object[] { "en-MW", "dd/MM/yyyy" }; - yield return new object[] { "en-MY", "dd/MM/yyyy" }; - yield return new object[] { "en-NA", "dd/MM/yyyy" }; - yield return new object[] { "en-NF", "dd/MM/yyyy" }; - yield return new object[] { "en-NG", "dd/MM/yyyy" }; - yield return new object[] { "en-NL", "dd/MM/yyyy" }; - yield return new object[] { "en-NR", "dd/MM/yyyy" }; - yield return new object[] { "en-NU", "dd/MM/yyyy" }; - yield return new object[] { "en-NZ", PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "d/MM/yyyy" : "dd/MM/yyyy" }; // "d/MM/yyyy" - yield return new object[] { "en-PG", "dd/MM/yyyy" }; - yield return new object[] { "en-PH", "M/d/yyyy" }; // "dd/MM/yyyy" - yield return new object[] { "en-PK", "dd/MM/yyyy" }; - yield return new object[] { "en-PN", "dd/MM/yyyy" }; - yield return new object[] { "en-PR", "M/d/yyyy" }; - yield return new object[] { "en-PW", "dd/MM/yyyy" }; - yield return new object[] { "en-RW", "dd/MM/yyyy" }; - yield return new object[] { "en-SB", "dd/MM/yyyy" }; - yield return new object[] { "en-SC", "dd/MM/yyyy" }; - yield return new object[] { "en-SD", "dd/MM/yyyy" }; - yield return new object[] { "en-SE", "yyyy-MM-dd" }; - yield return new object[] { "en-SG", "d/M/yyyy" }; - yield return new object[] { "en-SH", "dd/MM/yyyy" }; - yield return new object[] { "en-SI", "dd/MM/yyyy" }; - yield return new object[] { "en-SL", "dd/MM/yyyy" }; - yield return new object[] { "en-SS", "dd/MM/yyyy" }; - yield return new object[] { "en-SX", "dd/MM/yyyy" }; - yield return new object[] { "en-SZ", "dd/MM/yyyy" }; - yield return new object[] { "en-TC", "dd/MM/yyyy" }; - yield return new object[] { "en-TK", "dd/MM/yyyy" }; - yield return new object[] { "en-TO", "dd/MM/yyyy" }; - yield return new object[] { "en-TT", "dd/MM/yyyy" }; - yield return new object[] { "en-TV", "dd/MM/yyyy" }; - yield return new object[] { "en-TZ", "dd/MM/yyyy" }; - yield return new object[] { "en-UG", "dd/MM/yyyy" }; - yield return new object[] { "en-UM", "M/d/yyyy" }; - yield return new object[] { "en-US", "M/d/yyyy" }; - yield return new object[] { "en-VC", "dd/MM/yyyy" }; - yield return new object[] { "en-VG", "dd/MM/yyyy" }; - yield return new object[] { "en-VI", "M/d/yyyy" }; - yield return new object[] { "en-VU", "dd/MM/yyyy" }; - yield return new object[] { "en-WS", "dd/MM/yyyy" }; - yield return new object[] { "en-ZA", "yyyy/MM/dd" }; - yield return new object[] { "en-ZM", "dd/MM/yyyy" }; - yield return new object[] { "en-ZW", "d/M/yyyy" }; - yield return new object[] { "es-419", "d/M/yyyy" }; - yield return new object[] { "es-ES", "d/M/yyyy" }; - yield return new object[] { "es-MX", "dd/MM/yyyy" }; - yield return new object[] { "et-EE", "dd.MM.yyyy" }; - yield return new object[] { "fa-IR", "yyyy/M/d" }; // "yyyy/M/d" - yield return new object[] { "fi-FI", "d.M.yyyy" }; - yield return new object[] { "fil-PH", "M/d/yyyy" }; - yield return new object[] { "fr-BE", "d/MM/yyyy" }; - yield return new object[] { "fr-CA", "yyyy-MM-dd" }; - yield return new object[] { "fr-CH", "dd.MM.yyyy" }; - yield return new object[] { "fr-FR", "dd/MM/yyyy" }; - yield return new object[] { "gu-IN", "d/M/yyyy" }; - yield return new object[] { "he-IL", "d.M.yyyy" }; - yield return new object[] { "hi-IN", "d/M/yyyy" }; - yield return new object[] { "hr-BA", "d. M. yyyy." }; - yield return new object[] { "hr-HR", "dd. MM. yyyy." }; - yield return new object[] { "hu-HU", "yyyy. MM. dd." }; - yield return new object[] { "id-ID", "dd/MM/yyyy" }; - yield return new object[] { "it-CH", "dd.MM.yyyy" }; - yield return new object[] { "it-IT", "dd/MM/yyyy" }; - yield return new object[] { "ja-JP", "yyyy/MM/dd" }; - yield return new object[] { "kn-IN", "d/M/yyyy" }; - yield return new object[] { "ko-KR", "yyyy. M. d." }; - yield return new object[] { "lt-LT", "yyyy-MM-dd" }; - yield return new object[] { "lv-LV", "dd.MM.yyyy" }; - yield return new object[] { "ml-IN", "d/M/yyyy" }; - yield return new object[] { "mr-IN", "d/M/yyyy" }; - yield return new object[] { "ms-BN", "d/MM/yyyy" }; - yield return new object[] { "ms-MY", "d/MM/yyyy" }; - yield return new object[] { "ms-SG", "d/MM/yyyy" }; - yield return new object[] { "nb-NO", "dd.MM.yyyy" }; - yield return new object[] { "no", "dd.MM.yyyy" }; - yield return new object[] { "no-NO", "dd.MM.yyyy" }; - yield return new object[] { "nl-AW", "dd-MM-yyyy" }; - yield return new object[] { "nl-BE", "d/MM/yyyy" }; - yield return new object[] { "nl-NL", "dd-MM-yyyy" }; - yield return new object[] { "pl-PL", "d.MM.yyyy" }; // "dd.MM.yyyy" - yield return new object[] { "pt-BR", "dd/MM/yyyy" }; - yield return new object[] { "pt-PT", "dd/MM/yyyy" }; - yield return new object[] { "ro-RO", "dd.MM.yyyy" }; - yield return new object[] { "ru-RU", "dd.MM.yyyy" }; - yield return new object[] { "sk-SK", "d. M. yyyy" }; - yield return new object[] { "sl-SI", PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "d. MM. yyyy" : "d. M. yyyy" }; // "d. MM. yyyy" - yield return new object[] { "sr-Cyrl-RS", "d.M.yyyy." }; - yield return new object[] { "sr-Latn-RS", "d.M.yyyy." }; - yield return new object[] { "sv-AX", "yyyy-MM-dd" }; - yield return new object[] { "sv-SE", "yyyy-MM-dd" }; - yield return new object[] { "sw-CD", "dd/MM/yyyy" }; - yield return new object[] { "sw-KE", "dd/MM/yyyy" }; - yield return new object[] { "sw-TZ", "dd/MM/yyyy" }; - yield return new object[] { "sw-UG", "dd/MM/yyyy" }; - yield return new object[] { "ta-IN", "d/M/yyyy" }; - yield return new object[] { "ta-LK", "d/M/yyyy" }; - yield return new object[] { "ta-MY", "d/M/yyyy" }; - yield return new object[] { "ta-SG", "d/M/yyyy" }; - yield return new object[] { "te-IN", "dd-MM-yyyy" }; - yield return new object[] { "th-TH", "d/M/yyyy" }; - yield return new object[] { "tr-CY", "d.MM.yyyy" }; - yield return new object[] { "tr-TR", "d.MM.yyyy" }; - yield return new object[] { "uk-UA", "dd.MM.yyyy" }; - yield return new object[] { "vi-VN", PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "dd/MM/yyyy" : "d/M/yyyy" }; // "dd/MM/yyyy" - yield return new object[] { "zh-CN", "yyyy/M/d" }; - yield return new object[] { "zh-Hans-HK", "d/M/yyyy" }; - yield return new object[] { "zh-SG", "dd/MM/yyyy" }; - yield return new object[] { "zh-HK", "d/M/yyyy" }; - yield return new object[] { "zh-TW", "yyyy/M/d" }; - } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization))] [MemberData(nameof(ShortDatePattern_Get_TestData_ICU))] public void ShortDatePattern_Get_ReturnsExpected_ICU(DateTimeFormatInfo format, string expected) @@ -212,14 +21,6 @@ public void ShortDatePattern_Get_ReturnsExpected_ICU(DateTimeFormatInfo format, Assert.Equal(expected, format.ShortDatePattern); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] - [MemberData(nameof(ShortDatePattern_Get_TestData_HybridGlobalization))] - public void ShortDatePattern_Get_ReturnsExpected_HybridGlobalization(string cultureName, string expected) - { - var format = new CultureInfo(cultureName).DateTimeFormat; - Assert.True(expected == format.ShortDatePattern, $"Failed for culture: {cultureName}. Expected: {expected}, Actual: {format.ShortDatePattern}"); - } - [Fact] public void ShortDatePattern_InvariantInfo() { diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoShortTimePattern.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoShortTimePattern.cs index d806e19b7502cd..a3d73485f074a3 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoShortTimePattern.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoShortTimePattern.cs @@ -14,205 +14,6 @@ public void ShortTimePattern_GetInvariantInfo_ReturnsExpected() Assert.Equal("HH:mm", DateTimeFormatInfo.InvariantInfo.ShortTimePattern); } - public static IEnumerable ShortTimePattern_Get_TestData_HybridGlobalization() - { - // see the comments on the right to check the non-Hybrid result, if it differs - yield return new object[] { new CultureInfo("ar-SA").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("am-ET").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("bg-BG").DateTimeFormat, "H:mm" }; // HH:mm - yield return new object[] { new CultureInfo("bn-BD").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("bn-IN").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("ca-AD").DateTimeFormat, "H:mm" }; - yield return new object[] { new CultureInfo("ca-ES").DateTimeFormat, "H:mm" }; - yield return new object[] { new CultureInfo("cs-CZ").DateTimeFormat, "H:mm" }; - yield return new object[] { new CultureInfo("da-DK").DateTimeFormat, "HH.mm" }; - yield return new object[] { new CultureInfo("de-AT").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("de-BE").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("de-CH").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("de-DE").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("de-IT").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("de-LI").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("de-LU").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("el-CY").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("el-GR").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-AE").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-AG").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-AI").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-AS").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-AT").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-AU").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-BB").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-BE").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-BI").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-BM").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-BS").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-BW").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-BZ").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-CA").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-CC").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-CH").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-CK").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-CM").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-CX").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-CY").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-DE").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-DK").DateTimeFormat, "HH.mm" }; - yield return new object[] { new CultureInfo("en-DM").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-ER").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-FI").DateTimeFormat, "H.mm" }; - yield return new object[] { new CultureInfo("en-FJ").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-FK").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-FM").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-GB").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-GD").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-GG").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-GH").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-GI").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-GM").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-GU").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-GY").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-HK").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-IE").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-IL").DateTimeFormat, "H:mm" }; - yield return new object[] { new CultureInfo("en-IM").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-IN").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-IO").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-JE").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-JM").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-KE").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-KI").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-KN").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-KY").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-LC").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-LR").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-LS").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-MG").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-MH").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-MO").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-MP").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-MS").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-MT").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-MU").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-MW").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-MY").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-NA").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-NF").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-NG").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-NL").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-NR").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-NU").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-NZ").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-PG").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-PH").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-PK").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-PN").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-PR").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-PW").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-RW").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-SB").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-SC").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-SD").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-SE").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-SG").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-SH").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-SI").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-SL").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-SS").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-SX").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-SZ").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-TC").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-TK").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-TO").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-TT").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-TV").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-TZ").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-UG").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-UM").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-US").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-VC").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-VG").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-VI").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-VU").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-WS").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-ZA").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-ZM").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("en-ZW").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("en-US").DateTimeFormat, "h:mm tt" }; - string latinAmericanSpanishPattern = PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "HH:mm" : "h:mm tt"; // "HH:mm" - yield return new object[] { new CultureInfo("es-419").DateTimeFormat, latinAmericanSpanishPattern }; - yield return new object[] { new CultureInfo("es-ES").DateTimeFormat, "H:mm" }; - yield return new object[] { new CultureInfo("es-MX").DateTimeFormat, latinAmericanSpanishPattern }; - yield return new object[] { new CultureInfo("et-EE").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("fa-IR").DateTimeFormat, "H:mm" }; - yield return new object[] { new CultureInfo("fi-FI").DateTimeFormat, "H.mm" }; - yield return new object[] { new CultureInfo("fil-PH").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("fr-BE").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("fr-CA").DateTimeFormat, "HH 'h' mm 'min'" }; // HH 'h' mm - yield return new object[] { new CultureInfo("fr-CH").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("fr-FR").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("gu-IN").DateTimeFormat, "hh:mm tt" }; - yield return new object[] { new CultureInfo("he-IL").DateTimeFormat, "H:mm" }; - yield return new object[] { new CultureInfo("hi-IN").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("hr-BA").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("hr-HR").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("hu-HU").DateTimeFormat, "H:mm" }; - yield return new object[] { new CultureInfo("id-ID").DateTimeFormat, "HH.mm" }; - yield return new object[] { new CultureInfo("it-CH").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("it-IT").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("ja-JP").DateTimeFormat, "H:mm" }; - yield return new object[] { new CultureInfo("kn-IN").DateTimeFormat, "hh:mm tt" }; - yield return new object[] { new CultureInfo("ko-KR").DateTimeFormat, "tt h:mm" }; - yield return new object[] { new CultureInfo("lt-LT").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("lv-LV").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("ml-IN").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("mr-IN").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("ms-BN").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("ms-MY").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("ms-SG").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("nb-NO").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("no-NO").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("nl-AW").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("nl-BE").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("nl-NL").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("pl-PL").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("pt-BR").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("pt-PT").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("ro-RO").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("ru-RU").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("sk-SK").DateTimeFormat, "H:mm" }; - yield return new object[] { new CultureInfo("sl-SI").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("sr-Cyrl-RS").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("sr-Latn-RS").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("sv-AX").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("sv-SE").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("sw-CD").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("sw-KE").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("sw-TZ").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("sw-UG").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("ta-IN").DateTimeFormat, "tt h:mm" }; - yield return new object[] { new CultureInfo("ta-LK").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("ta-MY").DateTimeFormat, "tt h:mm" }; - yield return new object[] { new CultureInfo("ta-SG").DateTimeFormat, "tt h:mm" }; - yield return new object[] { new CultureInfo("te-IN").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("th-TH").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("tr-CY").DateTimeFormat, "h:mm tt" }; - yield return new object[] { new CultureInfo("tr-TR").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("uk-UA").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("vi-VN").DateTimeFormat, "HH:mm" }; - yield return new object[] { new CultureInfo("zh-CN").DateTimeFormat, "HH:mm" }; // tth:mm - yield return new object[] { new CultureInfo("zh-Hans-HK").DateTimeFormat, "tth:mm" }; - yield return new object[] { new CultureInfo("zh-SG").DateTimeFormat, "tth:mm" }; - yield return new object[] { new CultureInfo("zh-HK").DateTimeFormat, "tth:mm" }; - yield return new object[] { new CultureInfo("zh-TW").DateTimeFormat, "tth:mm" }; - } - - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] - [MemberData(nameof(ShortTimePattern_Get_TestData_HybridGlobalization))] - public void ShortTimePattern_Get_ReturnsExpected_HybridGlobalization(DateTimeFormatInfo format, string value) - { - Assert.Equal(value, format.ShortTimePattern); - } - public static IEnumerable ShortTimePattern_Set_TestData() { yield return new object[] { string.Empty }; diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoShortestDayNames.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoShortestDayNames.cs index f99c694d3bb2f5..6e8d620a50102d 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoShortestDayNames.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoShortestDayNames.cs @@ -28,76 +28,6 @@ public static IEnumerable ShortestDayNames_Set_TestData() yield return new object[] { new string[] { "", "", "", "", "", "", "" } }; } - public static IEnumerable ShortestDayNames_Get_TestData_HybridGlobalization() - { - yield return new object[] { new CultureInfo("ar-SA").DateTimeFormat, new string[] { "ح", "ن", "ث", "ر", "خ", "ج", "س" } }; - yield return new object[] { new CultureInfo("am-ET").DateTimeFormat, new string[] { "እ", "ሰ", "ማ", "ረ", "ሐ", "ዓ", "ቅ" } }; - yield return new object[] { new CultureInfo("bg-BG").DateTimeFormat, new string[] { "н", "п", "в", "с", "ч", "п", "с" } }; - yield return new object[] { new CultureInfo("bn-IN").DateTimeFormat, new string[] { "র", "সো", "ম", "বু", "বৃ", "শু", "শ" } }; - if (PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS) - { - yield return new object[] { new CultureInfo("ca-ES").DateTimeFormat, new string[] { "dg", "dl", "dt", "dc", "dj", "dv", "ds" } }; - yield return new object[] { new CultureInfo("en-AU").DateTimeFormat, new string[] { "Su.", "M.", "Tu.", "W.", "Th.", "F.", "Sa." } }; - } - else - { - yield return new object[] { new CultureInfo("ca-ES").DateTimeFormat, new string[] { "dg.", "dl.", "dt.", "dc.", "dj.", "dv.", "ds." } }; - yield return new object[] { new CultureInfo("en-AU").DateTimeFormat, new string[] { "S", "M", "T", "W", "T", "F", "S" } }; // "Su.", "M.", "Tu.", "W.", "Th.", "F.", "Sa." - } - - yield return new object[] { new CultureInfo("cs-CZ").DateTimeFormat, new string[] { "N", "P", "Ú", "S", "Č", "P", "S" } }; - yield return new object[] { new CultureInfo("da-DK").DateTimeFormat, new string[] { "S", "M", "T", "O", "T", "F", "L" } }; - yield return new object[] { new CultureInfo("de-LU").DateTimeFormat, new string[] { "S", "M", "D", "M", "D", "F", "S" } }; - yield return new object[] { new CultureInfo("el-CY").DateTimeFormat, new string[] { "Κ", "Δ", "Τ", "Τ", "Π", "Π", "Σ" } }; - yield return new object[] { new CultureInfo("en-GB").DateTimeFormat, new string[] { "S", "M", "T", "W", "T", "F", "S" } }; - yield return new object[] { new CultureInfo("es-419").DateTimeFormat, new string[] { "D", "L", "M", "M", "J", "V", "S" } }; - yield return new object[] { new CultureInfo("es-ES").DateTimeFormat, new string[] { "D", "L", "M", "X", "J", "V", "S" } }; - yield return new object[] { new CultureInfo("et-EE").DateTimeFormat, new string[] { "P", "E", "T", "K", "N", "R", "L" } }; - yield return new object[] { new CultureInfo("fa-IR").DateTimeFormat, new string[] { "ی", "د", "س", "چ", "پ", "ج", "ش" } }; - yield return new object[] { new CultureInfo("fi-FI").DateTimeFormat, new string[] { "S", "M", "T", "K", "T", "P", "L" } }; - yield return new object[] { new CultureInfo("fil-PH").DateTimeFormat, new string[] { "Lin", "Lun", "Mar", "Miy", "Huw", "Biy", "Sab" } }; - yield return new object[] { new CultureInfo("fr-CA").DateTimeFormat, new string[] { "D", "L", "M", "M", "J", "V", "S" } }; - yield return new object[] { new CultureInfo("gu-IN").DateTimeFormat, new string[] { "ર", "સો", "મં", "બુ", "ગુ", "શુ", "શ" } }; - yield return new object[] { new CultureInfo("he-IL").DateTimeFormat, new string[] { "א׳", "ב׳", "ג׳", "ד׳", "ה׳", "ו׳", "ש׳" } }; - yield return new object[] { new CultureInfo("hi-IN").DateTimeFormat, new string[] { "र", "सो", "मं", "बु", "गु", "शु", "श" } }; - yield return new object[] { new CultureInfo("hr-BA").DateTimeFormat, new string[] { "N", "P", "U", "S", "Č", "P", "S" } }; - yield return new object[] { new CultureInfo("hr-HR").DateTimeFormat, new string[] { "n", "p", "u", "s", "č", "p", "s" } }; - yield return new object[] { new CultureInfo("hu-HU").DateTimeFormat, new string[] { "V", "H", "K", "Sz", "Cs", "P", "Sz" } }; - yield return new object[] { new CultureInfo("id-ID").DateTimeFormat, new string[] { "M", "S", "S", "R", "K", "J", "S" } }; - yield return new object[] { new CultureInfo("it-CH").DateTimeFormat, new string[] { "D", "L", "M", "M", "G", "V", "S" } }; - yield return new object[] { new CultureInfo("ja-JP").DateTimeFormat, new string[] { "日", "月", "火", "水", "木", "金", "土" } }; - yield return new object[] { new CultureInfo("kn-IN").DateTimeFormat, new string[] { "ಭಾ", "ಸೋ", "ಮಂ", "ಬು", "ಗು", "ಶು", "ಶ" } }; - yield return new object[] { new CultureInfo("ko-KR").DateTimeFormat, new string[] { "일", "월", "화", "수", "목", "금", "토" } }; - yield return new object[] { new CultureInfo("lt-LT").DateTimeFormat, new string[] { "S", "P", "A", "T", "K", "P", "Š" } }; - yield return new object[] { new CultureInfo("lv-LV").DateTimeFormat, new string[] { "S", "P", "O", "T", "C", "P", "S" } }; - yield return new object[] { new CultureInfo("ml-IN").DateTimeFormat, new string[] { "ഞാ", "തി", "ചൊ", "ബു", "വ്യാ", "വെ", "ശ" } }; - yield return new object[] { new CultureInfo("ms-BN").DateTimeFormat, new string[] { "A", "I", "S", "R", "K", "J", "S" } }; - yield return new object[] { new CultureInfo("nb-NO").DateTimeFormat, new string[] { "S", "M", "T", "O", "T", "F", "L" } }; - yield return new object[] { new CultureInfo("nl-NL").DateTimeFormat, new string[] { "Z", "M", "D", "W", "D", "V", "Z" } }; - yield return new object[] { new CultureInfo("pl-PL").DateTimeFormat, new string[] { "N", "P", "W", "Ś", "C", "P", "S" } }; - yield return new object[] { new CultureInfo("pt-PT").DateTimeFormat, new string[] { "D", "S", "T", "Q", "Q", "S", "S" } }; - yield return new object[] { new CultureInfo("ro-RO").DateTimeFormat, new string[] { "D", "L", "M", "M", "J", "V", "S" } }; - yield return new object[] { new CultureInfo("ru-RU").DateTimeFormat, new string[] { "В", "П", "В", "С", "Ч", "П", "С" } }; - yield return new object[] { new CultureInfo("sk-SK").DateTimeFormat, new string[] { "n", "p", "u", "s", "š", "p", "s" } }; - yield return new object[] { new CultureInfo("sl-SI").DateTimeFormat, new string[] { "n", "p", "t", "s", "č", "p", "s" } }; - yield return new object[] { new CultureInfo("sr-Cyrl-RS").DateTimeFormat, new string[] { "н", "п", "у", "с", "ч", "п", "с" } }; - yield return new object[] { new CultureInfo("sw-CD").DateTimeFormat, new string[] { "S", "M", "T", "W", "T", "F", "S" } }; - yield return new object[] { new CultureInfo("ta-IN").DateTimeFormat, new string[] { "ஞா", "தி", "செ", "பு", "வி", "வெ", "ச" } }; - yield return new object[] { new CultureInfo("te-IN").DateTimeFormat, new string[] { "ఆ", "సో", "మ", "బు", "గు", "శు", "శ" } }; - yield return new object[] { new CultureInfo("th-TH").DateTimeFormat, new string[] { "อา", "จ", "อ", "พ", "พฤ", "ศ", "ส" } }; - yield return new object[] { new CultureInfo("tr-CY").DateTimeFormat, new string[] { "P", "P", "S", "Ç", "P", "C", "C" } }; - yield return new object[] { new CultureInfo("uk-UA").DateTimeFormat, new string[] { "Н", "П", "В", "С", "Ч", "П", "С" } }; - yield return new object[] { new CultureInfo("vi-VN").DateTimeFormat, new string[] { "CN", "T2", "T3", "T4", "T5", "T6", "T7" } }; - yield return new object[] { new CultureInfo("zh-CN").DateTimeFormat, new string[] { "日", "一", "二", "三", "四", "五", "六" } }; - } - - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] - [MemberData(nameof(ShortestDayNames_Get_TestData_HybridGlobalization))] - public void ShortestDayNames_Get_ReturnsExpected_HybridGlobalization(DateTimeFormatInfo format, string[] expected) - { - Assert.Equal(expected, format.ShortestDayNames); - } - [Theory] [MemberData(nameof(ShortestDayNames_Set_TestData))] public void ShortestDayNames_Set_GetReturnsExpected(string[] value) diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoTests.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoTests.cs index 6e00c34d4ca926..991e05f1f4f908 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoTests.cs @@ -138,7 +138,7 @@ public void GetShortestDayName_Invoke_ReturnsExpected(string cultureName) } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [Fact] public void Months_GetHebrew_ReturnsExpected() { CultureInfo ci = new CultureInfo("he-IL"); diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoYearMonthPattern.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoYearMonthPattern.cs index 4c4d01b957b40a..53c0028bf3c7bc 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoYearMonthPattern.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/DateTimeFormatInfo/DateTimeFormatInfoYearMonthPattern.cs @@ -12,30 +12,6 @@ public static IEnumerable YearMonthPattern_Get_TestData() { yield return new object[] { DateTimeFormatInfo.InvariantInfo, "yyyy MMMM" }; yield return new object[] { new CultureInfo("fr-FR").DateTimeFormat, "MMMM yyyy" }; - if (PlatformDetection.IsHybridGlobalizationOnBrowser) - { - // see the comments on the right to check the non-Hybrid result, if it differs - yield return new object[] { new CultureInfo("ar-SA").DateTimeFormat, "MMMM yyyy" }; // "MMMM yyyy g" - yield return new object[] { new CultureInfo("bg-BG").DateTimeFormat, "MMMM yyyy \u0433." }; // ICU: "MMMM yyyy '\u0433'." - yield return new object[] { new CultureInfo("ca-AD").DateTimeFormat, PlatformDetection.IsFirefox || PlatformDetection.IsNodeJS ? "MMMM de yyyy" : "MMMM del yyyy" }; // ICU: "MMMM 'de' yyyy" - yield return new object[] { new CultureInfo("es-419").DateTimeFormat, "MMMM de yyyy" }; // ICU: "MMMM 'de' yyyy" - yield return new object[] { new CultureInfo("es-ES").DateTimeFormat, "MMMM de yyyy" }; // ICU: "MMMM 'de' yyyy" - yield return new object[] { new CultureInfo("es-MX").DateTimeFormat, "MMMM de yyyy" }; // ICU: "MMMM 'de' yyyy" - yield return new object[] { new CultureInfo("fa-IR").DateTimeFormat, "yyyy MMMM" }; - yield return new object[] { new CultureInfo("hr-HR").DateTimeFormat, "MMMM yyyy." }; - yield return new object[] { new CultureInfo("hu-HU").DateTimeFormat, "yyyy. MMMM" }; - yield return new object[] { new CultureInfo("ja-JP").DateTimeFormat, "yyyy\u5e74M\u6708" }; - yield return new object[] { new CultureInfo("ko-KR").DateTimeFormat, "yyyy\ub144 MMMM" }; - yield return new object[] { new CultureInfo("lt-LT").DateTimeFormat, "yyyy m. MMMM" }; // ICU: "yyyy 'm'. MMMM" - yield return new object[] { new CultureInfo("lv-LV").DateTimeFormat, "yyyy. g. MMMM" }; // ICU: "yyyy. 'g'. MMMM" - yield return new object[] { new CultureInfo("ml-IN").DateTimeFormat, "yyyy MMMM" }; - yield return new object[] { new CultureInfo("pt-PT").DateTimeFormat, "MMMM de yyyy" }; // ICU: "MMMM 'de' yyyy" - yield return new object[] { new CultureInfo("ru-RU").DateTimeFormat, "MMMM yyyy \u0433." }; // ICU: "MMMM yyyy '\u0433'." - yield return new object[] { new CultureInfo("sr-Latn-RS").DateTimeFormat, "MMMM yyyy." }; - yield return new object[] { new CultureInfo("vi-VN").DateTimeFormat, "MMMM n\u0103m yyyy" }; // ICU: "MMMM 'n\u0103m' yyyy" - yield return new object[] { new CultureInfo("zh-CN").DateTimeFormat, "yyyy\u5e74M\u6708" }; - yield return new object[] { new CultureInfo("zh-TW").DateTimeFormat, "yyyy\u5e74M\u6708" }; - } } [Theory] diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/Hybrid/System.Globalization.Hybrid.WASM.Tests.csproj b/src/libraries/System.Runtime/tests/System.Globalization.Tests/Hybrid/System.Globalization.Hybrid.WASM.Tests.csproj deleted file mode 100644 index 8c90eef092f036..00000000000000 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/Hybrid/System.Globalization.Hybrid.WASM.Tests.csproj +++ /dev/null @@ -1,61 +0,0 @@ - - - $(NetCoreAppCurrent)-browser - true - true - true - true - - - - WasmTestOnChrome - $(TestArchiveRoot)browseronly/ - $(TestArchiveTestsRoot)$(OSPlatformConfig)/ - $(DefineConstants);TARGET_BROWSER - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/TextInfoTests.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/TextInfoTests.cs index 466be98c442d9f..924de536b6efbd 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/TextInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/TextInfoTests.cs @@ -274,15 +274,7 @@ public static IEnumerable ToLower_TestData() // we also don't preform. // Greek Capital Letter Sigma (does not case to U+03C2 with "final sigma" rule). yield return new object[] { cultureName, "\u03A3", "\u03C3" }; - if (PlatformDetection.IsHybridGlobalizationOnBrowser) - { - // JS is using "final sigma" rule correctly - it's costly to unify it with ICU's behavior - yield return new object[] { cultureName, "O\u03A3", "o\u03C2" }; - } - else - { - yield return new object[] { cultureName, "O\u03A3", "o\u03C3" }; - } + yield return new object[] { cultureName, "O\u03A3", "o\u03C3" }; } foreach (string cultureName in GetTestLocales()) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/StringComparer.cs b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/StringComparer.cs index bb69f0df97a3ec..fa2a92c932cefe 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/StringComparer.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/StringComparer.cs @@ -87,8 +87,7 @@ public static IEnumerable UpperLowerCasing_TestData() yield return new object[] { "abcd", "ABCD", "en-US" }; yield return new object[] { "latin i", "LATIN I", "en-US" }; - // https://github.com/dotnet/runtime/issues/95503 - if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnBrowser && !PlatformDetection.IsAndroid && !PlatformDetection.IsLinuxBionic) + if (PlatformDetection.IsNotInvariantGlobalization && !PlatformDetection.IsAndroid && !PlatformDetection.IsLinuxBionic) { yield return new object[] { "turky \u0131", "TURKY I", "tr-TR" }; yield return new object[] { "turky i", "TURKY \u0130", "tr-TR" }; @@ -182,12 +181,11 @@ public static IEnumerable CreateFromCultureAndOptionsData() if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) { - bool ignoreSymbolsIgnoresOnlyPunctuation = PlatformDetection.IsHybridGlobalizationOnBrowser; yield return new object[] { "abcd", "ab cd", "en-US", CompareOptions.IgnoreSymbols, true }; - yield return new object[] { "abcd", "ab+cd", "en-US", CompareOptions.IgnoreSymbols, !ignoreSymbolsIgnoresOnlyPunctuation }; + yield return new object[] { "abcd", "ab+cd", "en-US", CompareOptions.IgnoreSymbols, true }; yield return new object[] { "abcd", "ab%cd", "en-US", CompareOptions.IgnoreSymbols, true }; yield return new object[] { "abcd", "ab&cd", "en-US", CompareOptions.IgnoreSymbols, true }; - yield return new object[] { "abcd", "ab$cd", "en-US", CompareOptions.IgnoreSymbols, !ignoreSymbolsIgnoresOnlyPunctuation }; + yield return new object[] { "abcd", "ab$cd", "en-US", CompareOptions.IgnoreSymbols, true }; yield return new object[] { "a-bcd", "ab$cd", "en-US", CompareOptions.IgnoreSymbols, true }; yield return new object[] { "abcd*", "ab$cd", "en-US", CompareOptions.IgnoreSymbols, true }; yield return new object[] { "ab$dd", "ab$cd", "en-US", CompareOptions.IgnoreSymbols, false }; @@ -200,7 +198,7 @@ public static IEnumerable CreateFromCultureAndOptionsData() { "abcd", "ABcd", "en-US", CompareOptions.StringSort, false }, }; - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))] [MemberData(nameof(CreateFromCultureAndOptionsData))] [MemberData(nameof(CreateFromCultureAndOptionsStringSortData))] public static void CreateFromCultureAndOptions(string actualString, string expectedString, string cultureName, CompareOptions options, bool result) @@ -212,7 +210,7 @@ public static void CreateFromCultureAndOptions(string actualString, string expec Assert.Equal(result, sc.Equals((object)actualString, (object)expectedString)); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser))] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))] [MemberData(nameof(CreateFromCultureAndOptionsData))] public static void CreateFromCultureAndOptionsStringSort(string actualString, string expectedString, string cultureName, CompareOptions options, bool result) { diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringGetHashCodeTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringGetHashCodeTests.cs index d6af781605accc..ab646a0941162f 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringGetHashCodeTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringGetHashCodeTests.cs @@ -91,7 +91,7 @@ public static IEnumerable GetHashCodeOrdinalIgnoreCase_TestData() yield return new object[] { "AaBbCcDdEeFfGgHh".Insert(i, "\u00E9" /* LATIN SMALL LETTER E WITH ACUTE */) }; yield return new object[] { "AaBbCcDdEeFfGgHh".Insert(i, "\u044D" /* CYRILLIC SMALL LETTER E */) }; // https://github.com/dotnet/runtime/issues/95503 - if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform && PlatformDetection.IsNotHybridGlobalizationOnBrowser) + if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform) yield return new object[] { "AaBbCcDdEeFfGgHh".Insert(i, "\u0131" /* LATIN SMALL LETTER DOTLESS I */) }; } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringTests.cs index 230ede85d79e06..c512ac077c86a9 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/StringTests.cs @@ -781,21 +781,17 @@ public static IEnumerable Replace_StringComparison_TestData() { yield return new object[] { "abc", "abc" + SoftHyphen, "def", StringComparison.InvariantCultureIgnoreCase, "def" }; - // https://github.com/dotnet/runtime/issues/95503 - if (!PlatformDetection.IsHybridGlobalizationOnBrowser) - { - string turkishSource = "\u0069\u0130"; + string turkishSource = "\u0069\u0130"; - yield return new object[] { turkishSource, "\u0069", "a", StringComparison.Ordinal, "a\u0130" }; - yield return new object[] { turkishSource, "\u0069", "a", StringComparison.OrdinalIgnoreCase, "a\u0130" }; - yield return new object[] { turkishSource, "\u0130", "a", StringComparison.Ordinal, "\u0069a" }; - yield return new object[] { turkishSource, "\u0130", "a", StringComparison.OrdinalIgnoreCase, "\u0069a" }; + yield return new object[] { turkishSource, "\u0069", "a", StringComparison.Ordinal, "a\u0130" }; + yield return new object[] { turkishSource, "\u0069", "a", StringComparison.OrdinalIgnoreCase, "a\u0130" }; + yield return new object[] { turkishSource, "\u0130", "a", StringComparison.Ordinal, "\u0069a" }; + yield return new object[] { turkishSource, "\u0130", "a", StringComparison.OrdinalIgnoreCase, "\u0069a" }; - yield return new object[] { turkishSource, "\u0069", "a", StringComparison.InvariantCulture, "a\u0130" }; - yield return new object[] { turkishSource, "\u0069", "a", StringComparison.InvariantCultureIgnoreCase, "a\u0130" }; - yield return new object[] { turkishSource, "\u0130", "a", StringComparison.InvariantCulture, "\u0069a" }; - yield return new object[] { turkishSource, "\u0130", "a", StringComparison.InvariantCultureIgnoreCase, "\u0069a" }; - } + yield return new object[] { turkishSource, "\u0069", "a", StringComparison.InvariantCulture, "a\u0130" }; + yield return new object[] { turkishSource, "\u0069", "a", StringComparison.InvariantCultureIgnoreCase, "a\u0130" }; + yield return new object[] { turkishSource, "\u0130", "a", StringComparison.InvariantCulture, "\u0069a" }; + yield return new object[] { turkishSource, "\u0130", "a", StringComparison.InvariantCultureIgnoreCase, "\u0069a" }; } // To catch regressions when dealing with zero-length "this" inputs @@ -804,8 +800,8 @@ public static IEnumerable Replace_StringComparison_TestData() yield return new object[] { "", "\0", "y", StringComparison.InvariantCulture, "" }; } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform))] - [MemberData(nameof(Replace_StringComparison_TestData))] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform))] + [MemberData(nameof(Replace_StringComparison_TestData))] public void Replace_StringComparison_ReturnsExpected(string original, string oldValue, string newValue, StringComparison comparisonType, string expected) { Assert.Equal(expected, original.Replace(oldValue, newValue, comparisonType)); @@ -813,7 +809,6 @@ public void Replace_StringComparison_ReturnsExpected(string original, string old [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform))] [ActiveIssue("https://github.com/dotnet/runtime/issues/60568", TestPlatforms.Android | TestPlatforms.LinuxBionic)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/95503", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] public void Replace_StringComparison_TurkishI() { const string Source = "\u0069\u0130"; @@ -873,7 +868,6 @@ public static IEnumerable Replace_StringComparisonCulture_TestData() [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform))] [MemberData(nameof(Replace_StringComparisonCulture_TestData))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/95503", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnBrowser))] public void Replace_StringComparisonCulture_ReturnsExpected(string original, string oldValue, string newValue, bool ignoreCase, CultureInfo culture, string expected) { Assert.Equal(expected, original.Replace(oldValue, newValue, ignoreCase, culture)); @@ -897,7 +891,7 @@ public void Replace_StringComparison_EmptyOldValue_ThrowsArgumentException() AssertExtensions.Throws("oldValue", () => "abc".Replace("", "def", true, CultureInfo.CurrentCulture)); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform))] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform))] public void Replace_StringComparison_WeightlessOldValue_WithOrdinalComparison_Succeeds() { Assert.Equal("abcdef", ("abc" + ZeroWidthJoiner).Replace(ZeroWidthJoiner, "def")); @@ -905,7 +899,7 @@ public void Replace_StringComparison_WeightlessOldValue_WithOrdinalComparison_Su Assert.Equal("abcdef", ("abc" + ZeroWidthJoiner).Replace(ZeroWidthJoiner, "def", StringComparison.OrdinalIgnoreCase)); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnBrowser), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform))] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform))] public void Replace_StringComparison_WeightlessOldValue_WithLinguisticComparison_TerminatesReplacement() { Assert.Equal("abc" + ZeroWidthJoiner + "def", ("abc" + ZeroWidthJoiner + "def").Replace(ZeroWidthJoiner, "xyz", StringComparison.CurrentCulture)); diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Text/RuneTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Text/RuneTests.cs index 578d09f3eaad69..cac581e03e3609 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Text/RuneTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Text/RuneTests.cs @@ -57,9 +57,8 @@ public static void Casing_Invariant(int original, int upper, int lower) Assert.Equal(new Rune(lower), Rune.ToLowerInvariant(rune)); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalizationAndNotHybridOnBrowser), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform))] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform))] // HybridGlobalization on Apple mobile platforms has issues with casing dotless I - // HybridGlobalization on Browser uses Invariant HashCode and SortKey, so its effect does not match this of ICU [InlineData('0', '0', '0')] [InlineData('a', 'A', 'a')] [InlineData('i', 'I', 'i')] diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index a30405c85421db..fae36df0a3f5b1 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -83,12 +83,6 @@ - - - - - - @@ -394,12 +388,6 @@ - - - - - - diff --git a/src/mono/browser/README.md b/src/mono/browser/README.md index 202f36683e040e..ca9e5893a50f39 100644 --- a/src/mono/browser/README.md +++ b/src/mono/browser/README.md @@ -319,14 +319,12 @@ npm update --lockfile-version=1 * `runtime-wasm*` pipelines are triggered manually, and they only run the jobs that would not run on any default pipelines based on path changes. * The `AOT` jobs run only smoke tests on `runtime`, and on `runtime-wasm*` pipelines all the `AOT` tests are run. -* HG libtests are library test with `HybridGlobalization=true` | . | runtime-wasm | runtime-wasm-libtests | runtime-wasm-non-libtests | | ----------------- | -------------------------- | -------------------- | -------------------- | | libtests | linux+windows: all | linux+windows: all | none | | libtests eat | linux: all | linux: all | none | | libtests aot | linux+windows: all | linux+windows: all | none | -| libtests hg | linux+windows: all | linux+windows: all | none | | high resource aot | linux+windows: all | linux+windows: all | none | | Wasm.Build.Tests | linux+windows | none | linux+windows | | Debugger tests | linux+windows | none | linux+windows | diff --git a/src/mono/wasm/Wasm.Build.Tests/HybridGlobalizationTests.cs b/src/mono/wasm/Wasm.Build.Tests/HybridGlobalizationTests.cs deleted file mode 100644 index 8d1a861dc213cc..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/HybridGlobalizationTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.IO; -using Xunit; -using Xunit.Abstractions; - -#nullable enable - -namespace Wasm.Build.Tests -{ - public class HybridGlobalizationTests : TestMainJsTestBase - { - public HybridGlobalizationTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) - { - } - - public static IEnumerable HybridGlobalizationTestData(bool aot, RunHost host) - => ConfigWithAOTData(aot) - .WithRunHosts(host) - .UnwrapItemsAsArrays(); - - [Theory] - [MemberData(nameof(HybridGlobalizationTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - [MemberData(nameof(HybridGlobalizationTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - public void AOT_HybridGlobalizationTests(BuildArgs buildArgs, RunHost host, string id) - => TestHybridGlobalizationTests(buildArgs, host, id); - - [Theory] - [MemberData(nameof(HybridGlobalizationTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - public void RelinkingWithoutAOT(BuildArgs buildArgs, RunHost host, string id) - => TestHybridGlobalizationTests(buildArgs, host, id, - extraProperties: "true", - dotnetWasmFromRuntimePack: false); - - private void TestHybridGlobalizationTests(BuildArgs buildArgs, RunHost host, string id, string extraProperties="", bool? dotnetWasmFromRuntimePack=null) - { - string projectName = $"hybrid"; - extraProperties = $"{extraProperties}true"; - - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, extraProperties); - - if (dotnetWasmFromRuntimePack == null) - dotnetWasmFromRuntimePack = IsDotnetWasmFromRuntimePack(buildArgs); - - string programText = File.ReadAllText(Path.Combine(BuildEnvironment.TestAssetsPath, "Wasm.Buid.Tests.Programs", "HybridGlobalization.cs")); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, - GlobalizationMode: GlobalizationMode.Hybrid)); - - string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id); - Assert.Contains("HybridGlobalization works, thrown exception as expected", output); - } - } -} diff --git a/src/mono/wasm/features.md b/src/mono/wasm/features.md index 31d281ba389a1d..c13dde952952c9 100644 --- a/src/mono/wasm/features.md +++ b/src/mono/wasm/features.md @@ -259,8 +259,6 @@ For some use cases, you may wish to override this behavior or create a custom IC There are also rare use cases where your application does not rely on the contents of the ICU databases. In those scenarios, you can make your application smaller by enabling Invariant Globalization via the `true` msbuild property. For more details see [globalization-invariant-mode.md](../../../docs/design/features/globalization-invariant-mode.md). -We are currently developing a third approach for locales where we offer a more limited feature set by relying on browser APIs, called "Hybrid Globalization". This provides more functionality than Invariant Culture mode without the need to ship the ICU library or its databases, which improves startup time. You can use the msbuild property `true` to test this in-development feature, but be aware that it is currently incomplete and may have performance issues. For more details see [globalization-hybrid-mode.md](../../../docs/design/features/globalization-hybrid-mode.md). - Customized globalization settings require [wasm-tools workload](#wasm-tools-workload) to be installed. ### Timezones From d97abb18d4866e7b519ae41156e304624a198691 Mon Sep 17 00:00:00 2001 From: Alex4414 Date: Wed, 11 Dec 2024 20:05:01 +0300 Subject: [PATCH 38/70] fix wrong arguments order in CrlCacheExpired call (#110457) Arguments nextUpdate and verificationTime should be passed in different order --- .../Security/Cryptography/X509Certificates/OpenSslCrlCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslCrlCache.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslCrlCache.cs index 52b6b967c0640d..494e5db0d5305b 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslCrlCache.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslCrlCache.cs @@ -171,7 +171,7 @@ private static bool AddCachedCrlCore(string crlFile, SafeX509StoreHandle store, { if (OpenSslX509ChainEventSource.Log.IsEnabled()) { - OpenSslX509ChainEventSource.Log.CrlCacheExpired(nextUpdate, verificationTime); + OpenSslX509ChainEventSource.Log.CrlCacheExpired(verificationTime, nextUpdate); } return false; From fe9a96af9f78eab624b4a34d1c4cdae9a3e7f77e Mon Sep 17 00:00:00 2001 From: Linus Hamlin <78953007+lilinus@users.noreply.github.com> Date: Wed, 11 Dec 2024 18:56:53 +0100 Subject: [PATCH 39/70] Fix TensorExtensions.StdDev (#110392) * Fix TensorExtensions.StdDev * Add constraint to ref * Revert "Add constraint to ref" This reverts commit f740f503a9ef0b763f71442fd8b4e578142f9c83. * Revert "Fix TensorExtensions.StdDev" This reverts commit c21298471fe1ea272930c3e5427bf986dba275b6. * Use pow method * Use existing variable --- .../Numerics/Tensors/netcore/TensorExtensions.cs | 12 +++++++++++- .../System.Numerics.Tensors/tests/TensorTests.cs | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs index eec1e295e3c77b..1274b20b8cf9eb 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs @@ -3521,7 +3521,17 @@ public static T StdDev(in ReadOnlyTensorSpan x) TensorPrimitives.Abs(output, output); TensorPrimitives.Pow((ReadOnlySpan)output, T.CreateChecked(2), output); T sum = TensorPrimitives.Sum((ReadOnlySpan)output); - return T.CreateChecked(sum / T.CreateChecked(x._shape._memoryLength)); + T variance = sum / T.CreateChecked(x._shape._memoryLength); + + if (typeof(T) == typeof(float)) + { + return T.CreateChecked(MathF.Sqrt(float.CreateChecked(variance))); + } + if (typeof(T) == typeof(double)) + { + return T.CreateChecked(Math.Sqrt(double.CreateChecked(variance))); + } + return T.Pow(variance, T.CreateChecked(0.5)); } #endregion diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs index 96e2e9ede1313b..a418230aff14a0 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs @@ -1113,7 +1113,7 @@ public static float StdDev(float[] values) { sum += MathF.Pow(values[i] - mean, 2); } - return sum / values.Length; + return MathF.Sqrt(sum / values.Length); } [Fact] From 0181b15731c66bf4efab239f8c1cb3dc33570016 Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Wed, 11 Dec 2024 12:45:25 -0800 Subject: [PATCH 40/70] Use FLS detach as thread termination notification on windows. (#110589) * Use FLS detach as thread termination notification on windows. * use _ASSERTE_ALL_BUILDS * one more case * InitFlsSlot throws per convention used in threading initialization * OsDetachThread could be void * Update src/coreclr/vm/ceemain.cpp --------- Co-authored-by: Jan Kotas --- src/coreclr/nativeaot/Runtime/threadstore.cpp | 2 +- src/coreclr/vm/ceemain.cpp | 152 ++++++++++++++---- src/coreclr/vm/ceemain.h | 4 + src/coreclr/vm/threads.cpp | 13 ++ 4 files changed, 140 insertions(+), 31 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/threadstore.cpp b/src/coreclr/nativeaot/Runtime/threadstore.cpp index c2b42491a387d9..c03907e4eed665 100644 --- a/src/coreclr/nativeaot/Runtime/threadstore.cpp +++ b/src/coreclr/nativeaot/Runtime/threadstore.cpp @@ -162,7 +162,7 @@ void ThreadStore::DetachCurrentThread() } // Unregister from OS notifications - // This can return false if detach notification is spurious and does not belong to this thread. + // This can return false if a thread did not register for OS notification. if (!PalDetachThread(pDetachingThread)) { return; diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index 2784196ab2e959..eba08c952b21dc 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -1701,6 +1701,125 @@ BOOL STDMETHODCALLTYPE EEDllMain( // TRUE on success, FALSE on error. #endif // !defined(CORECLR_EMBEDDED) +static void RuntimeThreadShutdown(void* thread) +{ + Thread* pThread = (Thread*)thread; + _ASSERTE(pThread == GetThreadNULLOk()); + + if (pThread) + { +#ifdef FEATURE_COMINTEROP + // reset the CoInitialize state + // so we don't call CoUninitialize during thread detach + pThread->ResetCoInitialized(); +#endif // FEATURE_COMINTEROP + // For case where thread calls ExitThread directly, we need to reset the + // frame pointer. Otherwise stackwalk would AV. We need to do it in cooperative mode. + // We need to set m_GCOnTransitionsOK so this thread won't trigger GC when toggle GC mode + if (pThread->m_pFrame != FRAME_TOP) + { +#ifdef _DEBUG + pThread->m_GCOnTransitionsOK = FALSE; +#endif + GCX_COOP_NO_DTOR(); + pThread->m_pFrame = FRAME_TOP; + GCX_COOP_NO_DTOR_END(); + } + + pThread->DetachThread(TRUE); + } + else + { + // Since we don't actually cleanup the TLS data along this path, verify that it is already cleaned up + AssertThreadStaticDataFreed(); + } + + ThreadDetaching(); +} + +#ifdef TARGET_WINDOWS + +// Index for the fiber local storage of the attached thread pointer +static uint32_t g_flsIndex = FLS_OUT_OF_INDEXES; + +// This is called when each *fiber* is destroyed. When the home fiber of a thread is destroyed, +// it means that the thread itself is destroyed. +// Since we receive that notification outside of the Loader Lock, it allows us to safely acquire +// the ThreadStore lock in the RuntimeThreadShutdown. +static void __stdcall FiberDetachCallback(void* lpFlsData) +{ + ASSERT(g_flsIndex != FLS_OUT_OF_INDEXES); + ASSERT(lpFlsData == FlsGetValue(g_flsIndex)); + + if (lpFlsData != NULL) + { + // The current fiber is the home fiber of a thread, so the thread is shutting down + RuntimeThreadShutdown(lpFlsData); + } +} + +void InitFlsSlot() +{ + // We use fiber detach callbacks to run our thread shutdown code because the fiber detach + // callback is made without the OS loader lock + g_flsIndex = FlsAlloc(FiberDetachCallback); + if (g_flsIndex == FLS_OUT_OF_INDEXES) + { + COMPlusThrowWin32(); + } +} + +// Register the thread with OS to be notified when thread is about to be destroyed +// It fails fast if a different thread was already registered with the current fiber. +// Parameters: +// thread - thread to attach +static void OsAttachThread(void* thread) +{ + void* threadFromCurrentFiber = FlsGetValue(g_flsIndex); + + if (threadFromCurrentFiber != NULL) + { + _ASSERTE_ALL_BUILDS(!"Multiple threads encountered from a single fiber"); + } + + // Associate the current fiber with the current thread. This makes the current fiber the thread's "home" + // fiber. This fiber is the only fiber allowed to execute managed code on this thread. When this fiber + // is destroyed, we consider the thread to be destroyed. + FlsSetValue(g_flsIndex, thread); +} + +// Detach thread from OS notifications. +// It fails fast if some other thread value was attached to the current fiber. +// Parameters: +// thread - thread to detach +// Return: +// true if the thread was detached, false if there was no attached thread +void OsDetachThread(void* thread) +{ + ASSERT(g_flsIndex != FLS_OUT_OF_INDEXES); + void* threadFromCurrentFiber = FlsGetValue(g_flsIndex); + + if (threadFromCurrentFiber == NULL) + { + // we've seen this thread, but not this fiber. It must be a "foreign" fiber that was + // borrowing this thread. + return; + } + + if (threadFromCurrentFiber != thread) + { + _ASSERTE_ALL_BUILDS(!"Detaching a thread from the wrong fiber"); + } + + FlsSetValue(g_flsIndex, NULL); +} + +void EnsureTlsDestructionMonitor() +{ + OsAttachThread(GetThread()); +} + +#else struct TlsDestructionMonitor { bool m_activated = false; @@ -1714,36 +1833,7 @@ struct TlsDestructionMonitor { if (m_activated) { - Thread* thread = GetThreadNULLOk(); - if (thread) - { -#ifdef FEATURE_COMINTEROP - // reset the CoInitialize state - // so we don't call CoUninitialize during thread detach - thread->ResetCoInitialized(); -#endif // FEATURE_COMINTEROP - // For case where thread calls ExitThread directly, we need to reset the - // frame pointer. Otherwise stackwalk would AV. We need to do it in cooperative mode. - // We need to set m_GCOnTransitionsOK so this thread won't trigger GC when toggle GC mode - if (thread->m_pFrame != FRAME_TOP) - { -#ifdef _DEBUG - thread->m_GCOnTransitionsOK = FALSE; -#endif - GCX_COOP_NO_DTOR(); - thread->m_pFrame = FRAME_TOP; - GCX_COOP_NO_DTOR_END(); - } - - thread->DetachThread(TRUE); - } - else - { - // Since we don't actually cleanup the TLS data along this path, verify that it is already cleaned up - AssertThreadStaticDataFreed(); - } - - ThreadDetaching(); + RuntimeThreadShutdown(GetThreadNULLOk()); } } }; @@ -1757,6 +1847,8 @@ void EnsureTlsDestructionMonitor() tls_destructionMonitor.Activate(); } +#endif + #ifdef DEBUGGING_SUPPORTED // // InitializeDebugger initialized the Runtime-side COM+ Debugging Services diff --git a/src/coreclr/vm/ceemain.h b/src/coreclr/vm/ceemain.h index 1404a5a04237ff..120082d30572ca 100644 --- a/src/coreclr/vm/ceemain.h +++ b/src/coreclr/vm/ceemain.h @@ -46,6 +46,10 @@ void ForceEEShutdown(ShutdownCompleteAction sca = SCA_ExitProcessWhenShutdownCom void ThreadDetaching(); void EnsureTlsDestructionMonitor(); +#ifdef TARGET_WINDOWS +void InitFlsSlot(); +void OsDetachThread(void* thread); +#endif void SetLatchedExitCode (INT32 code); INT32 GetLatchedExitCode (void); diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index e89b6c7d94c1d0..7ffb9f4746d94a 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -353,6 +353,7 @@ void SetThread(Thread* t) { LIMITED_METHOD_CONTRACT + Thread* origThread = gCurrentThreadInfo.m_pThread; gCurrentThreadInfo.m_pThread = t; if (t != NULL) { @@ -360,6 +361,14 @@ void SetThread(Thread* t) EnsureTlsDestructionMonitor(); t->InitRuntimeThreadLocals(); } +#ifdef TARGET_WINDOWS + else if (origThread != NULL) + { + // Unregister from OS notifications + // This can return false if a thread did not register for OS notification. + OsDetachThread(origThread); + } +#endif // Clear or set the app domain to the one domain based on if the thread is being nulled out or set gCurrentThreadInfo.m_pAppDomain = t == NULL ? NULL : AppDomain::GetCurrentDomain(); @@ -1039,6 +1048,10 @@ void InitThreadManager() } CONTRACTL_END; +#ifdef TARGET_WINDOWS + InitFlsSlot(); +#endif + // All patched helpers should fit into one page. // If you hit this assert on retail build, there is most likely problem with BBT script. _ASSERTE_ALL_BUILDS((BYTE*)JIT_PatchedCodeLast - (BYTE*)JIT_PatchedCodeStart > (ptrdiff_t)0); From 2692fc535684b8b0557a5f17a4d7349fb0e5874d Mon Sep 17 00:00:00 2001 From: Mitchell Hwang <16830051+mdh1418@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:35:42 -0500 Subject: [PATCH 41/70] [Profiler] Avoid Recursive ThreadStoreLock in Profiling Thread Enumerator (#110548) * [Profiler] Avoid Recursive ThreadStoreLock Profiling Enumerators look to acquire the ThreadStoreLock. In release config, re-acquiring the ThreadStoreLock and releasing it in ProfilerThreadEnum::Init will cause problems if the callback invoking EnumThread has logic that depends on the ThreadStoreLock being held. To avoid recursively acquiring the ThreadStoreLock, expand the condition when the profiling thread enumerator shouldn't acquire the ThreadStoreLock. * [Profiler] Change order to set fProfilerRequestedRuntimeSuspend There was a potential race condition when setting the flag before suspending and resetting the flag after restarting. For example, if the thread restarting runtime is preempted right after resuming runtime, the flag could remain unset by the time another thread looks to suspend runtime, which would see that the flag as set. * [Profiler][Tests] Add unit test for EnumThreads during suspension * [Profiler][Tests] Fixup EnumThreads test --- src/coreclr/vm/profilingenumerators.cpp | 4 +- src/coreclr/vm/proftoeeinterfaceimpl.cpp | 8 +- src/tests/profiler/native/CMakeLists.txt | 3 +- src/tests/profiler/native/classfactory.cpp | 5 + .../enumthreadsprofiler.cpp | 104 ++++++++++++++++++ .../enumthreadsprofiler/enumthreadsprofiler.h | 34 ++++++ src/tests/profiler/unittest/enumthreads.cs | 56 ++++++++++ .../profiler/unittest/enumthreads.csproj | 21 ++++ 8 files changed, 229 insertions(+), 6 deletions(-) create mode 100644 src/tests/profiler/native/enumthreadsprofiler/enumthreadsprofiler.cpp create mode 100644 src/tests/profiler/native/enumthreadsprofiler/enumthreadsprofiler.h create mode 100644 src/tests/profiler/unittest/enumthreads.cs create mode 100644 src/tests/profiler/unittest/enumthreads.csproj diff --git a/src/coreclr/vm/profilingenumerators.cpp b/src/coreclr/vm/profilingenumerators.cpp index f214be043f0bf8..50883da5ea8822 100644 --- a/src/coreclr/vm/profilingenumerators.cpp +++ b/src/coreclr/vm/profilingenumerators.cpp @@ -552,9 +552,11 @@ HRESULT ProfilerThreadEnum::Init() } CONTRACTL_END; + // If EnumThreads is called from a profiler callback where the runtime is already suspended, + // don't recursively acquire/release the ThreadStore Lock. // If a profiler has requested that the runtime suspend to do stack snapshots, it // will be holding the ThreadStore lock already - ThreadStoreLockHolder tsLock(!g_profControlBlock.fProfilerRequestedRuntimeSuspend); + ThreadStoreLockHolder tsLock(!ThreadStore::HoldingThreadStore() && !g_profControlBlock.fProfilerRequestedRuntimeSuspend); Thread * pThread = NULL; diff --git a/src/coreclr/vm/proftoeeinterfaceimpl.cpp b/src/coreclr/vm/proftoeeinterfaceimpl.cpp index 1d3530c615d1e5..bdf2648af9d7fa 100644 --- a/src/coreclr/vm/proftoeeinterfaceimpl.cpp +++ b/src/coreclr/vm/proftoeeinterfaceimpl.cpp @@ -6835,8 +6835,8 @@ HRESULT ProfToEEInterfaceImpl::SuspendRuntime() return CORPROF_E_SUSPENSION_IN_PROGRESS; } - g_profControlBlock.fProfilerRequestedRuntimeSuspend = TRUE; ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_REASON::SUSPEND_FOR_PROFILER); + g_profControlBlock.fProfilerRequestedRuntimeSuspend = TRUE; return S_OK; } @@ -6874,8 +6874,8 @@ HRESULT ProfToEEInterfaceImpl::ResumeRuntime() return CORPROF_E_UNSUPPORTED_CALL_SEQUENCE; } - ThreadSuspend::RestartEE(FALSE /* bFinishedGC */, TRUE /* SuspendSucceeded */); g_profControlBlock.fProfilerRequestedRuntimeSuspend = FALSE; + ThreadSuspend::RestartEE(FALSE /* bFinishedGC */, TRUE /* SuspendSucceeded */); return S_OK; } @@ -7667,8 +7667,8 @@ HRESULT ProfToEEInterfaceImpl::EnumerateGCHeapObjects(ObjectCallback callback, v // SuspendEE() may race with other threads by design and this thread may block // arbitrarily long inside SuspendEE() for other threads to complete their own // suspensions. - g_profControlBlock.fProfilerRequestedRuntimeSuspend = TRUE; ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_REASON::SUSPEND_FOR_PROFILER); + g_profControlBlock.fProfilerRequestedRuntimeSuspend = TRUE; ownEESuspension = TRUE; } @@ -7700,8 +7700,8 @@ HRESULT ProfToEEInterfaceImpl::EnumerateGCHeapObjects(ObjectCallback callback, v if (ownEESuspension) { - ThreadSuspend::RestartEE(FALSE /* bFinishedGC */, TRUE /* SuspendSucceeded */); g_profControlBlock.fProfilerRequestedRuntimeSuspend = FALSE; + ThreadSuspend::RestartEE(FALSE /* bFinishedGC */, TRUE /* SuspendSucceeded */); } return hr; diff --git a/src/tests/profiler/native/CMakeLists.txt b/src/tests/profiler/native/CMakeLists.txt index 1279874df07c32..4333a8b2698280 100644 --- a/src/tests/profiler/native/CMakeLists.txt +++ b/src/tests/profiler/native/CMakeLists.txt @@ -5,11 +5,11 @@ project(Profiler) set(SOURCES assemblyprofiler/assemblyprofiler.cpp eltprofiler/slowpatheltprofiler.cpp + enumthreadsprofiler/enumthreadsprofiler.cpp eventpipeprofiler/eventpipereadingprofiler.cpp eventpipeprofiler/eventpipewritingprofiler.cpp eventpipeprofiler/eventpipemetadatareader.cpp gcallocateprofiler/gcallocateprofiler.cpp - nongcheap/nongcheap.cpp gcbasicprofiler/gcbasicprofiler.cpp gcheapenumerationprofiler/gcheapenumerationprofiler.cpp gcheapenumerationprofiler/gcheapenumerationprofiler.def @@ -20,6 +20,7 @@ set(SOURCES metadatagetdispenser/metadatagetdispenser.cpp moduleload/moduleload.cpp multiple/multiple.cpp + nongcheap/nongcheap.cpp nullprofiler/nullprofiler.cpp rejitprofiler/rejitprofiler.cpp rejitprofiler/ilrewriter.cpp diff --git a/src/tests/profiler/native/classfactory.cpp b/src/tests/profiler/native/classfactory.cpp index bb27152f8581f8..677b57fef95efc 100644 --- a/src/tests/profiler/native/classfactory.cpp +++ b/src/tests/profiler/native/classfactory.cpp @@ -3,6 +3,7 @@ #include "classfactory.h" #include "eltprofiler/slowpatheltprofiler.h" +#include "enumthreadsprofiler/enumthreadsprofiler.h" #include "eventpipeprofiler/eventpipereadingprofiler.h" #include "eventpipeprofiler/eventpipewritingprofiler.h" #include "getappdomainstaticaddress/getappdomainstaticaddress.h" @@ -144,6 +145,10 @@ HRESULT STDMETHODCALLTYPE ClassFactory::CreateInstance(IUnknown *pUnkOuter, REFI { profiler = new GCHeapEnumerationProfiler(); } + else if (clsid == EnumThreadsProfiler::GetClsid()) + { + profiler = new EnumThreadsProfiler(); + } else { printf("No profiler found in ClassFactory::CreateInstance. Did you add your profiler to the list?\n"); diff --git a/src/tests/profiler/native/enumthreadsprofiler/enumthreadsprofiler.cpp b/src/tests/profiler/native/enumthreadsprofiler/enumthreadsprofiler.cpp new file mode 100644 index 00000000000000..de6caeaef7100a --- /dev/null +++ b/src/tests/profiler/native/enumthreadsprofiler/enumthreadsprofiler.cpp @@ -0,0 +1,104 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "enumthreadsprofiler.h" + +GUID EnumThreadsProfiler::GetClsid() +{ + // {0742962D-2ED3-44B0-BA84-06B1EF0A0A0B} + GUID clsid = { 0x0742962d, 0x2ed3, 0x44b0,{ 0xba, 0x84, 0x06, 0xb1, 0xef, 0x0a, 0x0a, 0x0b } }; + return clsid; +} + +HRESULT EnumThreadsProfiler::Initialize(IUnknown* pICorProfilerInfoUnk) +{ + Profiler::Initialize(pICorProfilerInfoUnk); + printf("EnumThreadsProfiler::Initialize\n"); + + HRESULT hr = S_OK; + if (FAILED(hr = pCorProfilerInfo->SetEventMask2(COR_PRF_MONITOR_GC | COR_PRF_MONITOR_SUSPENDS, COR_PRF_HIGH_MONITOR_NONE))) + { + printf("FAIL: ICorProfilerInfo::SetEventMask2() failed hr=0x%x", hr); + IncrementFailures(); + } + + return hr; +} + +HRESULT STDMETHODCALLTYPE EnumThreadsProfiler::GarbageCollectionStarted(int cGenerations, BOOL generationCollected[], COR_PRF_GC_REASON reason) +{ + SHUTDOWNGUARD(); + + printf("EnumThreadsProfiler::GarbageCollectionStarted\n"); + _gcStarts.fetch_add(1, std::memory_order_relaxed); + if (_gcStarts < _gcFinishes) + { + IncrementFailures(); + printf("EnumThreadsProfiler::GarbageCollectionStarted: FAIL: Expected GCStart >= GCFinish. Start=%d, Finish=%d\n", (int)_gcStarts, (int)_gcFinishes); + } + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE EnumThreadsProfiler::GarbageCollectionFinished() +{ + SHUTDOWNGUARD(); + + printf("EnumThreadsProfiler::GarbageCollectionFinished\n"); + _gcFinishes.fetch_add(1, std::memory_order_relaxed); + if (_gcStarts < _gcFinishes) + { + IncrementFailures(); + printf("EnumThreadsProfiler::GarbageCollectionFinished: FAIL: Expected GCStart >= GCFinish. Start=%d, Finish=%d\n", (int)_gcStarts, (int)_gcFinishes); + } + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE EnumThreadsProfiler::RuntimeSuspendFinished() +{ + SHUTDOWNGUARD(); + + printf("EnumThreadsProfiler::RuntimeSuspendFinished\n"); + + ICorProfilerThreadEnum* threadEnum = nullptr; + HRESULT enumThreadsHR = pCorProfilerInfo->EnumThreads(&threadEnum); + printf("Finished enumerating threads\n"); + _profilerEnumThreadsCompleted.fetch_add(1, std::memory_order_relaxed); + threadEnum->Release(); + return enumThreadsHR; +} + +HRESULT EnumThreadsProfiler::Shutdown() +{ + Profiler::Shutdown(); + + if (_gcStarts == 0) + { + printf("EnumThreadsProfiler::Shutdown: FAIL: Expected GarbageCollectionStarted to be called\n"); + } + else if (_gcFinishes == 0) + { + printf("EnumThreadsProfiler::Shutdown: FAIL: Expected GarbageCollectionFinished to be called\n"); + } + else if (_profilerEnumThreadsCompleted == 0) + { + printf("EnumThreadsProfiler::Shutdown: FAIL: Expected RuntimeSuspendFinished to be called and EnumThreads completed\n"); + } + else if(_failures == 0) + { + printf("PROFILER TEST PASSES\n"); + } + else + { + // failures were printed earlier when _failures was incremented + } + fflush(stdout); + + return S_OK; +} + +void EnumThreadsProfiler::IncrementFailures() +{ + _failures.fetch_add(1, std::memory_order_relaxed); +} diff --git a/src/tests/profiler/native/enumthreadsprofiler/enumthreadsprofiler.h b/src/tests/profiler/native/enumthreadsprofiler/enumthreadsprofiler.h new file mode 100644 index 00000000000000..df716108432594 --- /dev/null +++ b/src/tests/profiler/native/enumthreadsprofiler/enumthreadsprofiler.h @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#include "../profiler.h" + +class EnumThreadsProfiler : public Profiler +{ +public: + EnumThreadsProfiler() : Profiler(), + _gcStarts(0), + _gcFinishes(0), + _profilerEnumThreadsCompleted(0), + _failures(0) + {} + + // Profiler callbacks override + static GUID GetClsid(); + virtual HRESULT STDMETHODCALLTYPE Initialize(IUnknown* pICorProfilerInfoUnk); + virtual HRESULT STDMETHODCALLTYPE GarbageCollectionStarted(int cGenerations, BOOL generationCollected[], COR_PRF_GC_REASON reason); + virtual HRESULT STDMETHODCALLTYPE GarbageCollectionFinished(); + virtual HRESULT STDMETHODCALLTYPE RuntimeSuspendFinished(); + virtual HRESULT STDMETHODCALLTYPE Shutdown(); + + // Helper methods + void IncrementFailures(); + +private: + std::atomic _gcStarts; + std::atomic _gcFinishes; + std::atomic _profilerEnumThreadsCompleted; + std::atomic _failures; +}; diff --git a/src/tests/profiler/unittest/enumthreads.cs b/src/tests/profiler/unittest/enumthreads.cs new file mode 100644 index 00000000000000..95562decc3f551 --- /dev/null +++ b/src/tests/profiler/unittest/enumthreads.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Profiler.Tests +{ + class EnumThreadsTests + { + static readonly Guid EnumThreadsProfilerGuid = new Guid("0742962D-2ED3-44B0-BA84-06B1EF0A0A0B"); + + public static int EnumerateThreadsWithNonProfilerRequestedRuntimeSuspension() + { + GC.Collect(); + return 100; + } + + public static int Main(string[] args) + { + if (args.Length > 0 && args[0].Equals("RunTest", StringComparison.OrdinalIgnoreCase)) + { + switch (args[1]) + { + case nameof(EnumerateThreadsWithNonProfilerRequestedRuntimeSuspension): + return EnumerateThreadsWithNonProfilerRequestedRuntimeSuspension(); + default: + return 102; + } + } + + if (!RunProfilerTest(nameof(EnumerateThreadsWithNonProfilerRequestedRuntimeSuspension))) + { + return 101; + } + + return 100; + } + + private static bool RunProfilerTest(string testName) + { + try + { + return ProfilerTestRunner.Run(profileePath: System.Reflection.Assembly.GetExecutingAssembly().Location, + testName: "EnumThreads", + profilerClsid: EnumThreadsProfilerGuid, + profileeArguments: testName + ) == 100; + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + return false; + } + } +} diff --git a/src/tests/profiler/unittest/enumthreads.csproj b/src/tests/profiler/unittest/enumthreads.csproj new file mode 100644 index 00000000000000..d51dcb692abfe0 --- /dev/null +++ b/src/tests/profiler/unittest/enumthreads.csproj @@ -0,0 +1,21 @@ + + + .NETCoreApp + exe + true + true + + true + + true + + + + + + + + From 097ed73fc3e556edfbfbaa52bc28df5a4f8f466b Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Thu, 12 Dec 2024 00:12:33 +0100 Subject: [PATCH 42/70] Remove HttpMetricsEnrichmentContext caching (#110580) --- .../Metrics/HttpMetricsEnrichmentContext.cs | 103 +++++--------- .../System/Net/Http/Metrics/MetricsHandler.cs | 6 +- .../tests/FunctionalTests/MetricsTest.cs | 133 ++++++++++++++++++ 3 files changed, 169 insertions(+), 73 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Metrics/HttpMetricsEnrichmentContext.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Metrics/HttpMetricsEnrichmentContext.cs index 89a5d1fd1bfacd..34f274d98aa73d 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/Metrics/HttpMetricsEnrichmentContext.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/Metrics/HttpMetricsEnrichmentContext.cs @@ -1,12 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Metrics; -using System.Runtime.InteropServices; -using System.Threading; namespace System.Net.Http.Metrics { @@ -19,22 +16,18 @@ namespace System.Net.Http.Metrics /// information exposed on the instance. /// /// > [!IMPORTANT] - /// > The intance must not be used from outside of the enrichment callbacks. + /// > The instance must not be used from outside of the enrichment callbacks. /// public sealed class HttpMetricsEnrichmentContext { - private static readonly HttpRequestOptionsKey s_optionsKeyForContext = new(nameof(HttpMetricsEnrichmentContext)); - private static readonly ConcurrentQueue s_pool = new(); - private static int s_poolItemCount; - private const int PoolCapacity = 1024; + private static readonly HttpRequestOptionsKey>> s_optionsKeyForCallbacks = new(nameof(HttpMetricsEnrichmentContext)); - private readonly List> _callbacks = new(); private HttpRequestMessage? _request; private HttpResponseMessage? _response; private Exception? _exception; - private List> _tags = new(capacity: 16); + private TagList _tags; - internal HttpMetricsEnrichmentContext() { } // Hide the default parameterless constructor. + private HttpMetricsEnrichmentContext() { } // Hide the default parameterless constructor. /// /// Gets the that has been sent. @@ -68,7 +61,7 @@ public sealed class HttpMetricsEnrichmentContext /// /// This method must not be used from outside of the enrichment callbacks. /// - public void AddCustomTag(string name, object? value) => _tags.Add(new KeyValuePair(name, value)); + public void AddCustomTag(string name, object? value) => _tags.Add(name, value); /// /// Adds a callback to register custom tags for request metrics `http-client-request-duration` and `http-client-failed-requests`. @@ -77,85 +70,55 @@ public sealed class HttpMetricsEnrichmentContext /// The callback responsible to add custom tags via . public static void AddCallback(HttpRequestMessage request, Action callback) { + ArgumentNullException.ThrowIfNull(request); + ArgumentNullException.ThrowIfNull(callback); + HttpRequestOptions options = request.Options; - // We associate an HttpMetricsEnrichmentContext with the request on the first call to AddCallback(), - // and store the callbacks in the context. This allows us to cache all the enrichment objects together. - if (!options.TryGetValue(s_optionsKeyForContext, out HttpMetricsEnrichmentContext? context)) + if (options.TryGetValue(s_optionsKeyForCallbacks, out List>? callbacks)) + { + callbacks.Add(callback); + } + else { - if (s_pool.TryDequeue(out context)) - { - Debug.Assert(context._callbacks.Count == 0); - Interlocked.Decrement(ref s_poolItemCount); - } - else - { - context = new HttpMetricsEnrichmentContext(); - } - - options.Set(s_optionsKeyForContext, context); + options.Set(s_optionsKeyForCallbacks, [callback]); } - context._callbacks.Add(callback); } - internal static HttpMetricsEnrichmentContext? GetEnrichmentContextForRequest(HttpRequestMessage request) + internal static List>? GetEnrichmentCallbacksForRequest(HttpRequestMessage request) { - if (request._options is null) + if (request._options is HttpRequestOptions options && + options.Remove(s_optionsKeyForCallbacks.Key, out object? callbacks)) { - return null; + return (List>)callbacks!; } - request._options.TryGetValue(s_optionsKeyForContext, out HttpMetricsEnrichmentContext? context); - return context; + + return null; } - internal void RecordDurationWithEnrichment(HttpRequestMessage request, + internal static void RecordDurationWithEnrichment( + List> callbacks, + HttpRequestMessage request, HttpResponseMessage? response, Exception? exception, TimeSpan durationTime, in TagList commonTags, Histogram requestDuration) { - _request = request; - _response = response; - _exception = exception; - - Debug.Assert(_tags.Count == 0); - - // Adding the enrichment tags to the TagList would likely exceed its' on-stack capacity, leading to an allocation. - // To avoid this, we add all the tags to a List which is cached together with HttpMetricsEnrichmentContext. - // Use a for loop to iterate over the TagList, since TagList.GetEnumerator() allocates, see - // https://github.com/dotnet/runtime/issues/87022. - for (int i = 0; i < commonTags.Count; i++) + var context = new HttpMetricsEnrichmentContext { - _tags.Add(commonTags[i]); - } + _request = request, + _response = response, + _exception = exception, + _tags = commonTags, + }; - try + foreach (Action callback in callbacks) { - foreach (Action callback in _callbacks) - { - callback(this); - } - - requestDuration.Record(durationTime.TotalSeconds, CollectionsMarshal.AsSpan(_tags)); - } - finally - { - _request = null; - _response = null; - _exception = null; - _callbacks.Clear(); - _tags.Clear(); - - if (Interlocked.Increment(ref s_poolItemCount) <= PoolCapacity) - { - s_pool.Enqueue(this); - } - else - { - Interlocked.Decrement(ref s_poolItemCount); - } + callback(context); } + + requestDuration.Record(durationTime.TotalSeconds, context._tags); } } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Metrics/MetricsHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Metrics/MetricsHandler.cs index 7a381483099d4d..79a544816ccb09 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/Metrics/MetricsHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/Metrics/MetricsHandler.cs @@ -121,14 +121,14 @@ private void RequestStop(HttpRequestMessage request, HttpResponseMessage? respon TimeSpan durationTime = Stopwatch.GetElapsedTime(startTimestamp, Stopwatch.GetTimestamp()); - HttpMetricsEnrichmentContext? enrichmentContext = HttpMetricsEnrichmentContext.GetEnrichmentContextForRequest(request); - if (enrichmentContext is null) + List>? callbacks = HttpMetricsEnrichmentContext.GetEnrichmentCallbacksForRequest(request); + if (callbacks is null) { _requestsDuration.Record(durationTime.TotalSeconds, tags); } else { - enrichmentContext.RecordDurationWithEnrichment(request, response, exception, durationTime, tags, _requestsDuration); + HttpMetricsEnrichmentContext.RecordDurationWithEnrichment(callbacks, request, response, exception, durationTime, tags, _requestsDuration); } } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/MetricsTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/MetricsTest.cs index 6a8d156cdfc41a..9d4afdd97788ab 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/MetricsTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/MetricsTest.cs @@ -467,6 +467,49 @@ public Task RequestDuration_CustomTags_Recorded() VerifyRequestDuration(m, uri, UseVersion, 200); Assert.Equal("/test", m.Tags.ToArray().Single(t => t.Key == "route").Value); + }, async server => + { + await server.HandleRequestAsync(); + }); + } + + [Fact] + public Task RequestDuration_MultipleCallbacksPerRequest_AllCalledInOrder() + { + return LoopbackServerFactory.CreateClientAndServerAsync(async uri => + { + using HttpMessageInvoker client = CreateHttpMessageInvoker(); + using InstrumentRecorder recorder = SetupInstrumentRecorder(InstrumentNames.RequestDuration); + using HttpRequestMessage request = new(HttpMethod.Get, uri) { Version = UseVersion }; + + int lastCallback = -1; + + HttpMetricsEnrichmentContext.AddCallback(request, ctx => + { + Assert.Equal(-1, lastCallback); + lastCallback = 1; + ctx.AddCustomTag("custom1", "foo"); + }); + HttpMetricsEnrichmentContext.AddCallback(request, ctx => + { + Assert.Equal(1, lastCallback); + lastCallback = 2; + ctx.AddCustomTag("custom2", "bar"); + }); + HttpMetricsEnrichmentContext.AddCallback(request, ctx => + { + Assert.Equal(2, lastCallback); + ctx.AddCustomTag("custom3", "baz"); + }); + + using HttpResponseMessage response = await SendAsync(client, request); + + Measurement m = Assert.Single(recorder.GetMeasurements()); + VerifyRequestDuration(m, uri, UseVersion, 200); + Assert.Equal("foo", Assert.Single(m.Tags.ToArray(), t => t.Key == "custom1").Value); + Assert.Equal("bar", Assert.Single(m.Tags.ToArray(), t => t.Key == "custom2").Value); + Assert.Equal("baz", Assert.Single(m.Tags.ToArray(), t => t.Key == "custom3").Value); + }, async server => { await server.AcceptConnectionSendResponseAndCloseAsync(); @@ -1075,6 +1118,96 @@ public class HttpMetricsTest_Http11_Async_HttpMessageInvoker : HttpMetricsTest_H public HttpMetricsTest_Http11_Async_HttpMessageInvoker(ITestOutputHelper output) : base(output) { } + + [Fact] + public async Task RequestDuration_RequestReused_EnrichmentCallbacksAreCleared() + { + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => + { + using HttpMessageInvoker client = CreateHttpMessageInvoker(); + using InstrumentRecorder recorder = SetupInstrumentRecorder(InstrumentNames.RequestDuration); + + using HttpRequestMessage request = new(HttpMethod.Get, uri); + + int firstCallbackCalls = 0; + + HttpMetricsEnrichmentContext.AddCallback(request, ctx => + { + firstCallbackCalls++; + ctx.AddCustomTag("key1", "foo"); + }); + + (await SendAsync(client, request)).Dispose(); + Assert.Equal(1, firstCallbackCalls); + + Measurement m = Assert.Single(recorder.GetMeasurements()); + Assert.Equal("key1", Assert.Single(m.Tags.ToArray(), t => t.Value as string == "foo").Key); + + HttpMetricsEnrichmentContext.AddCallback(request, static ctx => + { + ctx.AddCustomTag("key2", "foo"); + }); + + (await SendAsync(client, request)).Dispose(); + Assert.Equal(1, firstCallbackCalls); + + Assert.Equal(2, recorder.GetMeasurements().Count); + m = recorder.GetMeasurements()[1]; + Assert.Equal("key2", Assert.Single(m.Tags.ToArray(), t => t.Value as string == "foo").Key); + }, async server => + { + await server.HandleRequestAsync(); + await server.HandleRequestAsync(); + }); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public async Task RequestDuration_ConcurrentRequestsSeeDifferentContexts() + { + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => + { + using HttpMessageInvoker client = CreateHttpMessageInvoker(); + using var _ = SetupInstrumentRecorder(InstrumentNames.RequestDuration); + + using HttpRequestMessage request1 = new(HttpMethod.Get, uri); + using HttpRequestMessage request2 = new(HttpMethod.Get, uri); + + HttpMetricsEnrichmentContext.AddCallback(request1, _ => { }); + (await client.SendAsync(request1, CancellationToken.None)).Dispose(); + + HttpMetricsEnrichmentContext context1 = null; + HttpMetricsEnrichmentContext context2 = null; + CountdownEvent countdownEvent = new(2); + + HttpMetricsEnrichmentContext.AddCallback(request1, ctx => + { + context1 = ctx; + countdownEvent.Signal(); + Assert.True(countdownEvent.Wait(TestHelper.PassingTestTimeout)); + }); + HttpMetricsEnrichmentContext.AddCallback(request2, ctx => + { + context2 = ctx; + countdownEvent.Signal(); + Assert.True(countdownEvent.Wait(TestHelper.PassingTestTimeout)); + }); + + Task task1 = Task.Run(() => client.SendAsync(request1, CancellationToken.None)); + Task task2 = Task.Run(() => client.SendAsync(request2, CancellationToken.None)); + + (await task1).Dispose(); + (await task2).Dispose(); + + Assert.NotSame(context1, context2); + }, async server => + { + await server.HandleRequestAsync(); + + await Task.WhenAll( + server.HandleRequestAsync(), + server.HandleRequestAsync()); + }, options: new GenericLoopbackOptions { ListenBacklog = 2 }); + } } [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMobile))] From ab2fa84f2e9638a1c5bb624c181fca956cb6f2e0 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Thu, 12 Dec 2024 02:36:34 +0100 Subject: [PATCH 43/70] [NRBF] Reduce the most time-consuming test case to avoid timeouts for checked builds (#110550) --- .../tests/ArraySinglePrimitiveRecordTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Formats.Nrbf/tests/ArraySinglePrimitiveRecordTests.cs b/src/libraries/System.Formats.Nrbf/tests/ArraySinglePrimitiveRecordTests.cs index 7ef801808e4e95..9f714c9dddac15 100644 --- a/src/libraries/System.Formats.Nrbf/tests/ArraySinglePrimitiveRecordTests.cs +++ b/src/libraries/System.Formats.Nrbf/tests/ArraySinglePrimitiveRecordTests.cs @@ -20,7 +20,7 @@ public NonSeekableStream(byte[] buffer) : base(buffer) { } public static IEnumerable GetCanReadArrayOfAnySizeArgs() { - foreach (int size in new[] { 1, 127, 128, 512_001, 512_001 }) + foreach (int size in new[] { 1, 127, 128, 20_001 }) { yield return new object[] { size, true }; yield return new object[] { size, false }; From ae492ef9c4c31258ec8ec3cb3b41600b2642929c Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Thu, 12 Dec 2024 11:41:06 +0100 Subject: [PATCH 44/70] All `WasmBuildTests` use static project from assets or `dotnet new`, unification of browser tests (#109069) * Fix build errors in WASI - refactor of WASI in a future PR. * Fix binlog location. * Update README. * Feedback: non-nullable _projectDir. * Fix `SatelliteAssembliesTests` and other test that require 3 states of `WasmBuildNative` * `RebuildTests` fix after introducing figerprinting. * Fix no-worklod tests: 1) do not double-assert `AssertRuntimePackPath` in blazor 2) `AssertRuntimePackPath` is correct only when workload is there 3) NativeBuild should allow undefined in blazor tests as well. * Fix `NativeBuildTests` - AppBundle was not asserted here originally. * `WasmGenerateAppBundle=false` when workloads used (set in workload manifest). The check is incorrect in the current process of testing. * Fix `MainWithArgsTests`. Url query can have arg key duplicates -> is better represented by `NameValueCollection` than `Dictionary` * AppBundle was never asserted for size in `OptimizationFlagChangeTests` + use statistics that are fingerprinting insensitive. * Reading from config is a known issue. * Fix `ReferenceNewAssemblyRebuildTest`: Json lib was already referenced before the change in the test. Use Cryptography lib instead. * Keep blazor workload tests together. * Fix: `PInvokeTableGeneratorTests` on Windows. * Move all no-workload blazor tests to one class. * Use more meaningful name for `MiscTests3`. * `PInvokeTableGeneratorTests` takes 2-3 times more time than average WBT class, divide it. * Block the timeouting test with exisiting issue. * Try avoiding port collisions in blazor tests run on kestrel. * Removal of files and comments that were supposed to be deleted after approval. --- .../scenarios/BuildWasmAppsJobsList.txt | 22 +- .../wasi/Wasi.Build.Tests/BuildTestBase.cs | 7 + .../SharedBuildPerTestClassFixture.cs | 1 + .../wasm/Wasm.Build.Tests/AppSettingsTests.cs | 42 + .../AspNetCore/SignalRClientTests.cs | 10 +- .../Blazor/AppsettingsTests.cs | 52 +- .../Blazor/BlazorBuildOptions.cs | 25 - .../Blazor/BlazorRunOptions.cs | 64 +- .../Blazor/BlazorWasmProjectProvider.cs | 32 - .../Blazor/BlazorWasmTestBase.cs | 267 ++--- .../Blazor/BuildPublishTests.cs | 160 +-- .../Wasm.Build.Tests/Blazor/CleanTests.cs | 68 +- .../Wasm.Build.Tests/Blazor/DllImportTests.cs | 106 ++ .../Blazor/IcuShardingTests.cs | 112 -- .../wasm/Wasm.Build.Tests/Blazor/IcuTests.cs | 134 --- .../wasm/Wasm.Build.Tests/Blazor/MiscTests.cs | 93 +- .../Wasm.Build.Tests/Blazor/MiscTests2.cs | 69 -- .../Wasm.Build.Tests/Blazor/MiscTests3.cs | 167 --- .../Wasm.Build.Tests/Blazor/NativeRefTests.cs | 62 +- .../Blazor/NoopNativeRebuildTest.cs | 66 +- .../Blazor/SignalRClientTests.cs | 10 +- .../Blazor/SimpleMultiThreadedTests.cs | 66 +- .../Wasm.Build.Tests/Blazor/SimpleRunTests.cs | 82 +- .../Blazor/WorkloadRequiredTests.cs | 195 +-- .../wasm/Wasm.Build.Tests/BrowserRunner.cs | 34 +- .../BrowserStructures/AssertBundleOptions.cs | 18 + .../BrowserStructures/BrowserRunOptions.cs | 53 + .../BrowserStructures/BuildOptions.cs | 49 + .../BrowserStructures/BuildProduct.cs | 13 + .../BrowserStructures/MSBuildOptions.cs | 28 + .../BrowserStructures/NativeFilesType.cs | 9 + .../BrowserStructures/PublishOptions.cs | 57 + .../BrowserStructures/RunOptions.cs | 33 + .../SharedBuildPerTestClassFixture.cs | 67 ++ .../BrowserStructures/TestAsset.cs | 7 + .../Wasm.Build.Tests/BuildProjectOptions.cs | 31 - .../Wasm.Build.Tests/BuildPublishTests.cs | 197 +--- .../wasm/Wasm.Build.Tests/BuildTestBase.cs | 412 +------ .../Common/AssertBundleOptionsBase.cs | 29 - .../AssertTestMainJsAppBundleOptions.cs | 41 - .../Common/AssertWasmSdkBundleOptions.cs | 37 - .../Common/BuildAndRunAttribute.cs | 21 +- .../Wasm.Build.Tests/Common/BuildPaths.cs | 2 +- .../Wasm.Build.Tests/Common/Configuration.cs | 13 + .../Common/EnvironmentVariables.cs | 1 + .../Common/HelperExtensions.cs | 43 - .../Wasm.Build.Tests/Common/ProjectInfo.cs | 15 + .../wasm/Wasm.Build.Tests/Common/RunHost.cs | 19 - .../wasm/Wasm.Build.Tests/Common/RunResult.cs | 13 + .../wasm/Wasm.Build.Tests/Common/Template.cs | 13 + .../wasm/Wasm.Build.Tests/Common/TestUtils.cs | 8 + .../wasm/Wasm.Build.Tests/Common/Utils.cs | 4 +- .../wasm/Wasm.Build.Tests/ConfigSrcTests.cs | 37 - .../wasm/Wasm.Build.Tests/DebugLevelTests.cs | 154 ++- .../wasm/Wasm.Build.Tests/DllImportTests.cs | 181 +++ .../DownloadThenInitTests.cs | 20 +- .../HostRunner/BrowserHostRunner.cs | 41 - .../HostRunner/IHostRunner.cs | 17 - .../HostRunner/V8HostRunner.cs | 43 - .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 25 +- .../Wasm.Build.Tests/IcuShardingTests2.cs | 11 +- src/mono/wasm/Wasm.Build.Tests/IcuTests.cs | 60 +- .../wasm/Wasm.Build.Tests/IcuTestsBase.cs | 81 +- .../{TestAppScenarios => }/InterpPgoTests.cs | 16 +- .../InvariantGlobalizationTests.cs | 64 +- .../InvariantTimezoneTests.cs | 59 +- .../LazyLoadingTests.cs | 33 +- .../LibraryInitializerTests.cs | 28 +- .../Wasm.Build.Tests/MainWithArgsTests.cs | 105 +- .../MaxParallelDownloadsTests.cs | 23 +- .../{TestAppScenarios => }/MemoryTests.cs | 27 +- .../ModuleConfigTests.cs | 51 +- .../wasm/Wasm.Build.Tests/NativeBuildTests.cs | 110 +- .../Wasm.Build.Tests/NativeLibraryTests.cs | 181 +-- .../FlagsChangeRebuildTest.cs | 42 +- .../NativeRebuildTestsBase.cs | 90 +- .../NoopNativeRebuildTest.cs | 29 +- .../OptimizationFlagChangeTests.cs | 41 +- .../ReferenceNewAssemblyRebuildTest.cs | 42 +- .../SimpleSourceChangeRebuildTest.cs | 39 +- .../NonWasmTemplateBuildTests.cs | 18 +- .../PInvokeTableGeneratorTests.cs | 1044 ++++------------- .../PInvokeTableGeneratorTestsBase.cs | 35 + .../Wasm.Build.Tests/ProjectProviderBase.cs | 210 ++-- src/mono/wasm/Wasm.Build.Tests/README.md | 16 +- .../wasm/Wasm.Build.Tests/RebuildTests.cs | 49 +- .../SatelliteAssembliesTests.cs | 231 ++-- .../SatelliteLoadingTests.cs | 27 +- .../wasm/Wasm.Build.Tests/SignalRTestsBase.cs | 19 +- .../Templates/NativeBuildTests.cs | 88 +- .../Templates/WasmTemplateTests.cs | 205 ++-- .../Templates/WasmTemplateTestsBase.cs | 359 ++++-- .../TestAppScenarios/AppSettingsTests.cs | 46 - .../TestAppScenarios/AppTestBase.cs | 178 --- .../TestMainJsProjectProvider.cs | 119 -- .../Wasm.Build.Tests/TestMainJsTestBase.cs | 96 -- .../Wasm.Build.Tests/Wasm.Build.Tests.csproj | 3 + .../wasm/Wasm.Build.Tests/WasmBuildAppBase.cs | 50 + .../wasm/Wasm.Build.Tests/WasmBuildAppTest.cs | 183 +-- .../WasmNativeDefaultsTests.cs | 140 ++- .../WasmRunOutOfAppBundleTests.cs | 58 +- .../wasm/Wasm.Build.Tests/WasmSIMDTests.cs | 102 +- .../WasmSdkBasedProjectProvider.cs | 110 +- .../wasm/Wasm.Build.Tests/WorkloadTests.cs | 2 +- .../testassets/AppUsingNativeLib/Program.cs | 2 +- .../AppUsingNativeLib/native-lib.cpp | 2 +- .../BlazorBasicTestApp/App/App.razor | 12 + .../App/BlazorBasicTestApp.csproj | 14 + .../App/Layout/MainLayout.razor | 16 + .../App/Layout/NavMenu.razor | 39 + .../App/Pages/Counter.razor | 18 + .../BlazorBasicTestApp/App/Pages/Home.razor | 17 + .../BlazorBasicTestApp/App/Program.cs | 11 + .../BlazorBasicTestApp/App/_Imports.razor | 10 + .../App/wwwroot/favicon.png | Bin 0 -> 1148 bytes .../App/wwwroot/icon-192.png | Bin 0 -> 2626 bytes .../BlazorBasicTestApp/App/wwwroot/index.html | 32 + .../RazorClassLibrary/Component1.razor | 3 + .../RazorClassLibrary.csproj | 18 + .../RazorClassLibrary/_Imports.razor | 1 + .../EntryPoints/AsyncMainWithArgs.cs | 15 + .../testassets/EntryPoints/CultureResource.cs | 40 + .../HybridGlobalization.cs | 8 +- .../EntryPoints/InvariantGlobalization.cs | 25 + .../EntryPoints/InvariantTimezone.cs | 23 + .../testassets/EntryPoints/MyDllImport.cs | 8 + .../testassets/EntryPoints/NativeCrypto.cs | 17 + .../EntryPoints/NativeRebuildNewAssembly.cs | 17 + .../EntryPoints/PInvoke/AbiRules.cs | 107 ++ .../PInvoke/BittableDifferentAssembly.cs | 18 + .../PInvoke/BittableDifferentAssembly_Lib.cs | 6 + .../PInvoke/BittableSameAssembly.cs | 21 + .../EntryPoints/PInvoke/BuildNative.cs | 8 + .../EntryPoints/PInvoke/ComInterop.cs | 17 + .../EntryPoints/PInvoke/DllImportNoWarning.cs | 16 + .../EntryPoints/PInvoke/DllImportWarning.cs | 13 + .../EntryPoints/PInvoke/FunctionPointers.cs | 13 + .../EntryPoints/PInvoke/ICall_Lib.cs | 18 + .../PInvoke}/UnmanagedCallback.cs | 4 +- .../PInvoke/UnmanagedCallbackInFile.cs | 16 + .../PInvoke/UnmanagedCallbackNamespaced.cs | 42 + .../EntryPoints/PInvoke/VariadicFunctions.cs | 21 + .../EntryPoints/SimpleSourceChange.cs | 7 + .../wasm/testassets/EntryPoints/SkiaSharp.cs | 14 + .../EntryPoints/SyncMainWithArgs.cs | 15 + .../LibraryWithResources.csproj | 2 +- .../InvariantGlobalization.cs | 25 - .../InvariantTimezone.cs | 21 - .../App/WasmBasicTestApp.csproj | 6 +- .../WasmBasicTestApp/App/wwwroot/index.html | 2 +- .../WasmBasicTestApp/App/wwwroot/main.js | 15 + .../{Library => Json}/Json.cs | 0 .../{Library => Json}/Json.csproj | 0 .../WasmBasicTestApp/Library/Library.cs | 5 + .../WasmBasicTestApp/Library/Library.csproj | 7 + .../wasm/testassets/marshal_ilgen_test.cs | 2 +- src/mono/wasm/testassets/native-libs/local.c | 2 +- .../wasm/testassets/native-libs/mylib.cpp | 7 + .../wasm/testassets/native-libs/native-lib.o | Bin 542 -> 617 bytes 159 files changed, 3912 insertions(+), 5103 deletions(-) rename src/mono/{wasm/Wasm.Build.Tests/Common => wasi/Wasi.Build.Tests}/SharedBuildPerTestClassFixture.cs (95%) create mode 100644 src/mono/wasm/Wasm.Build.Tests/AppSettingsTests.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorBuildOptions.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmProjectProvider.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/Blazor/DllImportTests.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/Blazor/IcuShardingTests.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/Blazor/IcuTests.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests2.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests3.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/BrowserStructures/AssertBundleOptions.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BrowserRunOptions.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildOptions.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildProduct.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/BrowserStructures/MSBuildOptions.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/BrowserStructures/NativeFilesType.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/BrowserStructures/PublishOptions.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/BrowserStructures/RunOptions.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/BrowserStructures/SharedBuildPerTestClassFixture.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/BrowserStructures/TestAsset.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/BuildProjectOptions.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/Common/AssertBundleOptionsBase.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/Common/AssertTestMainJsAppBundleOptions.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/Common/AssertWasmSdkBundleOptions.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/Common/Configuration.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/Common/ProjectInfo.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/Common/RunHost.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/Common/RunResult.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/Common/Template.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/ConfigSrcTests.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/DllImportTests.cs rename src/mono/wasm/Wasm.Build.Tests/{TestAppScenarios => }/DownloadThenInitTests.cs (67%) delete mode 100644 src/mono/wasm/Wasm.Build.Tests/HostRunner/BrowserHostRunner.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/HostRunner/IHostRunner.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs rename src/mono/wasm/Wasm.Build.Tests/{TestAppScenarios => }/InterpPgoTests.cs (92%) rename src/mono/wasm/Wasm.Build.Tests/{TestAppScenarios => }/LazyLoadingTests.cs (60%) rename src/mono/wasm/Wasm.Build.Tests/{TestAppScenarios => }/LibraryInitializerTests.cs (57%) rename src/mono/wasm/Wasm.Build.Tests/{TestAppScenarios => }/MaxParallelDownloadsTests.cs (61%) rename src/mono/wasm/Wasm.Build.Tests/{TestAppScenarios => }/MemoryTests.cs (62%) rename src/mono/wasm/Wasm.Build.Tests/{TestAppScenarios => }/ModuleConfigTests.cs (58%) create mode 100644 src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTestsBase.cs rename src/mono/wasm/Wasm.Build.Tests/{TestAppScenarios => }/SatelliteLoadingTests.cs (73%) delete mode 100644 src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/TestMainJsTestBase.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/WasmBuildAppBase.cs create mode 100644 src/mono/wasm/testassets/BlazorBasicTestApp/App/App.razor create mode 100644 src/mono/wasm/testassets/BlazorBasicTestApp/App/BlazorBasicTestApp.csproj create mode 100644 src/mono/wasm/testassets/BlazorBasicTestApp/App/Layout/MainLayout.razor create mode 100644 src/mono/wasm/testassets/BlazorBasicTestApp/App/Layout/NavMenu.razor create mode 100644 src/mono/wasm/testassets/BlazorBasicTestApp/App/Pages/Counter.razor create mode 100644 src/mono/wasm/testassets/BlazorBasicTestApp/App/Pages/Home.razor create mode 100644 src/mono/wasm/testassets/BlazorBasicTestApp/App/Program.cs create mode 100644 src/mono/wasm/testassets/BlazorBasicTestApp/App/_Imports.razor create mode 100644 src/mono/wasm/testassets/BlazorBasicTestApp/App/wwwroot/favicon.png create mode 100644 src/mono/wasm/testassets/BlazorBasicTestApp/App/wwwroot/icon-192.png create mode 100644 src/mono/wasm/testassets/BlazorBasicTestApp/App/wwwroot/index.html create mode 100644 src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/Component1.razor create mode 100644 src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/RazorClassLibrary.csproj create mode 100644 src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/_Imports.razor create mode 100644 src/mono/wasm/testassets/EntryPoints/AsyncMainWithArgs.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/CultureResource.cs rename src/mono/wasm/testassets/{Wasm.Buid.Tests.Programs => EntryPoints}/HybridGlobalization.cs (50%) create mode 100644 src/mono/wasm/testassets/EntryPoints/InvariantGlobalization.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/InvariantTimezone.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/MyDllImport.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/NativeCrypto.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/NativeRebuildNewAssembly.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/PInvoke/AbiRules.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/PInvoke/BittableDifferentAssembly.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/PInvoke/BittableDifferentAssembly_Lib.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/PInvoke/BittableSameAssembly.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/PInvoke/BuildNative.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/PInvoke/ComInterop.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/PInvoke/DllImportNoWarning.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/PInvoke/DllImportWarning.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/PInvoke/FunctionPointers.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/PInvoke/ICall_Lib.cs rename src/mono/wasm/testassets/{Wasm.Buid.Tests.Programs => EntryPoints/PInvoke}/UnmanagedCallback.cs (83%) create mode 100644 src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallbackInFile.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallbackNamespaced.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/PInvoke/VariadicFunctions.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/SimpleSourceChange.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/SkiaSharp.cs create mode 100644 src/mono/wasm/testassets/EntryPoints/SyncMainWithArgs.cs delete mode 100644 src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs delete mode 100644 src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantTimezone.cs rename src/mono/wasm/testassets/WasmBasicTestApp/{Library => Json}/Json.cs (100%) rename src/mono/wasm/testassets/WasmBasicTestApp/{Library => Json}/Json.csproj (100%) create mode 100644 src/mono/wasm/testassets/WasmBasicTestApp/Library/Library.cs create mode 100644 src/mono/wasm/testassets/WasmBasicTestApp/Library/Library.csproj create mode 100644 src/mono/wasm/testassets/native-libs/mylib.cpp diff --git a/eng/testing/scenarios/BuildWasmAppsJobsList.txt b/eng/testing/scenarios/BuildWasmAppsJobsList.txt index 1f8b21d6eca66e..8df77ea901da48 100644 --- a/eng/testing/scenarios/BuildWasmAppsJobsList.txt +++ b/eng/testing/scenarios/BuildWasmAppsJobsList.txt @@ -3,23 +3,21 @@ Wasm.Build.NativeRebuild.Tests.NoopNativeRebuildTest Wasm.Build.NativeRebuild.Tests.OptimizationFlagChangeTests Wasm.Build.NativeRebuild.Tests.ReferenceNewAssemblyRebuildTest Wasm.Build.NativeRebuild.Tests.SimpleSourceChangeRebuildTest -Wasm.Build.Tests.TestAppScenarios.InterpPgoTests +Wasm.Build.Tests.InterpPgoTests Wasm.Build.Templates.Tests.NativeBuildTests Wasm.Build.Tests.Blazor.AppsettingsTests Wasm.Build.Tests.Blazor.BuildPublishTests Wasm.Build.Tests.Blazor.SimpleRunTests Wasm.Build.Tests.Blazor.CleanTests Wasm.Build.Tests.Blazor.MiscTests -Wasm.Build.Tests.Blazor.MiscTests2 -Wasm.Build.Tests.Blazor.MiscTests3 +Wasm.Build.Tests.Blazor.DllImportTests Wasm.Build.Tests.Blazor.NativeTests Wasm.Build.Tests.Blazor.NoopNativeRebuildTest Wasm.Build.Tests.Blazor.WorkloadRequiredTests -Wasm.Build.Tests.Blazor.IcuTests -Wasm.Build.Tests.Blazor.IcuShardingTests Wasm.Build.Tests.Blazor.SignalRClientTests Wasm.Build.Tests.BuildPublishTests Wasm.Build.Tests.ConfigSrcTests +Wasm.Build.Tests.DllImportTests Wasm.Build.Tests.IcuShardingTests Wasm.Build.Tests.IcuShardingTests2 Wasm.Build.Tests.IcuTests @@ -32,13 +30,13 @@ Wasm.Build.Tests.NonWasmTemplateBuildTests Wasm.Build.Tests.PInvokeTableGeneratorTests Wasm.Build.Tests.RebuildTests Wasm.Build.Tests.SatelliteAssembliesTests -Wasm.Build.Tests.TestAppScenarios.AppSettingsTests -Wasm.Build.Tests.TestAppScenarios.DownloadThenInitTests -Wasm.Build.Tests.TestAppScenarios.LazyLoadingTests -Wasm.Build.Tests.TestAppScenarios.LibraryInitializerTests -Wasm.Build.Tests.TestAppScenarios.SatelliteLoadingTests -Wasm.Build.Tests.TestAppScenarios.ModuleConfigTests -Wasm.Build.Tests.TestAppScenarios.MemoryTests +Wasm.Build.Tests.AppSettingsTests +Wasm.Build.Tests.DownloadThenInitTests +Wasm.Build.Tests.LazyLoadingTests +Wasm.Build.Tests.LibraryInitializerTests +Wasm.Build.Tests.SatelliteLoadingTests +Wasm.Build.Tests.ModuleConfigTests +Wasm.Build.Tests.MemoryTests Wasm.Build.Tests.AspNetCore.SignalRClientTests Wasm.Build.Tests.WasmBuildAppTest Wasm.Build.Tests.WasmNativeDefaultsTests diff --git a/src/mono/wasi/Wasi.Build.Tests/BuildTestBase.cs b/src/mono/wasi/Wasi.Build.Tests/BuildTestBase.cs index 76bc6fed455bea..c05c99d72c8c9e 100644 --- a/src/mono/wasi/Wasi.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasi/Wasi.Build.Tests/BuildTestBase.cs @@ -758,6 +758,13 @@ public record BuildProjectOptions string? TargetFramework = null, IDictionary? ExtraBuildEnvironmentVariables = null ); + + public record AssertBundleOptions( + BuildProjectOptions BuildOptions, + bool ExpectSymbolsFile = true, + bool AssertIcuAssets = true, + bool AssertSymbolsFile = true + ); public enum NativeFilesType { FromRuntimePack, Relinked, AOT }; } diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/SharedBuildPerTestClassFixture.cs b/src/mono/wasi/Wasi.Build.Tests/SharedBuildPerTestClassFixture.cs similarity index 95% rename from src/mono/wasm/Wasm.Build.Tests/Common/SharedBuildPerTestClassFixture.cs rename to src/mono/wasi/Wasi.Build.Tests/SharedBuildPerTestClassFixture.cs index 019391f3efaade..a89fc21946b522 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/SharedBuildPerTestClassFixture.cs +++ b/src/mono/wasi/Wasi.Build.Tests/SharedBuildPerTestClassFixture.cs @@ -9,6 +9,7 @@ #nullable enable +// ToDo: should be common with Wasm.Build.Tests, copied here after Wasm.Build.Tests refactoring namespace Wasm.Build.Tests { public class SharedBuildPerTestClassFixture : IDisposable diff --git a/src/mono/wasm/Wasm.Build.Tests/AppSettingsTests.cs b/src/mono/wasm/Wasm.Build.Tests/AppSettingsTests.cs new file mode 100644 index 00000000000000..49eff5165bf0fd --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/AppSettingsTests.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +#nullable enable + +namespace Wasm.Build.Tests; + +public class AppSettingsTests : WasmTemplateTestsBase +{ + public AppSettingsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + [Theory] + [InlineData("Development")] + [InlineData("Production")] + public async Task LoadAppSettingsBasedOnApplicationEnvironment(string applicationEnvironment) + { + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.WasmBasicTestApp, "AppSettingsTest"); + PublishProject(info, config); + BrowserRunOptions options = new( + config, + TestScenario: "AppSettingsTest", + BrowserQueryString: new NameValueCollection { { "applicationEnvironment", applicationEnvironment } } + ); + RunResult result = await RunForPublishWithWebServer(options); + Assert.Contains(result.TestOutput, m => m.Contains("'/appsettings.json' exists 'True'")); + Assert.Contains(result.TestOutput, m => m.Contains($"'/appsettings.Development.json' exists '{applicationEnvironment == "Development"}'")); + Assert.Contains(result.TestOutput, m => m.Contains($"'/appsettings.Production.json' exists '{applicationEnvironment == "Production"}'")); + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/AspNetCore/SignalRClientTests.cs b/src/mono/wasm/Wasm.Build.Tests/AspNetCore/SignalRClientTests.cs index 2675e253bcd543..30f3073de9af20 100644 --- a/src/mono/wasm/Wasm.Build.Tests/AspNetCore/SignalRClientTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/AspNetCore/SignalRClientTests.cs @@ -19,10 +19,10 @@ public SignalRClientTests(ITestOutputHelper output, SharedBuildPerTestClassFixtu [ActiveIssue("https://github.com/dotnet/runtime/issues/106807")] [ConditionalTheory(typeof(BuildTestBase), nameof(IsWorkloadWithMultiThreadingForDefaultFramework))] - [InlineData("Debug", "LongPolling")] - [InlineData("Release", "LongPolling")] - [InlineData("Debug", "WebSockets")] - [InlineData("Release", "WebSockets")] - public async Task SignalRPassMessageWasmBrowser(string config, string transport) => + [InlineData(Configuration.Debug, "LongPolling")] + [InlineData(Configuration.Release, "LongPolling")] + [InlineData(Configuration.Debug, "WebSockets")] + [InlineData(Configuration.Release, "WebSockets")] + public async Task SignalRPassMessageWasmBrowser(Configuration config, string transport) => await SignalRPassMessage("wasmclient", config, transport); } diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/AppsettingsTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/AppsettingsTests.cs index db0607d226a8c3..fa47fc4b458717 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/AppsettingsTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/AppsettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Xunit; @@ -21,39 +22,34 @@ public AppsettingsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture [Fact] public async Task FileInVfs() { - string id = $"blazor_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); - - string projectDirectory = Path.GetDirectoryName(projectFile)!; - - File.WriteAllText(Path.Combine(projectDirectory, "wwwroot", "appsettings.json"), $"{{ \"Id\": \"{id}\" }}"); - - string programPath = Path.Combine(projectDirectory, "Program.cs"); - string programContent = File.ReadAllText(programPath); - programContent = programContent.Replace("var builder", - """ - System.Console.WriteLine($"appSettings Exists '{File.Exists("/appsettings.json")}'"); - System.Console.WriteLine($"appSettings Content '{File.ReadAllText("/appsettings.json")}'"); - var builder - """); - File.WriteAllText(programPath, programContent); - - BlazorBuild(new BlazorBuildOptions(id, "debug", NativeFilesType.FromRuntimePack)); + Configuration config = Configuration.Debug; + ProjectInfo info = CreateWasmTemplateProject(Template.BlazorWasm, config, aot: false, "blazor"); + UpdateHomePage(); + string projectDirectory = Path.GetDirectoryName(info.ProjectFilePath)!; + File.WriteAllText(Path.Combine(projectDirectory, "wwwroot", "appsettings.json"), $"{{ \"Id\": \"{info.ProjectName}\" }}"); + UpdateFile("Program.cs", new Dictionary + { + { + "var builder", + """ + System.Console.WriteLine($"appSettings Exists '{File.Exists("/appsettings.json")}'"); + System.Console.WriteLine($"appSettings Content '{File.ReadAllText("/appsettings.json")}'"); + var builder + """ + } + }); + (string _, string buildOutput) = BlazorBuild(info, config); bool existsChecked = false; bool contentChecked = false; - - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() - { - Config = "debug", - OnConsoleMessage = (_, msg) => - { - if (msg.Text.Contains("appSettings Exists 'True'")) + await RunForBuildWithDotnetRun(new BlazorRunOptions( + config, + OnConsoleMessage: (_, msg) => { + if (msg.Contains("appSettings Exists 'True'")) existsChecked = true; - else if (msg.Text.Contains($"appSettings Content '{{ \"Id\": \"{id}\" }}'")) + else if (msg.Contains($"appSettings Content '{{ \"Id\": \"{info.ProjectName}\" }}'")) contentChecked = true; - } - }); + })); Assert.True(existsChecked, "File '/appsettings.json' wasn't found"); Assert.True(contentChecked, "Content of '/appsettings.json' is not matched"); diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorBuildOptions.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorBuildOptions.cs deleted file mode 100644 index cebbcae891f383..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorBuildOptions.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -namespace Wasm.Build.Tests; - -public record BlazorBuildOptions -( - string Id, - string Config, - NativeFilesType ExpectedFileType = NativeFilesType.FromRuntimePack, - string TargetFramework = BuildTestBase.DefaultTargetFrameworkForBlazor, - string BootConfigFileName = "blazor.boot.json", - bool IsPublish = false, - bool WarnAsError = true, - bool ExpectSuccess = true, - bool ExpectRelinkDirWhenPublishing = false, - bool ExpectFingerprintOnDotnetJs = false, - RuntimeVariant RuntimeType = RuntimeVariant.SingleThreaded, - GlobalizationMode GlobalizationMode = GlobalizationMode.Sharded, - string CustomIcuFile = "", - bool AssertAppBundle = true, - string? BinFrameworkDir = null -); diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorRunOptions.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorRunOptions.cs index c0e2a2e60cce0d..628227edc7c85c 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorRunOptions.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorRunOptions.cs @@ -3,26 +3,56 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Threading.Tasks; using Microsoft.Playwright; #nullable enable -namespace Wasm.Build.Tests.Blazor; -public record BlazorRunOptions -( - BlazorRunHost Host = BlazorRunHost.DotnetRun, - bool DetectRuntimeFailures = true, - bool CheckCounter = true, - Dictionary? ServerEnvironment = null, - Func? Test = null, - Action? OnConsoleMessage = null, - Action? OnServerMessage = null, - Action? OnErrorMessage = null, - string Config = "Debug", - string? ExtraArgs = null, - string BrowserPath = "", - string QueryString = "" -); +namespace Wasm.Build.Tests; +public record BlazorRunOptions : RunOptions +{ + public bool CheckCounter { get; init; } + public Func? Test { get; init; } + + public BlazorRunOptions( + Configuration Configuration, + bool AOT = false, + RunHost Host = RunHost.DotnetRun, + bool DetectRuntimeFailures = true, + Dictionary? ServerEnvironment = null, + NameValueCollection? BrowserQueryString = null, + Action? OnConsoleMessage = null, + Action? OnServerMessage = null, + Action? OnErrorMessage = null, + string ExtraArgs = "", + string BrowserPath = "", + string Locale = "en-US", + int? ExpectedExitCode = 0, + string CustomBundleDir = "", + bool CheckCounter = true, + Func? Test = null, + Func? ExecuteAfterLoaded = null + ) : base( + Configuration, + AOT, + Host, + DetectRuntimeFailures, + ServerEnvironment, + BrowserQueryString, + OnConsoleMessage, + OnServerMessage, + OnErrorMessage, + ExtraArgs, + BrowserPath, + Locale, + ExpectedExitCode, + CustomBundleDir, + ExecuteAfterLoaded + ) + { + this.CheckCounter = CheckCounter; + this.Test = Test; + } +} -public enum BlazorRunHost { DotnetRun, WebServer }; diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmProjectProvider.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmProjectProvider.cs deleted file mode 100644 index f715ece03bd553..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmProjectProvider.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.IO; -using Xunit.Abstractions; - -#nullable enable - -namespace Wasm.Build.Tests; - -public class BlazorWasmProjectProvider : WasmSdkBasedProjectProvider -{ - public BlazorWasmProjectProvider(ITestOutputHelper _testOutput, string defaultTargetFramework, string? _projectDir = null) - : base(_testOutput, defaultTargetFramework, _projectDir) - { } - - public void AssertBundle(BlazorBuildOptions options) - => AssertBundle(new AssertWasmSdkBundleOptions( - Config: options.Config, - BootConfigFileName: options.BootConfigFileName, - IsPublish: options.IsPublish, - TargetFramework: options.TargetFramework, - BinFrameworkDir: options.BinFrameworkDir ?? FindBinFrameworkDir(options.Config, options.IsPublish, options.TargetFramework), - GlobalizationMode: options.GlobalizationMode, - CustomIcuFile: options.CustomIcuFile, - ExpectFingerprintOnDotnetJs: options.ExpectFingerprintOnDotnetJs, - ExpectedFileType: options.ExpectedFileType, - RuntimeType: options.RuntimeType, - AssertIcuAssets: true, - AssertSymbolsFile: false // FIXME: not supported yet - )); -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmTestBase.cs index 75769e5da3cbc8..060b58639cb347 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmTestBase.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -17,13 +18,55 @@ namespace Wasm.Build.Tests; public abstract class BlazorWasmTestBase : WasmTemplateTestsBase { - protected readonly BlazorWasmProjectProvider _provider; + protected readonly WasmSdkBasedProjectProvider _provider; + private readonly string _blazorExtraBuildArgs = "-p:BlazorEnableCompression=false /warnaserror"; + protected readonly PublishOptions _defaultBlazorPublishOptions; + private readonly BuildOptions _defaultBlazorBuildOptions; + protected BlazorWasmTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext, new BlazorWasmProjectProvider(output, DefaultTargetFrameworkForBlazor)) + : base(output, buildContext, new WasmSdkBasedProjectProvider(output, DefaultTargetFrameworkForBlazor)) { - _provider = GetProvider(); + _provider = GetProvider(); + _defaultBlazorPublishOptions = _defaultPublishOptions with { ExtraMSBuildArgs = _blazorExtraBuildArgs }; + _defaultBlazorBuildOptions = _defaultBuildOptions with { ExtraMSBuildArgs = _blazorExtraBuildArgs }; } + private Dictionary blazorHomePageReplacements = new Dictionary + { + { + "Welcome to your new app.", + """ + Welcome to your new app. + @code { + protected override void OnAfterRender(bool firstRender) + { + if (firstRender) + { + Console.WriteLine("WASM EXIT 0"); + } + } + } + """ } + }; + + private Func? _executeAfterLoaded = async (runOptions, page) => + { + if (runOptions is BlazorRunOptions bro && bro.CheckCounter) + { + await page.Locator("text=Counter").ClickAsync(); + var txt = await page.Locator("p[role='status']").InnerHTMLAsync(); + Assert.Equal("Current count: 0", txt); + + await page.Locator("text=\"Click me\"").ClickAsync(); + await Task.Delay(300); + txt = await page.Locator("p[role='status']").InnerHTMLAsync(); + Assert.Equal("Current count: 1", txt); + } + }; + + protected void UpdateHomePage() => + UpdateFile(Path.Combine("Pages", "Home.razor"), blazorHomePageReplacements); + public void InitBlazorWasmProjectDir(string id, string targetFramework = DefaultTargetFrameworkForBlazor) { InitPaths(id); @@ -46,92 +89,82 @@ public string CreateBlazorWasmTemplateProject(string id) { InitBlazorWasmProjectDir(id); using DotNetCommand dotnetCommand = new DotNetCommand(s_buildEnv, _testOutput, useDefaultArgs: false); - CommandResult result = dotnetCommand.WithWorkingDirectory(_projectDir!) + CommandResult result = dotnetCommand.WithWorkingDirectory(_projectDir) .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) .ExecuteWithCapturedOutput("new blazorwasm") .EnsureSuccessful(); - return Path.Combine(_projectDir!, $"{id}.csproj"); + return Path.Combine(_projectDir, $"{id}.csproj"); } - protected (CommandResult, string) BlazorBuild(BlazorBuildOptions options, params string[] extraArgs) - { - if (options.WarnAsError) - extraArgs = extraArgs.Append("/warnaserror").ToArray(); - - (CommandResult res, string logPath) = BlazorBuildInternal(options.Id, options.Config, publish: false, setWasmDevel: false, expectSuccess: options.ExpectSuccess, extraArgs); + protected (string projectDir, string buildOutput) BlazorBuild(ProjectInfo info, Configuration config, bool? isNativeBuild = null) => + BlazorBuild(info, config, _defaultBlazorBuildOptions, isNativeBuild); - if (options.ExpectSuccess && options.AssertAppBundle) + protected (string projectDir, string buildOutput) BlazorBuild( + ProjectInfo info, Configuration config, MSBuildOptions buildOptions, bool? isNativeBuild = null) + { + try { - AssertBundle(res.Output, options with { IsPublish = false }); + if (buildOptions != _defaultBlazorPublishOptions) + buildOptions = buildOptions with { ExtraMSBuildArgs = $"{buildOptions.ExtraMSBuildArgs} {_blazorExtraBuildArgs}" }; + (string projectDir, string buildOutput) = BuildProject( + info, + config, + buildOptions, + isNativeBuild); + if (buildOptions.ExpectSuccess && buildOptions.AssertAppBundle) + { + // additional blazor-only assert, basic assert is done in BuildProject + AssertBundle(config, buildOutput, buildOptions, isNativeBuild); + } + return (projectDir, buildOutput); } - - return (res, logPath); - } - - protected (CommandResult, string) BlazorPublish(BlazorBuildOptions options, params string[] extraArgs) - { - if (options.WarnAsError) - extraArgs = extraArgs.Append("/warnaserror").ToArray(); - - (CommandResult res, string logPath) = BlazorBuildInternal(options.Id, options.Config, publish: true, setWasmDevel: false, expectSuccess: options.ExpectSuccess, extraArgs); - - if (options.ExpectSuccess && options.AssertAppBundle) + catch (XunitException xe) { - // Because we do relink in Release publish by default - if (options.Config == "Release") - options = options with { ExpectedFileType = NativeFilesType.Relinked }; - - AssertBundle(res.Output, options with { IsPublish = true }); + if (xe.Message.Contains("error CS1001: Identifier expected")) + Utils.DirectoryCopy(_projectDir, _logPath, testOutput: _testOutput); + throw; } - - return (res, logPath); } + + protected (string projectDir, string buildOutput) BlazorPublish(ProjectInfo info, Configuration config, bool? isNativeBuild = null) => + BlazorPublish(info, config, _defaultBlazorPublishOptions, isNativeBuild); - protected (CommandResult res, string logPath) BlazorBuildInternal( - string id, - string config, - bool publish = false, - bool setWasmDevel = true, - bool expectSuccess = true, - params string[] extraArgs) + protected (string projectDir, string buildOutput) BlazorPublish( + ProjectInfo info, Configuration config, PublishOptions publishOptions, bool? isNativeBuild = null) { try { - return BuildProjectWithoutAssert( - id, - config, - new BuildProjectOptions(CreateProject: false, UseCache: false, Publish: publish, ExpectSuccess: expectSuccess), - extraArgs.Concat(new[] - { - "-p:BlazorEnableCompression=false", - setWasmDevel ? "-p:_WasmDevel=true" : string.Empty - }).ToArray()); + if (publishOptions != _defaultBlazorPublishOptions) + publishOptions = publishOptions with { ExtraMSBuildArgs = $"{publishOptions.ExtraMSBuildArgs} {_blazorExtraBuildArgs}" }; + (string projectDir, string buildOutput) = PublishProject( + info, + config, + publishOptions, + isNativeBuild); + if (publishOptions.ExpectSuccess && publishOptions.AssertAppBundle) + { + // additional blazor-only assert, basic assert is done in PublishProject + AssertBundle(config, buildOutput, publishOptions, isNativeBuild); + } + return (projectDir, buildOutput); } catch (XunitException xe) { if (xe.Message.Contains("error CS1001: Identifier expected")) - Utils.DirectoryCopy(_projectDir!, Path.Combine(s_buildEnv.LogRootPath, id), testOutput: _testOutput); + Utils.DirectoryCopy(_projectDir, _logPath, testOutput: _testOutput); throw; } } - public void AssertBundle(string buildOutput, BlazorBuildOptions blazorBuildOptions) + public void AssertBundle(Configuration config, string buildOutput, MSBuildOptions buildOptions, bool? isNativeBuild = null) { - if (IsUsingWorkloads) - { - // In no-workload case, the path would be from a restored nuget - ProjectProviderBase.AssertRuntimePackPath(buildOutput, blazorBuildOptions.TargetFramework ?? DefaultTargetFramework, blazorBuildOptions.RuntimeType); - } - - _provider.AssertBundle(blazorBuildOptions); - - if (!blazorBuildOptions.IsPublish) + if (!buildOptions.IsPublish) return; + var expectedFileType = _provider.GetExpectedFileType(config, buildOptions.AOT, buildOptions.IsPublish, IsUsingWorkloads, isNativeBuild); // Publish specific checks - - if (blazorBuildOptions.ExpectedFileType == NativeFilesType.AOT) + if (expectedFileType == NativeFilesType.AOT) { // check for this too, so we know the format is correct for the negative // test for jsinterop.webassembly.dll @@ -141,112 +174,52 @@ public void AssertBundle(string buildOutput, BlazorBuildOptions blazorBuildOptio Assert.DoesNotContain("Microsoft.JSInterop.WebAssembly.dll -> Microsoft.JSInterop.WebAssembly.dll.bc", buildOutput); } - string objBuildDir = Path.Combine(_projectDir!, "obj", blazorBuildOptions.Config, blazorBuildOptions.TargetFramework!, "wasm", "for-build"); + string objBuildDir = Path.Combine(_projectDir, "obj", config.ToString(), buildOptions.TargetFramework!, "wasm", "for-build"); // Check that we linked only for publish - if (blazorBuildOptions.ExpectRelinkDirWhenPublishing) + if (buildOptions is PublishOptions publishOptions && publishOptions.ExpectRelinkDirWhenPublishing) Assert.True(Directory.Exists(objBuildDir), $"Could not find expected {objBuildDir}, which gets created when relinking during Build. This is likely a test authoring error"); else Assert.False(File.Exists(Path.Combine(objBuildDir, "emcc-link.rsp")), $"Found unexpected `emcc-link.rsp` in {objBuildDir}, which gets created when relinking during Build."); } - protected string CreateProjectWithNativeReference(string id) + protected ProjectInfo CreateProjectWithNativeReference(Configuration config, bool aot, string extraProperties) { - CreateBlazorWasmTemplateProject(id); - string extraItems = @$" {GetSkiaSharpReferenceItems()} "; - string projectFile = Path.Combine(_projectDir!, $"{id}.csproj"); - AddItemsPropertiesToProject(projectFile, extraItems: extraItems); - - return projectFile; + return CopyTestAsset( + config, aot, TestAsset.BlazorBasicTestApp, "blz_nativeref_aot", extraItems: extraItems, extraProperties: extraProperties); } // Keeping these methods with explicit Build/Publish in the name // so in the test code it is evident which is being run! - public Task BlazorRunForBuildWithDotnetRun(BlazorRunOptions runOptions) - => BlazorRunTest(runOptions with { Host = BlazorRunHost.DotnetRun }); - - public Task BlazorRunForPublishWithWebServer(BlazorRunOptions runOptions) - => BlazorRunTest(runOptions with { Host = BlazorRunHost.WebServer }); - - public Task BlazorRunTest(BlazorRunOptions runOptions) => runOptions.Host switch + public override async Task RunForBuildWithDotnetRun(RunOptions runOptions) => + await base.RunForBuildWithDotnetRun(runOptions with { + ExecuteAfterLoaded = runOptions.ExecuteAfterLoaded ?? _executeAfterLoaded, + ServerEnvironment = GetServerEnvironmentForBuild(runOptions.ServerEnvironment) + }); + + public override async Task RunForPublishWithWebServer(RunOptions runOptions) + => await base.RunForPublishWithWebServer(runOptions with { + ExecuteAfterLoaded = runOptions.ExecuteAfterLoaded ?? _executeAfterLoaded + }); + + private Dictionary? GetServerEnvironmentForBuild(Dictionary? originalServerEnv) { - BlazorRunHost.DotnetRun => - BlazorRunTest($"run -c {runOptions.Config} --no-build", _projectDir!, runOptions), - - BlazorRunHost.WebServer => - BlazorRunTest($"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files", - Path.GetFullPath(Path.Combine(FindBlazorBinFrameworkDir(runOptions.Config, forPublish: true), "..")), - runOptions), - - _ => throw new NotImplementedException(runOptions.Host.ToString()) - }; - - public async Task BlazorRunTest(string runArgs, - string workingDirectory, - BlazorRunOptions runOptions) - { - if (!string.IsNullOrEmpty(runOptions.ExtraArgs)) - runArgs += $" {runOptions.ExtraArgs}"; - - runOptions.ServerEnvironment?.ToList().ForEach( - kv => s_buildEnv.EnvVars[kv.Key] = kv.Value); - - using RunCommand runCommand = new RunCommand(s_buildEnv, _testOutput); - ToolCommand cmd = runCommand.WithWorkingDirectory(workingDirectory); - - await using var runner = new BrowserRunner(_testOutput); - var page = await runner.RunAsync( - cmd, - runArgs, - onConsoleMessage: OnConsoleMessage, - onServerMessage: runOptions.OnServerMessage, - onError: OnErrorMessage, - modifyBrowserUrl: browserUrl => new Uri(new Uri(browserUrl), runOptions.BrowserPath + runOptions.QueryString).ToString()); - - _testOutput.WriteLine("Waiting for page to load"); - await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded, new () { Timeout = 1 * 60 * 1000 }); - - if (runOptions.CheckCounter) + var serverEnvironment = new Dictionary(); + if (originalServerEnv != null) { - await page.Locator("text=Counter").ClickAsync(); - var txt = await page.Locator("p[role='status']").InnerHTMLAsync(); - Assert.Equal("Current count: 0", txt); - - await page.Locator("text=\"Click me\"").ClickAsync(); - await Task.Delay(300); - txt = await page.Locator("p[role='status']").InnerHTMLAsync(); - Assert.Equal("Current count: 1", txt); - } - - if (runOptions.Test is not null) - await runOptions.Test(page); - - _testOutput.WriteLine($"Waiting for additional 10secs to see if any errors are reported"); - await Task.Delay(10_000); - - void OnConsoleMessage(IPage page, IConsoleMessage msg) - { - _testOutput.WriteLine($"[{msg.Type}] {msg.Text}"); - - runOptions.OnConsoleMessage?.Invoke(page, msg); - - if (runOptions.DetectRuntimeFailures) + foreach (var kvp in originalServerEnv) { - if (msg.Text.Contains("[MONO] * Assertion") || msg.Text.Contains("Error: [MONO] ")) - throw new XunitException($"Detected a runtime failure at line: {msg.Text}"); + serverEnvironment.Add(kvp.Key, kvp.Value); } } - - void OnErrorMessage(string msg) - { - _testOutput.WriteLine($"[ERROR] {msg}"); - runOptions.OnErrorMessage?.Invoke(msg); - } + // avoid "System.IO.IOException: address already in use" + serverEnvironment.Add("ASPNETCORE_URLS", "http://127.0.0.1:0"); + return serverEnvironment; } - public string FindBlazorBinFrameworkDir(string config, bool forPublish, string framework = DefaultTargetFrameworkForBlazor, string? projectDir = null) - => _provider.FindBinFrameworkDir(config: config, forPublish: forPublish, framework: framework, projectDir: projectDir); + public string GetBlazorBinFrameworkDir(Configuration config, bool forPublish, string framework = DefaultTargetFrameworkForBlazor, string? projectDir = null) + => _provider.GetBinFrameworkDir(config: config, forPublish: forPublish, framework: framework, projectDir: projectDir); } diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs index 5877b0f6a06dd4..56364a1a09c390 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs @@ -23,150 +23,107 @@ public BuildPublishTests(ITestOutputHelper output, SharedBuildPerTestClassFixtur _enablePerTestCleanup = true; } - [Theory, TestCategory("no-workload")] - [InlineData("Debug")] - [InlineData("Release")] - public async Task DefaultTemplate_WithoutWorkload(string config) + public static TheoryData TestDataForDefaultTemplate_WithWorkload(bool isAot) { - string id = $"blz_no_workload_{config}_{GetRandomId()}_{s_unicodeChars}"; - CreateBlazorWasmTemplateProject(id); - - BlazorBuild(new BlazorBuildOptions(id, config)); - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config }); - - BlazorPublish(new BlazorBuildOptions(id, config)); - await BlazorRunForPublishWithWebServer(new BlazorRunOptions() { Config = config }); - } - - - public static TheoryData TestDataForDefaultTemplate_WithWorkload(bool isAot) - { - var data = new TheoryData(); + var data = new TheoryData(); if (!isAot) { // AOT does not support managed debugging, is disabled by design - data.Add("Debug", false); - data.Add("Debug", true); + data.Add(Configuration.Debug, false); + data.Add(Configuration.Debug, true); } // [ActiveIssue("https://github.com/dotnet/runtime/issues/103625", TestPlatforms.Windows)] // when running locally the path might be longer than 260 chars and these tests can fail with AOT - data.Add("Release", false); // Release relinks by default - data.Add("Release", true); + data.Add(Configuration.Release, false); // Release relinks by default + data.Add(Configuration.Release, true); return data; } [Theory] [MemberData(nameof(TestDataForDefaultTemplate_WithWorkload), parameters: new object[] { false })] - public void DefaultTemplate_NoAOT_WithWorkload(string config, bool testUnicode) + public void DefaultTemplate_NoAOT_WithWorkload(Configuration config, bool testUnicode) { - string id = testUnicode ? - $"blz_no_aot_{config}_{GetRandomId()}_{s_unicodeChars}" : - $"blz_no_aot_{config}_{GetRandomId()}"; - CreateBlazorWasmTemplateProject(id); - - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack)); - if (config == "Release") - { - // relinking in publish for Release config - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.Relinked, ExpectRelinkDirWhenPublishing: true)); - } - else - { - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack, ExpectRelinkDirWhenPublishing: true)); - } + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blz_no_aot", appendUnicodeToPath: testUnicode); + BlazorPublish(info, config); } [Theory] [MemberData(nameof(TestDataForDefaultTemplate_WithWorkload), parameters: new object[] { true })] - public void DefaultTemplate_AOT_WithWorkload(string config, bool testUnicode) + public void DefaultTemplate_AOT_WithWorkload(Configuration config, bool testUnicode) { - string id = testUnicode ? - $"blz_aot_{config}_{GetRandomId()}_{s_unicodeChars}" : - $"blz_aot_{config}_{GetRandomId()}"; - CreateBlazorWasmTemplateProject(id); + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blz_aot", appendUnicodeToPath: testUnicode); + BlazorBuild(info, config); - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack)); - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.AOT), "-p:RunAOTCompilation=true"); + PublishProject(info, config, new PublishOptions(AOT: true, UseCache: false)); } [Theory] - [InlineData("Debug", false)] - [InlineData("Release", false)] - [InlineData("Debug", true)] - [InlineData("Release", true)] - public void DefaultTemplate_CheckFingerprinting(string config, bool expectFingerprintOnDotnetJs) + [InlineData(Configuration.Debug, false)] + [InlineData(Configuration.Release, false)] + [InlineData(Configuration.Debug, true)] + [InlineData(Configuration.Release, true)] + public void DefaultTemplate_CheckFingerprinting(Configuration config, bool expectFingerprintOnDotnetJs) { - string id = $"blz_checkfingerprinting_{config}_{GetRandomId()}"; - - CreateBlazorWasmTemplateProject(id); - - var options = new BlazorBuildOptions(id, config, NativeFilesType.Relinked, ExpectRelinkDirWhenPublishing: true, ExpectFingerprintOnDotnetJs: expectFingerprintOnDotnetJs); - var finterprintingArg = expectFingerprintOnDotnetJs ? "/p:WasmFingerprintDotnetJs=true" : string.Empty; - - BlazorBuild(options, "/p:WasmBuildNative=true", finterprintingArg); - BlazorPublish(options, "/p:WasmBuildNative=true", finterprintingArg); + var extraProperty = expectFingerprintOnDotnetJs ? + "truetrue" : + "true"; + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blz_checkfingerprinting", extraProperties: extraProperty); + BlazorBuild(info, config, isNativeBuild: true); + BlazorPublish(info, config, new PublishOptions(UseCache: false), isNativeBuild: true); } // Disabling for now - publish folder can have more than one dotnet*hash*js, and not sure // how to pick which one to check, for the test //[Theory] - //[InlineData("Debug")] - //[InlineData("Release")] - //public void DefaultTemplate_AOT_OnlyWithPublishCommandLine_Then_PublishNoAOT(string config) + //[InlineData(Configuration.Debug)] + //[InlineData(Configuration.Release)] + //public void DefaultTemplate_AOT_OnlyWithPublishCommandLine_Then_PublishNoAOT(Configuration config) //{ //string id = $"blz_aot_pub_{config}"; //CreateBlazorWasmTemplateProject(id); //// No relinking, no AOT - //BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack); + //BlazorBuild(new BuildOptions(id, config, NativeFilesType.FromRuntimePack); //// AOT=true only for the publish command line, similar to what //// would happen when setting it in Publish dialog for VS - //BlazorPublish(new BlazorBuildOptions(id, config, expectedFileType: NativeFilesType.AOT, "-p:RunAOTCompilation=true"); + //BlazorPublish(new BuildOptions(id, config, expectedFileType: NativeFilesType.AOT, "-p:RunAOTCompilation=true"); //// publish again, no AOT - //BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.Relinked); + //BlazorPublish(new BuildOptions(id, config, NativeFilesType.Relinked); //} [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public void DefaultTemplate_WithResources_Publish(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void DefaultTemplate_WithResources_Publish(Configuration config) { string[] cultures = ["ja-JP", "es-ES"]; - string id = $"blz_resources_{config}_{GetRandomId()}"; - CreateBlazorWasmTemplateProject(id); + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blz_resources"); // Ensure we have the source data we rely on string resxSourcePath = Path.Combine(BuildEnvironment.TestAssetsPath, "resx"); foreach (string culture in cultures) Assert.True(File.Exists(Path.Combine(resxSourcePath, $"words.{culture}.resx"))); - Utils.DirectoryCopy(resxSourcePath, Path.Combine(_projectDir!, "resx")); + Utils.DirectoryCopy(resxSourcePath, Path.Combine(_projectDir, "resx")); // Build and assert resource dlls - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack)); - AssertResourcesDlls(FindBlazorBinFrameworkDir(config, false)); + BlazorBuild(info, config); + AssertResourcesDlls(GetBlazorBinFrameworkDir(config, forPublish: false)); // Publish and assert resource dlls - if (config == "Release") - { - // relinking in publish for Release config - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.Relinked, ExpectRelinkDirWhenPublishing: true, IsPublish: true)); - } - else - { - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack, ExpectRelinkDirWhenPublishing: true, IsPublish: true)); - } - - AssertResourcesDlls(FindBlazorBinFrameworkDir(config, true)); + BlazorPublish(info, config, new PublishOptions(UseCache: false)); + AssertResourcesDlls(GetBlazorBinFrameworkDir(config, forPublish: true)); void AssertResourcesDlls(string basePath) { foreach (string culture in cultures) { - string? resourceAssemblyPath = Directory.EnumerateFiles(Path.Combine(basePath, culture), $"*{ProjectProviderBase.WasmAssemblyExtension}").SingleOrDefault(f => Path.GetFileNameWithoutExtension(f).StartsWith($"{id}.resources")); + string? resourceAssemblyPath = Directory.EnumerateFiles( + Path.Combine(basePath, culture), + $"*{ProjectProviderBase.WasmAssemblyExtension}").SingleOrDefault(f => Path.GetFileNameWithoutExtension(f).StartsWith($"{info.ProjectName}.resources")); Assert.True(resourceAssemblyPath != null && File.Exists(resourceAssemblyPath), $"Expects to have a resource assembly at {resourceAssemblyPath}"); } } @@ -177,36 +134,29 @@ void AssertResourcesDlls(string basePath) [InlineData("false", false)] // the other case public async Task Test_WasmStripILAfterAOT(string stripILAfterAOT, bool expectILStripping) { - string config = "Release"; - string id = $"blz_WasmStripILAfterAOT_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - string projectDirectory = Path.GetDirectoryName(projectFile)!; - + Configuration config = Configuration.Release; string extraProperties = "true"; if (!string.IsNullOrEmpty(stripILAfterAOT)) extraProperties += $"{stripILAfterAOT}"; - AddItemsPropertiesToProject(projectFile, extraProperties); + ProjectInfo info = CopyTestAsset(config, aot: true, TestAsset.BlazorBasicTestApp, "blz_WasmStripILAfterAOT", extraProperties: extraProperties); - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.AOT, AssertAppBundle : false)); - await BlazorRunForPublishWithWebServer(new BlazorRunOptions() { Config = config }); + BlazorPublish(info, config); + await RunForPublishWithWebServer(new BlazorRunOptions(config)); - string frameworkDir = Path.Combine(projectDirectory, "bin", config, BuildTestBase.DefaultTargetFrameworkForBlazor, "publish", "wwwroot", "_framework"); - string objBuildDir = Path.Combine(projectDirectory, "obj", config, BuildTestBase.DefaultTargetFrameworkForBlazor, "wasm", "for-publish"); + string frameworkDir = Path.Combine(_projectDir, "bin", config.ToString(), BuildTestBase.DefaultTargetFrameworkForBlazor, "publish", "wwwroot", "_framework"); + string objBuildDir = Path.Combine(_projectDir, "obj", config.ToString(), BuildTestBase.DefaultTargetFrameworkForBlazor, "wasm", "for-publish"); WasmTemplateTests.TestWasmStripILAfterAOTOutput(objBuildDir, frameworkDir, expectILStripping, _testOutput); } [Theory] - [InlineData("Debug")] - public void BlazorWasm_CannotAOT_InDebug(string config) + [InlineData(Configuration.Debug)] + public void BlazorWasm_CannotAOT_InDebug(Configuration config) { - string id = $"blazorwasm_{config}_aot_{GetRandomId()}"; - CreateBlazorWasmTemplateProject(id); - AddItemsPropertiesToProject(Path.Combine(_projectDir!, $"{id}.csproj"), - extraItems: null, - extraProperties: "true"); - - (CommandResult res, _) = BlazorPublish(new BlazorBuildOptions(id, config, ExpectSuccess: false)); - Assert.Contains("AOT is not supported in debug configuration", res.Output); + ProjectInfo info = CopyTestAsset( + config, aot: true, TestAsset.BlazorBasicTestApp, "blazorwasm", extraProperties: "true"); + + (string _, string output) = PublishProject(info, config, new PublishOptions(ExpectSuccess: false)); + Assert.Contains("AOT is not supported in debug configuration", output); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/CleanTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/CleanTests.cs index 122aa274962373..2d9a8baa260f7a 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/CleanTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/CleanTests.cs @@ -21,27 +21,20 @@ public CleanTests(ITestOutputHelper output, SharedBuildPerTestClassFixture build } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public void Blazor_BuildThenClean_NativeRelinking(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void Blazor_BuildThenClean_NativeRelinking(Configuration config) { - string id = GetRandomId(); + string extraProperties = @"<_WasmDevel>truetrue"; + ProjectInfo info = CopyTestAsset(config, aot: true, TestAsset.BlazorBasicTestApp, "clean", extraProperties: extraProperties); + BlazorBuild(info, config, isNativeBuild: true); - InitBlazorWasmProjectDir(id); - string projectFile = CreateBlazorWasmTemplateProject(id); - - string extraProperties = @"<_WasmDevel>true - true"; - - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked)); - - string relinkDir = Path.Combine(_projectDir!, "obj", config, DefaultTargetFrameworkForBlazor, "wasm", "for-build"); + string relinkDir = Path.Combine(_projectDir, "obj", config.ToString(), DefaultTargetFrameworkForBlazor, "wasm", "for-build"); Assert.True(Directory.Exists(relinkDir), $"Could not find expected relink dir: {relinkDir}"); - string logPath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-clean.binlog"); + string logPath = Path.Combine(s_buildEnv.LogRootPath, info.ProjectName, $"{info.ProjectName}-clean.binlog"); using ToolCommand cmd = new DotNetCommand(s_buildEnv, _testOutput) - .WithWorkingDirectory(_projectDir!); + .WithWorkingDirectory(_projectDir); cmd.WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) .ExecuteWithCapturedOutput("build", "-t:Clean", $"-p:Configuration={config}", $"-bl:{logPath}") .EnsureSuccessful(); @@ -50,52 +43,51 @@ public void Blazor_BuildThenClean_NativeRelinking(string config) } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public void Blazor_BuildNoNative_ThenBuildNative_ThenClean(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void Blazor_BuildNoNative_ThenBuildNative_ThenClean(Configuration config) => Blazor_BuildNativeNonNative_ThenCleanTest(config, firstBuildNative: false); [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public void Blazor_BuildNative_ThenBuildNonNative_ThenClean(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void Blazor_BuildNative_ThenBuildNonNative_ThenClean(Configuration config) => Blazor_BuildNativeNonNative_ThenCleanTest(config, firstBuildNative: true); - private void Blazor_BuildNativeNonNative_ThenCleanTest(string config, bool firstBuildNative) + private void Blazor_BuildNativeNonNative_ThenCleanTest(Configuration config, bool firstBuildNative) { - string id = GetRandomId(); - - InitBlazorWasmProjectDir(id); - string projectFile = CreateBlazorWasmTemplateProject(id); - string extraProperties = @"<_WasmDevel>true"; - - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); + ProjectInfo info = CopyTestAsset(config, aot: true, TestAsset.BlazorBasicTestApp, "clean_native", extraProperties: extraProperties); bool relink = firstBuildNative; - BlazorBuildInternal(id, config, publish: false, - extraArgs: relink ? "-p:WasmBuildNative=true" : string.Empty); + BlazorBuild(info, + config, + new BuildOptions(ExtraMSBuildArgs: relink ? "-p:WasmBuildNative=true" : string.Empty), + isNativeBuild: relink); - string relinkDir = Path.Combine(_projectDir!, "obj", config, DefaultTargetFrameworkForBlazor, "wasm", "for-build"); + string relinkDir = Path.Combine(_projectDir, "obj", config.ToString(), DefaultTargetFrameworkForBlazor, "wasm", "for-build"); if (relink) Assert.True(Directory.Exists(relinkDir), $"Could not find expected relink dir: {relinkDir}"); relink = !firstBuildNative; - BlazorBuildInternal(id, config, publish: false, - extraArgs: relink ? "-p:WasmBuildNative=true" : string.Empty); + BlazorBuild(info, + config, + new BuildOptions(UseCache: false, ExtraMSBuildArgs: relink ? "-p:WasmBuildNative=true" : string.Empty), + isNativeBuild: relink ? true : null); if (relink) Assert.True(Directory.Exists(relinkDir), $"Could not find expected relink dir: {relinkDir}"); - string logPath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-clean.binlog"); + string logPath = Path.Combine(s_buildEnv.LogRootPath, info.ProjectName, $"{info.ProjectName}-clean.binlog"); using ToolCommand cmd = new DotNetCommand(s_buildEnv, _testOutput) - .WithWorkingDirectory(_projectDir!); - cmd.WithEnvironmentVariable("NUGET_PACKAGES", _projectDir!) + .WithWorkingDirectory(_projectDir); + cmd.WithEnvironmentVariable("NUGET_PACKAGES", _projectDir) .ExecuteWithCapturedOutput("build", "-t:Clean", $"-p:Configuration={config}", $"-bl:{logPath}") .EnsureSuccessful(); AssertEmptyOrNonExistentDirectory(relinkDir); } + private void AssertEmptyOrNonExistentDirectory(string dirPath) { _testOutput.WriteLine($"dirPath: {dirPath}"); diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/DllImportTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/DllImportTests.cs new file mode 100644 index 00000000000000..48cb35d332c9a2 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/DllImportTests.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; +using Microsoft.Playwright; + +#nullable enable + +namespace Wasm.Build.Tests.Blazor; + +public class DllImportTests : BlazorWasmTestBase +{ + public DllImportTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + _enablePerTestCleanup = true; + } + + public static TheoryData DllImportTheoryData() + { + var data = new TheoryData(); + data.Add(Configuration.Debug, /*build*/true, /*publish*/false); + data.Add(Configuration.Release, /*build*/true, /*publish*/false); + data.Add(Configuration.Release, /*build*/false, /*publish*/true); + + // ActiveIssue("https://github.com/dotnet/runtime/issues/110482") + if (!s_isWindows) + { + data.Add(Configuration.Release, /*build*/true, /*publish*/true); + } + return data; + } + + [Theory] + [MemberData(nameof(DllImportTheoryData))] + public async Task WithDllImportInMainAssembly(Configuration config, bool build, bool publish) + { + // Based on https://github.com/dotnet/runtime/issues/59255 + string prefix = $"blz_dllimp_{config}_{s_unicodeChars}"; + if (build && publish) + prefix += "build_then_publish"; + else if (build) + prefix += "build"; + else + prefix += "publish"; + string extraItems = @""; + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, prefix, extraItems: extraItems); + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "MyDllImport.cs"), Path.Combine(_projectDir, "Pages", "MyDllImport.cs")); + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "mylib.cpp"), Path.Combine(_projectDir, "mylib.cpp")); + UpdateFile(Path.Combine("Pages", "MyDllImport.cs"), new Dictionary { { "##NAMESPACE##", info.ProjectName } }); + + BlazorAddRazorButton("cpp_add", """ + var result = MyDllImports.cpp_add(10, 12); + outputText = $"{result}"; + """); + + if (build) + BlazorBuild(info, config, isNativeBuild: true); + + if (publish) + BlazorPublish(info, config, new PublishOptions(UseCache: false), isNativeBuild: true); + + BlazorRunOptions runOptions = new(config, Test: TestDllImport); + if (publish) + await RunForPublishWithWebServer(runOptions); + else + await RunForBuildWithDotnetRun(runOptions); + + async Task TestDllImport(IPage page) + { + await page.Locator("text=\"cpp_add\"").ClickAsync(); + var txt = await page.Locator("p[role='test']").InnerHTMLAsync(); + Assert.Equal("Output: 22", txt); + } + } + + private void BlazorAddRazorButton(string buttonText, string customCode, string methodName = "test") => + UpdateFile(Path.Combine("Pages", "Counter.razor"), new Dictionary { + { + @"", + $@" + +

Output: @outputText

+ + " + }, + { + "private int currentCount = 0;", + $@" + private int currentCount = 0; + private string outputText = string.Empty; + public void {methodName}() + {{ + {customCode} + }} + " + } + }); +} diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/IcuShardingTests.cs deleted file mode 100644 index 9a6ba4e09ae8df..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/IcuShardingTests.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.IO; -using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; -using System.Collections.Generic; -using System.Threading.Tasks; - -#nullable enable - -namespace Wasm.Build.Tests.Blazor; - -// these tests only check if correct ICU files got copied -public class IcuShardingTests : BlazorWasmTestBase -{ - public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) {} - - [Theory] - [InlineData("Debug", "icudt.dat")] - [InlineData("Release", "icudt.dat")] - [InlineData("Debug", "icudt_CJK.dat")] - [InlineData("Release", "icudt_CJK.dat")] - public async Task CustomIcuFileFromRuntimePack(string config, string fileName) - { - string id = $"blz_customFromRuntimePack_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - var buildOptions = new BlazorBuildOptions( - id, - config, - WarnAsError: true, - GlobalizationMode: GlobalizationMode.Custom, - CustomIcuFile: fileName - ); - AddItemsPropertiesToProject( - projectFile, - extraProperties: - $"{fileName}"); - - (CommandResult res, string logPath) = BlazorBuild(buildOptions); - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config }); - } - - [Theory] - [InlineData("Debug", "incorrectName.dat", false)] - [InlineData("Release", "incorrectName.dat", false)] - [InlineData("Debug", "icudtNonExisting.dat", true)] - [InlineData("Release", "icudtNonExisting.dat", true)] - public void NonExistingCustomFileAssertError(string config, string fileName, bool isFilenameCorrect) - { - string id = $"blz_invalidCustomIcu_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - AddItemsPropertiesToProject( - projectFile, - extraProperties: - $"{fileName}"); - - try - { - (CommandResult res, string logPath) = BlazorBuild( - new BlazorBuildOptions( - id, - config, - WarnAsError: false, - GlobalizationMode: GlobalizationMode.Custom, - CustomIcuFile: fileName - )); - } - catch (XunitException ex) - { - if (isFilenameCorrect) - { - Assert.Contains($"Could not find $(BlazorIcuDataFileName)={fileName}, or when used as a path relative to the runtime pack", ex.Message); - } - else - { - Assert.Contains("File name in $(BlazorIcuDataFileName) has to start with 'icudt'", ex.Message); - } - } - catch (Exception) - { - throw new Exception("Unexpected exception in test scenario."); - } - // we expect build error, so there is not point in running the app - } - - [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public async Task CustomFileNotFromRuntimePackAbsolutePath(string config) - { - string id = $"blz_invalidCustomIcu_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - AddItemsPropertiesToProject( - projectFile, - extraProperties: - $"{IcuTestsBase.CustomIcuPath}"); - - (CommandResult res, string logPath) = BlazorBuild( - new BlazorBuildOptions( - id, - config, - WarnAsError: false, - GlobalizationMode: GlobalizationMode.Custom, - CustomIcuFile: IcuTestsBase.CustomIcuPath - )); - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config }); - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/IcuTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/IcuTests.cs deleted file mode 100644 index 8d8914ee195f9a..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/IcuTests.cs +++ /dev/null @@ -1,134 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.IO; -using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; -using System.Collections.Generic; -using System.Threading.Tasks; - -#nullable enable - -namespace Wasm.Build.Tests.Blazor; - -// these tests only check if correct ICU files got copied -public class IcuTests : BlazorWasmTestBase -{ - public IcuTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) {} - - [Theory] - [InlineData("Debug", false)] - [InlineData("Debug", true)] - [InlineData("Debug", null)] - [InlineData("Release", false)] - [InlineData("Release", true)] - [InlineData("Release", null)] - public async Task HybridWithInvariant(string config, bool? invariant) - { - string id = $"blz_hybrid_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - string extraProperties = "true"; - if (invariant != null) - extraProperties += $"{invariant}"; - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); - - (CommandResult res, string logPath) = BlazorBuild( - new BlazorBuildOptions( - id, - config, - WarnAsError: false, - GlobalizationMode: invariant == true ? GlobalizationMode.Invariant : GlobalizationMode.Hybrid, - ExpectedFileType: invariant == true ? NativeFilesType.Relinked : NativeFilesType.FromRuntimePack - )); - - string warning = "$(HybridGlobalization) has no effect when $(InvariantGlobalization) is set to true."; - if (invariant == true) - { - Assert.Contains(warning, res.Output); - } - else - { - Assert.DoesNotContain(warning, res.Output); - } - - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config }); - } - - [Theory] - [InlineData("Debug", false)] - [InlineData("Debug", true)] - [InlineData("Debug", null)] - [InlineData("Release", false)] - [InlineData("Release", true)] - [InlineData("Release", null)] - public async Task HybridWithFullIcuFromRuntimePack(string config, bool? fullIcu) - { - string id = $"blz_hybrid_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - string extraProperties = "true"; - if (fullIcu != null) - extraProperties += $"{fullIcu}"; - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); - - (CommandResult res, string logPath) = BlazorBuild( - new BlazorBuildOptions( - id, - config, - WarnAsError: false, - GlobalizationMode: GlobalizationMode.Hybrid - )); - - string warning = "$(BlazorWebAssemblyLoadAllGlobalizationData) has no effect when $(HybridGlobalization) is set to true."; - if (fullIcu == true) - { - Assert.Contains(warning, res.Output); - } - else - { - Assert.DoesNotContain(warning, res.Output); - } - - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config }); - } - - [Theory] - [InlineData("Debug", false)] - [InlineData("Debug", true)] - [InlineData("Debug", null)] - [InlineData("Release", false)] - [InlineData("Release", true)] - [InlineData("Release", null)] - public async Task FullIcuFromRuntimePackWithInvariant(string config, bool? invariant) - { - string id = $"blz_hybrid_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - string extraProperties = "true"; - if (invariant != null) - extraProperties += $"{invariant}"; - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); - - (CommandResult res, string logPath) = BlazorBuild( - new BlazorBuildOptions( - id, - config, - WarnAsError: false, - GlobalizationMode: invariant == true ? GlobalizationMode.Invariant : GlobalizationMode.FullIcu, - ExpectedFileType: invariant == true ? NativeFilesType.Relinked : NativeFilesType.FromRuntimePack - )); - - string warning = "$(BlazorWebAssemblyLoadAllGlobalizationData) has no effect when $(InvariantGlobalization) is set to true."; - if (invariant == true) - { - Assert.Contains(warning, res.Output); - } - else - { - Assert.DoesNotContain(warning, res.Output); - } - - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config }); - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs index c93ec2c6af452f..9ef748b8678081 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs @@ -1,9 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.IO; +using System.Linq; +using System.Text.Json; using Xunit; using Xunit.Abstractions; +using Xunit.Sdk; #nullable enable @@ -18,60 +22,85 @@ public MiscTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildC } [Theory] - [InlineData("Debug", true)] - [InlineData("Debug", false)] - [InlineData("Release", true)] - [InlineData("Release", false)] - public void NativeBuild_WithDeployOnBuild_UsedByVS(string config, bool nativeRelink) + [InlineData(Configuration.Debug, true)] + [InlineData(Configuration.Debug, false)] + [InlineData(Configuration.Release, true)] + [InlineData(Configuration.Release, false)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/103566")] + public void NativeBuild_WithDeployOnBuild_UsedByVS(Configuration config, bool nativeRelink) { - string id = $"blz_deploy_on_build_{config}_{nativeRelink}_{GetRandomId()}"; - string projectFile = CreateProjectWithNativeReference(id); - string extraProperties = config == "Debug" + string extraProperties = config == Configuration.Debug ? ("-O1" + "-O1") : string.Empty; if (!nativeRelink) extraProperties += "true"; - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); + ProjectInfo info = CopyTestAsset(config, aot: true, TestAsset.BlazorBasicTestApp, "blz_deploy_on_build", extraProperties: extraProperties); // build with -p:DeployOnBuild=true, and that will trigger a publish - (CommandResult res, _) = BlazorBuild(new BlazorBuildOptions( - Id: id, - Config: config, - ExpectedFileType: nativeRelink ? NativeFilesType.Relinked : NativeFilesType.AOT, - ExpectRelinkDirWhenPublishing: false, - IsPublish: false), - "-p:DeployBuild=true"); + (string _, string buildOutput) = BlazorBuild(info, + config, + new BuildOptions(ExtraMSBuildArgs: "-p:DeployBuild=true"), + isNativeBuild: true); // double check relinking! - int index = res.Output.IndexOf("pinvoke.c -> pinvoke.o"); - Assert.NotEqual(-1, index); + string substring = "pinvoke.c -> pinvoke.o"; + Assert.Contains(substring, buildOutput); // there should be only one instance of this string! - index = res.Output.IndexOf("pinvoke.c -> pinvoke.o", index + 1); - Assert.Equal(-1, index); + int occurrences = buildOutput.Split(new[] { substring }, StringSplitOptions.None).Length - 1; + Assert.Equal(2, occurrences); } [Theory] - [InlineData("Release")] - public void DefaultTemplate_AOT_InProjectFile(string config) + [InlineData(Configuration.Release)] + public void DefaultTemplate_AOT_InProjectFile(Configuration config) { - string id = $"blz_aot_prj_file_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - - string extraProperties = config == "Debug" - ? ("-O1" + + string extraProperties = config == Configuration.Debug + ? ("true" + + "-O1" + "-O1") - : string.Empty; - AddItemsPropertiesToProject(projectFile, extraProperties: "true" + extraProperties); + : "true"; + ProjectInfo info = CopyTestAsset(config, aot: true, TestAsset.BlazorBasicTestApp, "blz_aot_prj_file", extraProperties: extraProperties); // No relinking, no AOT - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack)); + BlazorBuild(info, config); // will aot - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.AOT, ExpectRelinkDirWhenPublishing: true)); + BlazorPublish(info, config, new PublishOptions(UseCache: false, AOT: true)); // build again - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack)); + BlazorBuild(info, config, new BuildOptions(UseCache: false)); + } + + [Fact] + public void BugRegression_60479_WithRazorClassLib() + { + Configuration config = Configuration.Release; + string razorClassLibraryName = "RazorClassLibrary"; + string extraItems = @$" + + "; + ProjectInfo info = CopyTestAsset(config, aot: true, TestAsset.BlazorBasicTestApp, "blz_razor_lib_top", extraItems: extraItems); + + // No relinking, no AOT + BlazorBuild(info, config); + + // will relink + BlazorPublish(info, config, new PublishOptions(UseCache: false)); + + // publish/wwwroot/_framework/blazor.boot.json + string frameworkDir = GetBlazorBinFrameworkDir(config, forPublish: true); + string bootJson = Path.Combine(frameworkDir, "blazor.boot.json"); + + Assert.True(File.Exists(bootJson), $"Could not find {bootJson}"); + var jdoc = JsonDocument.Parse(File.ReadAllText(bootJson)); + if (!jdoc.RootElement.TryGetProperty("resources", out JsonElement resValue) || + !resValue.TryGetProperty("lazyAssembly", out JsonElement lazyVal)) + { + throw new XunitException($"Could not find resources.lazyAssembly object in {bootJson}"); + } + + Assert.True(lazyVal.EnumerateObject().Select(jp => jp.Name).FirstOrDefault(f => f.StartsWith(razorClassLibraryName)) != null); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests2.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests2.cs deleted file mode 100644 index 00a47837523ce4..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests2.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.IO; -using Xunit; -using Xunit.Abstractions; - -#nullable enable - -namespace Wasm.Build.Tests.Blazor; - -public class MiscTests2 : BlazorWasmTestBase -{ - public MiscTests2(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) - { - } - - [Theory, TestCategory("no-workload")] - [InlineData("Debug")] - [InlineData("Release")] - public void NativeRef_EmitsWarningBecauseItRequiresWorkload(string config) - { - CommandResult res = PublishForRequiresWorkloadTest(config, extraItems: ""); - res.EnsureSuccessful(); - Assert.Matches("warning : .*but the native references won't be linked in", res.Output); - } - - [Theory, TestCategory("no-workload")] - [InlineData("Debug")] - [InlineData("Release")] - public void AOT_FailsBecauseItRequiresWorkload(string config) - { - CommandResult res = PublishForRequiresWorkloadTest(config, extraProperties: "true"); - Assert.NotEqual(0, res.ExitCode); - Assert.Contains("following workloads must be installed: wasm-tools", res.Output); - } - - [Theory, TestCategory("no-workload")] - [InlineData("Debug")] - [InlineData("Release")] - public void AOT_And_NativeRef_FailBecauseTheyRequireWorkload(string config) - { - CommandResult res = PublishForRequiresWorkloadTest(config, - extraProperties: "true", - extraItems: ""); - - Assert.NotEqual(0, res.ExitCode); - Assert.Contains("following workloads must be installed: wasm-tools", res.Output); - } - - private CommandResult PublishForRequiresWorkloadTest(string config, string extraItems="", string extraProperties="") - { - string id = $"needs_workload_{config}_{GetRandomId()}"; - CreateBlazorWasmTemplateProject(id); - - AddItemsPropertiesToProject(Path.Combine(_projectDir!, $"{id}.csproj"), - extraProperties: extraProperties, - extraItems: extraItems); - - string publishLogPath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}.binlog"); - using DotNetCommand cmd = new DotNetCommand(s_buildEnv, _testOutput); - return cmd.WithWorkingDirectory(_projectDir!) - .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) - .ExecuteWithCapturedOutput("publish", - $"-bl:{publishLogPath}", - $"-p:Configuration={config}"); - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests3.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests3.cs deleted file mode 100644 index cdd0143cd2f084..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests3.cs +++ /dev/null @@ -1,167 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.IO; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; -using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; -using Microsoft.Playwright; - -#nullable enable - -namespace Wasm.Build.Tests.Blazor; - -public class MiscTests3 : BlazorWasmTestBase -{ - public MiscTests3(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) - { - _enablePerTestCleanup = true; - } - - [Theory] - [InlineData("Debug", /*build*/true, /*publish*/false)] - [InlineData("Debug", /*build*/false, /*publish*/true)] - [InlineData("Debug", /*build*/true, /*publish*/true)] - [InlineData("Release", /*build*/true, /*publish*/false)] - [InlineData("Release", /*build*/false, /*publish*/true)] - [InlineData("Release", /*build*/true, /*publish*/true)] - public async Task WithDllImportInMainAssembly(string config, bool build, bool publish) - { - // Based on https://github.com/dotnet/runtime/issues/59255 - string id = $"blz_dllimp_{config}_{s_unicodeChars}"; - if (build && publish) - id += "build_then_publish"; - else if (build) - id += "build"; - else - id += "publish"; - - string projectFile = CreateProjectWithNativeReference(id); - string nativeSource = @" - #include - - extern ""C"" { - int cpp_add(int a, int b) { - return a + b; - } - }"; - - File.WriteAllText(Path.Combine(_projectDir!, "mylib.cpp"), nativeSource); - - string myDllImportCs = @$" - using System.Runtime.InteropServices; - namespace {id}; - - public static class MyDllImports - {{ - [DllImport(""mylib"")] - public static extern int cpp_add(int a, int b); - }}"; - - File.WriteAllText(Path.Combine(_projectDir!, "Pages", "MyDllImport.cs"), myDllImportCs); - - AddItemsPropertiesToProject(projectFile, extraItems: @""); - BlazorAddRazorButton("cpp_add", """ - var result = MyDllImports.cpp_add(10, 12); - outputText = $"{result}"; - """); - - if (build) - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked)); - - if (publish) - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.Relinked, ExpectRelinkDirWhenPublishing: build)); - - BlazorRunOptions runOptions = new() { Config = config, Test = TestDllImport }; - if (publish) - await BlazorRunForPublishWithWebServer(runOptions); - else - await BlazorRunForBuildWithDotnetRun(runOptions); - - async Task TestDllImport(IPage page) - { - await page.Locator("text=\"cpp_add\"").ClickAsync(); - var txt = await page.Locator("p[role='test']").InnerHTMLAsync(); - Assert.Equal("Output: 22", txt); - } - } - - [Fact] - public void BugRegression_60479_WithRazorClassLib() - { - string id = $"blz_razor_lib_top_{GetRandomId()}"; - InitBlazorWasmProjectDir(id); - - string wasmProjectDir = Path.Combine(_projectDir!, "wasm"); - string wasmProjectFile = Path.Combine(wasmProjectDir, "wasm.csproj"); - Directory.CreateDirectory(wasmProjectDir); - using DotNetCommand cmd = new DotNetCommand(s_buildEnv, _testOutput, useDefaultArgs: false); - cmd.WithWorkingDirectory(wasmProjectDir) - .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) - .ExecuteWithCapturedOutput("new blazorwasm") - .EnsureSuccessful(); - - string razorProjectDir = Path.Combine(_projectDir!, "RazorClassLibrary"); - Directory.CreateDirectory(razorProjectDir); - cmd.WithWorkingDirectory(razorProjectDir) - .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) - .ExecuteWithCapturedOutput("new razorclasslib") - .EnsureSuccessful(); - - string razorClassLibraryFileNameWithoutExtension = "RazorClassLibrary"; - AddItemsPropertiesToProject(wasmProjectFile, extraItems: @$" - - - "); - - _projectDir = wasmProjectDir; - string config = "Release"; - // No relinking, no AOT - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack)); - - // will relink - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.Relinked, ExpectRelinkDirWhenPublishing: true)); - - // publish/wwwroot/_framework/blazor.boot.json - string frameworkDir = FindBlazorBinFrameworkDir(config, forPublish: true); - string bootJson = Path.Combine(frameworkDir, "blazor.boot.json"); - - Assert.True(File.Exists(bootJson), $"Could not find {bootJson}"); - var jdoc = JsonDocument.Parse(File.ReadAllText(bootJson)); - if (!jdoc.RootElement.TryGetProperty("resources", out JsonElement resValue) || - !resValue.TryGetProperty("lazyAssembly", out JsonElement lazyVal)) - { - throw new XunitException($"Could not find resources.lazyAssembly object in {bootJson}"); - } - - Assert.True(lazyVal.EnumerateObject().Select(jp => jp.Name).FirstOrDefault(f => f.StartsWith(razorClassLibraryFileNameWithoutExtension)) != null); - } - - private void BlazorAddRazorButton(string buttonText, string customCode, string methodName = "test", string razorPage = "Pages/Counter.razor") - { - string additionalCode = $$""" -

Output: @outputText

- - - @code { - private string outputText = string.Empty; - public void {{methodName}}() - { - {{customCode}} - } - } - """; - - // find blazor's Counter.razor - string counterRazorPath = Path.Combine(_projectDir!, razorPage); - if (!File.Exists(counterRazorPath)) - throw new FileNotFoundException($"Could not find {counterRazorPath}"); - - string oldContent = File.ReadAllText(counterRazorPath); - File.WriteAllText(counterRazorPath, oldContent + additionalCode); - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/NativeRefTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/NativeRefTests.cs index fac71883e3f2ae..180c1e249c52bf 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/NativeRefTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/NativeRefTests.cs @@ -18,60 +18,48 @@ public NativeTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buil } [Theory] - [InlineData("Debug")] - [InlineData("Release")] + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] [ActiveIssue("https://github.com/dotnet/runtime/issues/82725")] - public void WithNativeReference_AOTInProjectFile(string config) + public void WithNativeReference_AOTInProjectFile(Configuration config) { - string id = $"blz_nativeref_aot_{config}_{GetRandomId()}"; - string projectFile = CreateProjectWithNativeReference(id); - string extraProperties = config == "Debug" + string extraProperties = config == Configuration.Debug ? ("-O1" + - "-O1") - : string.Empty; - AddItemsPropertiesToProject(projectFile, extraProperties: "true" + extraProperties); - - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked)); - - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.AOT, ExpectRelinkDirWhenPublishing: true)); - + "-O1" + + "true") + : "true"; + ProjectInfo info = CreateProjectWithNativeReference(config, aot: true, extraProperties: extraProperties); + BlazorBuild(info, config, isNativeBuild: true); + BlazorPublish(info, config, new PublishOptions(UseCache: false), isNativeBuild: true); // will relink - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked)); + BlazorBuild(info, config, new BuildOptions(UseCache: false), isNativeBuild: true); } [Theory] - [InlineData("Debug")] - [InlineData("Release")] + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] [ActiveIssue("https://github.com/dotnet/runtime/issues/82725")] - public void WithNativeReference_AOTOnCommandLine(string config) + public void WithNativeReference_AOTOnCommandLine(Configuration config) { - string id = $"blz_nativeref_aot_{config}_{GetRandomId()}"; - string projectFile = CreateProjectWithNativeReference(id); - string extraProperties = config == "Debug" + string extraProperties = config == Configuration.Debug ? ("-O1" + "-O1") : string.Empty; - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); - - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked)); - - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.AOT, ExpectRelinkDirWhenPublishing: true), "-p:RunAOTCompilation=true"); - + ProjectInfo info = CreateProjectWithNativeReference(config, aot: false, extraProperties: extraProperties); + BlazorBuild(info, config, isNativeBuild: true); + BlazorPublish(info, config, new PublishOptions(AOT: true), isNativeBuild: true); // no aot! - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.Relinked, ExpectRelinkDirWhenPublishing: true)); + BlazorPublish(info, config, isNativeBuild: true); } [Theory] - [InlineData("Release")] - public void BlazorWasm_CannotAOT_WithNoTrimming(string config) + [InlineData(Configuration.Release)] + public void BlazorWasm_CannotAOT_WithNoTrimming(Configuration config) { - string id = $"blazorwasm_{config}_aot_{GetRandomId()}"; - CreateBlazorWasmTemplateProject(id); - AddItemsPropertiesToProject(Path.Combine(_projectDir!, $"{id}.csproj"), - extraItems: null, - extraProperties: "falsetrue"); + string extraProperties = "falsetrue"; + ProjectInfo info = CopyTestAsset(config, aot: true, TestAsset.BlazorBasicTestApp, "blazorwasm_aot", extraProperties: extraProperties); - (CommandResult res, _) = BlazorPublish(new BlazorBuildOptions(id, config, ExpectSuccess: false)); - Assert.Contains("AOT is not supported without IL trimming", res.Output); + (string _, string output) = BlazorPublish(info, config, new PublishOptions(ExpectSuccess: false)); + Assert.Contains("AOT is not supported without IL trimming", output); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/NoopNativeRebuildTest.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/NoopNativeRebuildTest.cs index 942d88643df473..526a4737a29beb 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/NoopNativeRebuildTest.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/NoopNativeRebuildTest.cs @@ -19,58 +19,54 @@ public NoopNativeRebuildTest(ITestOutputHelper output, SharedBuildPerTestClassFi } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public void BlazorNoopRebuild(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void BlazorNoopRebuild(Configuration config) { - string id = $"blz_rebuild_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - AddItemsPropertiesToProject(projectFile, extraProperties: "true"); - - string objDir = Path.Combine(_projectDir!, "obj", config, DefaultTargetFrameworkForBlazor, "wasm"); - - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked)); - File.Move(Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-build.binlog"), - Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-build-first.binlog")); - + string extraProperties = "true"; + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blz_rebuild", extraProperties: extraProperties); + BlazorBuild(info, config, isNativeBuild: true); + string projectDir = Path.GetFileName(Path.GetDirectoryName(Path.GetDirectoryName(info.ProjectFilePath)))!; + File.Move(Path.Combine(s_buildEnv.LogRootPath, projectDir, $"{info.ProjectName}-build.binlog"), + Path.Combine(s_buildEnv.LogRootPath, projectDir, $"{info.ProjectName}-build-first.binlog")); + + string objDir = Path.Combine(_projectDir, "obj", config.ToString(), DefaultTargetFrameworkForBlazor, "wasm"); var pathsDict = _provider.GetFilesTable(true, objDir); pathsDict.Remove("runtime-icall-table.h"); - var originalStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + var originalStat = _provider.StatFiles(pathsDict); // build again - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked)); - var newStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + BlazorBuild(info, config, new BuildOptions(UseCache: false), isNativeBuild: true); + var newStat = _provider.StatFiles(pathsDict); - _provider.CompareStat(originalStat, newStat, pathsDict.Values); + _provider.CompareStat(originalStat, newStat, pathsDict); } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public void BlazorOnlyLinkRebuild(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void BlazorOnlyLinkRebuild(Configuration config) { - string id = $"blz_relink_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - AddItemsPropertiesToProject(projectFile, extraProperties: "true"); - - string objDir = Path.Combine(_projectDir!, "obj", config, DefaultTargetFrameworkForBlazor, "wasm"); - - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked), "-p:EmccLinkOptimizationFlag=-O2"); - File.Move(Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-build.binlog"), - Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-build-first.binlog")); - + string extraProperties = "true"; + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blz_relink", extraProperties: extraProperties); + var buildOptions = new BuildOptions(ExtraMSBuildArgs: "-p:EmccLinkOptimizationFlag=-O2"); + BlazorBuild(info, config, buildOptions, isNativeBuild: true); + string projectDir = Path.GetFileName(Path.GetDirectoryName(Path.GetDirectoryName(info.ProjectFilePath)))!; + File.Move(Path.Combine(s_buildEnv.LogRootPath, projectDir, $"{info.ProjectName}-build.binlog"), + Path.Combine(s_buildEnv.LogRootPath, projectDir, $"{info.ProjectName}-build-first.binlog")); + + string objDir = Path.Combine(_projectDir, "obj", config.ToString(), DefaultTargetFrameworkForBlazor, "wasm"); var pathsDict = _provider.GetFilesTable(true, objDir); pathsDict.Remove("runtime-icall-table.h"); pathsDict.UpdateTo(unchanged: false, "dotnet.native.wasm", "dotnet.native.js", "emcc-link.rsp"); - - var originalStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + var originalStat = _provider.StatFiles(pathsDict); // build again - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked), "-p:EmccLinkOptimizationFlag=-O1"); - var newStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + BlazorBuild(info, config, new BuildOptions(ExtraMSBuildArgs: "-p:EmccLinkOptimizationFlag=-O1", UseCache: false), isNativeBuild: true); + var newStat = _provider.StatFiles(pathsDict); - _provider.CompareStat(originalStat, newStat, pathsDict.Values); + _provider.CompareStat(originalStat, newStat, pathsDict); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/SignalRClientTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/SignalRClientTests.cs index cf4f938bc1885d..f48472f9b96467 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/SignalRClientTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/SignalRClientTests.cs @@ -20,11 +20,11 @@ public SignalRClientTests(ITestOutputHelper output, SharedBuildPerTestClassFixtu [ConditionalTheory(typeof(BuildTestBase), nameof(IsWorkloadWithMultiThreadingForDefaultFramework))] [ActiveIssue("https://github.com/dotnet/runtime/issues/100445")] // to be fixed by: "https://github.com/dotnet/aspnetcore/issues/54365" - [InlineData("Debug", "LongPolling")] - [InlineData("Release", "LongPolling")] - [InlineData("Debug", "WebSockets")] - [InlineData("Release", "WebSockets")] - public async Task SignalRPassMessageBlazor(string config, string transport) => + [InlineData(Configuration.Debug, "LongPolling")] + [InlineData(Configuration.Release, "LongPolling")] + [InlineData(Configuration.Debug, "WebSockets")] + [InlineData(Configuration.Release, "WebSockets")] + public async Task SignalRPassMessageBlazor(Configuration config, string transport) => await SignalRPassMessage("blazorclient", config, transport); } diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/SimpleMultiThreadedTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/SimpleMultiThreadedTests.cs index 556d40d42a40ec..ce948e9a4a2dc7 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/SimpleMultiThreadedTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/SimpleMultiThreadedTests.cs @@ -21,38 +21,37 @@ public SimpleMultiThreadedTests(ITestOutputHelper output, SharedBuildPerTestClas } // dotnet-run needed for running with *build* so wwwroot has the index.html etc - // [Theory] - // [InlineData("Debug")] - // [InlineData("Release")] - // public async Task BlazorBuildRunTest(string config) - // { - // string id = $"blazor_mt_{config}_{GetRandomId()}"; - // string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); - - // AddItemsPropertiesToProject(projectFile, "true"); - // BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack, RuntimeType: RuntimeType.MultiThreaded)); - // // await BlazorRunForBuildWithDotnetRun(config); - - // await BlazorRunTest($"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files --web-server-use-cors --web-server-use-cop --web-server-use-https --timeout=15:00:00", - // Path.GetFullPath(Path.Combine(FindBlazorBinFrameworkDir(config, forPublish: false), ".."))); - // } + [Theory] + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/100373")] // to be fixed by: "https://github.com/dotnet/aspnetcore/issues/54365" + public async Task BlazorBuildRunTest(Configuration config) + { + string extraProperties = "true"; + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blazorwasm", extraProperties: extraProperties); + bool isPublish = false; + string frameworkDir = GetBlazorBinFrameworkDir(config, isPublish); + BuildProject(info, config, new BuildOptions(RuntimeType: RuntimeVariant.MultiThreaded)); + // we wan to use "xharness wasm webserver" but from non-publish location + string extraArgs = " --web-server-use-cors --web-server-use-cop --web-server-use-https --timeout=15:00:00"; + await RunForPublishWithWebServer(new BlazorRunOptions(config, ExtraArgs: extraArgs, CustomBundleDir: Path.Combine(frameworkDir, ".."))); + } [ConditionalTheory(typeof(BuildTestBase), nameof(IsWorkloadWithMultiThreadingForDefaultFramework))] [ActiveIssue("https://github.com/dotnet/runtime/issues/100373")] // to be fixed by: "https://github.com/dotnet/aspnetcore/issues/54365" - // [InlineData("Debug", false)] // ActiveIssue https://github.com/dotnet/runtime/issues/98758 - // [InlineData("Debug", true)] - [InlineData("Release", false)] - // [InlineData("Release", true)] - public async Task BlazorPublishRunTest(string config, bool aot) + // [InlineData(Configuration.Debug, false)] // ActiveIssue https://github.com/dotnet/runtime/issues/98758 + // [InlineData(Configuration.Debug, true)] + [InlineData(Configuration.Release, false)] + // [InlineData(Configuration.Release, true)] + public async Task BlazorPublishRunTest(Configuration config, bool aot) { - string id = $"blazor_mt_{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); - AddItemsPropertiesToProject(projectFile, "true"); + string extraProperties = "true"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.BlazorBasicTestApp, "blazor_mt", extraProperties: extraProperties); // if (aot) // AddItemsPropertiesToProject(projectFile, "true"); File.WriteAllText( - Path.Combine(Path.GetDirectoryName(projectFile)!, "wwwroot", id + ".lib.module.js"), + Path.Combine(Path.GetDirectoryName(info.ProjectFilePath)!, "wwwroot", info.ProjectName + ".lib.module.js"), """ export function onRuntimeReady({ runtimeBuildInfo }) { console.log('Runtime is ready: ' + JSON.stringify(runtimeBuildInfo)); @@ -61,26 +60,21 @@ export function onRuntimeReady({ runtimeBuildInfo }) { """ ); - BlazorPublish(new BlazorBuildOptions( - id, - config, - aot ? NativeFilesType.AOT - : (config == "Release" ? NativeFilesType.Relinked : NativeFilesType.FromRuntimePack), - RuntimeType: RuntimeVariant.MultiThreaded)); + BuildProject(info, config, new PublishOptions(RuntimeType: RuntimeVariant.MultiThreaded, AOT: aot)); bool hasEmittedWasmEnableThreads = false; StringBuilder errorOutput = new(); - await BlazorRunForPublishWithWebServer( + await RunForPublishWithWebServer( runOptions: new BlazorRunOptions( - Config: config, + Configuration: config, ExtraArgs: "--web-server-use-cors --web-server-use-cop", - OnConsoleMessage: (_, message) => + OnConsoleMessage: (type, message) => { - if (message.Text.Contains("WasmEnableThreads=true")) + if (message.Contains("WasmEnableThreads=true")) hasEmittedWasmEnableThreads = true; - if (message.Type == "error") - errorOutput.AppendLine(message.Text); + if (type == "error") + errorOutput.AppendLine(message); }, OnErrorMessage: (message) => { diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/SimpleRunTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/SimpleRunTests.cs index 433d77699c1c76..e623c6d4efc7e3 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/SimpleRunTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/SimpleRunTests.cs @@ -23,75 +23,51 @@ public SimpleRunTests(ITestOutputHelper output, SharedBuildPerTestClassFixture b } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public async Task BlazorBuildRunTest(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public async Task BlazorBuildRunTest(Configuration config) { - string id = $"blazor_{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); - - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack)); - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config }); + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blazor"); + BlazorBuild(info, config); + await RunForBuildWithDotnetRun(new BlazorRunOptions(config)); } [Theory] - [InlineData("Debug", /*appendRID*/ true, /*useArtifacts*/ false)] - [InlineData("Debug", /*appendRID*/ true, /*useArtifacts*/ true)] - [InlineData("Debug", /*appendRID*/ false, /*useArtifacts*/ true)] - [InlineData("Debug", /*appendRID*/ false, /*useArtifacts*/ false)] - public async Task BlazorBuildAndRunForDifferentOutputPaths(string config, bool appendRID, bool useArtifacts) + [InlineData(Configuration.Debug, /*appendRID*/ true, /*useArtifacts*/ false)] + [InlineData(Configuration.Debug, /*appendRID*/ true, /*useArtifacts*/ true)] + [InlineData(Configuration.Debug, /*appendRID*/ false, /*useArtifacts*/ true)] + [InlineData(Configuration.Debug, /*appendRID*/ false, /*useArtifacts*/ false)] + public async Task BlazorBuildAndRunForDifferentOutputPaths(Configuration config, bool appendRID, bool useArtifacts) { - string id = $"{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); - string projectName = Path.GetFileNameWithoutExtension(projectFile); - + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blazor"); string extraPropertiesForDBP = ""; if (appendRID) extraPropertiesForDBP += "true"; if (useArtifacts) extraPropertiesForDBP += "true."; - - string projectDirectory = Path.GetDirectoryName(projectFile)!; + string projectDir = Path.GetDirectoryName(info.ProjectFilePath) ?? ""; + string rootDir = Path.GetDirectoryName(projectDir) ?? ""; if (!string.IsNullOrEmpty(extraPropertiesForDBP)) - AddItemsPropertiesToProject(Path.Combine(projectDirectory, "Directory.Build.props"), + AddItemsPropertiesToProject(Path.Combine(rootDir, "Directory.Build.props"), extraPropertiesForDBP); - var buildArgs = new BuildArgs(projectName, config, false, id, null); - buildArgs = ExpandBuildArgs(buildArgs); - - BlazorBuildOptions buildOptions = new(id, config, NativeFilesType.FromRuntimePack); - if (useArtifacts) - { - buildOptions = buildOptions with - { - BinFrameworkDir = Path.Combine(projectDirectory, - "bin", - id, - config.ToLower(), - "wwwroot", - "_framework") - }; - } - BlazorBuild(buildOptions); - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config }); + bool isPublish = false; + string frameworkDir = useArtifacts ? + Path.Combine( + projectDir, "bin", info.ProjectName, config.ToString().ToLower(), "wwwroot", "_framework") : + GetBinFrameworkDir(config, isPublish); + BuildProject(info, config, new BuildOptions(NonDefaultFrameworkDir: frameworkDir)); + await RunForBuildWithDotnetRun(new BlazorRunOptions(config)); } [Theory] - [InlineData("Debug", false)] - [InlineData("Release", false)] - [InlineData("Release", true)] - public async Task BlazorPublishRunTest(string config, bool aot) + [InlineData(Configuration.Debug, false)] + [InlineData(Configuration.Release, false)] + [InlineData(Configuration.Release, true)] + public async Task BlazorPublishRunTest(Configuration config, bool aot) { - string id = $"blazor_{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); - if (aot) - AddItemsPropertiesToProject(projectFile, "true"); - - BlazorPublish(new BlazorBuildOptions( - id, - config, - aot ? NativeFilesType.AOT - : (config == "Release" ? NativeFilesType.Relinked : NativeFilesType.FromRuntimePack))); - await BlazorRunForPublishWithWebServer(new BlazorRunOptions() { Config = config }); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.BlazorBasicTestApp, "blazor_publish"); + BlazorPublish(info, config); + await RunForPublishWithWebServer(new BlazorRunOptions(config)); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs index a735e2af15e444..9dbf10ac2a365e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs @@ -34,14 +34,14 @@ public WorkloadRequiredTests(ITestOutputHelper output, SharedBuildPerTestClassFi { } - public static TheoryData SettingDifferentFromValuesInRuntimePack() + public static TheoryData SettingDifferentFromValuesInRuntimePack() { - TheoryData data = new(); + TheoryData data = new(); - string[] configs = new[] { "Debug", "Release" }; + var configs = new[] { Configuration.Debug, Configuration.Release }; foreach (var defaultPair in PropertiesWithTriggerValues) { - foreach (string config in configs) + foreach (Configuration config in configs) { data.Add(config, $"<{defaultPair.propertyName}>{defaultPair.triggerValue}", true); data.Add(config, $"<{defaultPair.propertyName}>{!defaultPair.triggerValue}", false); @@ -53,18 +53,18 @@ public static TheoryData SettingDifferentFromValuesInRunti [Theory, TestCategory("no-workload")] [MemberData(nameof(SettingDifferentFromValuesInRuntimePack))] - public void WorkloadRequiredForBuild(string config, string extraProperties, bool workloadNeeded) + public void WorkloadRequiredForBuild(Configuration config, string extraProperties, bool workloadNeeded) => CheckWorkloadRequired(config, extraProperties, workloadNeeded, publish: false); [Theory, TestCategory("no-workload")] [MemberData(nameof(SettingDifferentFromValuesInRuntimePack))] - public void WorkloadRequiredForPublish(string config, string extraProperties, bool workloadNeeded) + public void WorkloadRequiredForPublish(Configuration config, string extraProperties, bool workloadNeeded) => CheckWorkloadRequired(config, extraProperties, workloadNeeded, publish: true); - public static TheoryData InvariantGlobalizationTestData(bool publish) + public static TheoryData InvariantGlobalizationTestData(bool publish) { - TheoryData data = new(); - foreach (string config in new[] { "Debug", "Release" }) + TheoryData data = new(); + foreach (Configuration config in new[] { Configuration.Debug, Configuration.Release }) { data.Add(config, /*invariant*/ true, /*publish*/ publish); data.Add(config, /*invariant*/ false, /*publish*/ publish); @@ -73,129 +73,156 @@ public static TheoryData InvariantGlobalizationTestData(bool } [Theory, TestCategory("no-workload")] - [MemberData(nameof(InvariantGlobalizationTestData), parameters: /*publish*/ false)] - [MemberData(nameof(InvariantGlobalizationTestData), parameters: /*publish*/ true)] - public async Task WorkloadNotRequiredForInvariantGlobalization(string config, bool invariant, bool publish) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public async Task DefaultTemplate_WithoutWorkload(Configuration config) { - string id = $"props_req_workload_{(publish ? "publish" : "build")}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blz_no_workload"); + BlazorBuild(info, config); + await RunForBuildWithDotnetRun(new BlazorRunOptions(config)); - if (invariant) - AddItemsPropertiesToProject(projectFile, extraProperties: "true"); + BlazorPublish(info, config, new PublishOptions(UseCache: false)); + await RunForPublishWithWebServer(new BlazorRunOptions(config)); + } - string counterPath = Path.Combine(Path.GetDirectoryName(projectFile)!, "Pages", "Counter.razor"); - string allText = File.ReadAllText(counterPath); - string ccText = "currentCount++;"; - if (allText.IndexOf(ccText) < 0) - throw new Exception("Counter.razor does not have the expected content. Test needs to be updated."); + [Theory, TestCategory("no-workload")] + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void NativeRef_EmitsWarningBecauseItRequiresWorkload(Configuration config) + { + CommandResult res = PublishForRequiresWorkloadTest(config, extraItems: ""); + res.EnsureSuccessful(); + Assert.Matches("warning : .*but the native references won't be linked in", res.Output); + } - allText = allText.Replace(ccText, $"{ccText}{Environment.NewLine}TestInvariantCulture();"); - allText += s_invariantCultureMethodForBlazor; - File.WriteAllText(counterPath, allText); - _testOutput.WriteLine($"Updated counter.razor: {allText}"); + [Theory, TestCategory("no-workload")] + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void AOT_FailsBecauseItRequiresWorkload(Configuration config) + { + CommandResult res = PublishForRequiresWorkloadTest(config, extraProperties: "true"); + Assert.NotEqual(0, res.ExitCode); + Assert.Contains("following workloads must be installed: wasm-tools", res.Output); + } - CommandResult result; - GlobalizationMode mode = invariant ? GlobalizationMode.Invariant : GlobalizationMode.Sharded; - if (publish) - { - (result, _) = BlazorPublish( - new BlazorBuildOptions( - id, - config, - ExpectSuccess: true, - GlobalizationMode: mode)); - } - else - { - (result, _) = BlazorBuild( - new BlazorBuildOptions( - id, - config, - ExpectSuccess: true, - GlobalizationMode: mode)); - } + [Theory, TestCategory("no-workload")] + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void AOT_And_NativeRef_FailBecauseTheyRequireWorkload(Configuration config) + { + CommandResult res = PublishForRequiresWorkloadTest(config, + extraProperties: "true", + extraItems: ""); - StringBuilder sbOutput = new(); - await BlazorRunTest(new BlazorRunOptions() + Assert.NotEqual(0, res.ExitCode); + Assert.Contains("following workloads must be installed: wasm-tools", res.Output); + } + + + [Theory, TestCategory("no-workload")] + [MemberData(nameof(InvariantGlobalizationTestData), parameters: /*publish*/ false)] + [MemberData(nameof(InvariantGlobalizationTestData), parameters: /*publish*/ true)] + public async Task WorkloadNotRequiredForInvariantGlobalization(Configuration config, bool invariant, bool publish) + { + string prefix = $"props_req_workload_{(publish ? "publish" : "build")}"; + string extraProperties = invariant ? $"true" : ""; + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, prefix, extraProperties: extraProperties); + string ccText = "currentCount++;"; + // UpdateFile throws if code that is to be replaced does not exist + UpdateFile(Path.Combine("Pages", "Counter.razor"), new Dictionary { - Config = config, - Host = publish ? BlazorRunHost.WebServer : BlazorRunHost.DotnetRun, - OnConsoleMessage = (_, msg) => - { - sbOutput.AppendLine(msg.Text); - } + { ccText, $"{ccText}\nTestInvariantCulture();" }, + { "private int currentCount = 0;", $"{s_invariantCultureMethodForBlazor}" } }); + string counterPath = Path.Combine(_projectDir, "Pages", "Counter.razor"); + string allText = File.ReadAllText(counterPath); + _testOutput.WriteLine($"Updated counter.razor: {allText}"); + + var globalizationMode = invariant ? GlobalizationMode.Invariant : GlobalizationMode.Sharded; + _ = publish ? + PublishProject(info, config, new PublishOptions(GlobalizationMode: globalizationMode)) : + BuildProject(info, config, new BuildOptions(GlobalizationMode: globalizationMode)); + + BlazorRunOptions runOptions = new(config); + RunResult result = publish ? await RunForPublishWithWebServer(runOptions) : await RunForBuildWithDotnetRun(runOptions); - string output = sbOutput.ToString(); if (invariant) { - Assert.Contains("Could not create es-ES culture", output); + Assert.Contains(result.TestOutput, m => m.Contains("Could not create es-ES culture")); // For invariant, we get: // Could not create es-ES culture: Argument_CultureNotSupportedInInvariantMode Arg_ParamName_Name, name // Argument_CultureInvalidIdentifier, es-ES // .. which is expected. // // Assert.Contains("es-ES is an invalid culture identifier.", output); - Assert.Contains("CurrentCulture.NativeName: Invariant Language (Invariant Country)", output); - Assert.DoesNotContain($"es-ES: Is-LCID-InvariantCulture:", output); + Assert.Contains(result.TestOutput, m => m.Contains("CurrentCulture.NativeName: Invariant Language (Invariant Country)")); + Assert.All(result.TestOutput, m => Assert.DoesNotContain("es-ES: Is-LCID-InvariantCulture", m)); } else { - Assert.DoesNotContain("Could not create es-ES culture", output); - Assert.DoesNotContain("invalid culture", output); - Assert.DoesNotContain("CurrentCulture.NativeName: Invariant Language (Invariant Country)", output); - Assert.Contains("es-ES: Is-LCID-InvariantCulture: False", output); - Assert.Contains("NativeName: espa\u00F1ol (Espa\u00F1a)", output); - + Assert.All(result.TestOutput, m => Assert.DoesNotContain("Could not create es-ES culture", m)); + Assert.All(result.TestOutput, m => Assert.DoesNotContain("invalid culture", m)); + Assert.All(result.TestOutput, m => Assert.DoesNotContain("CurrentCulture.NativeName: Invariant Language (Invariant Country)", m)); + Assert.Contains(result.TestOutput, m => m.Contains("es-ES: Is-LCID-InvariantCulture: False")); + Assert.Contains(result.TestOutput, m => m.Contains("NativeName: espa\u00F1ol (Espa\u00F1a)")); // ignoring the last line of the output which prints the current culture } } - private void CheckWorkloadRequired(string config, string extraProperties, bool workloadNeeded, bool publish) + private CommandResult PublishForRequiresWorkloadTest(Configuration config, string extraItems="", string extraProperties="") { - string id = $"props_req_workload_{(publish ? "publish" : "build")}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); - AddItemsPropertiesToProject(projectFile, extraProperties, - atTheEnd: @" - - "); + ProjectInfo info = CopyTestAsset( + config, aot: false, TestAsset.BlazorBasicTestApp, "needs_workload", extraProperties: extraProperties, extraItems: extraItems); + + string publishLogPath = Path.Combine(s_buildEnv.LogRootPath, info.ProjectName, $"{info.ProjectName}.binlog"); + using DotNetCommand cmd = new DotNetCommand(s_buildEnv, _testOutput); + return cmd.WithWorkingDirectory(_projectDir) + .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) + .ExecuteWithCapturedOutput("publish", + $"-bl:{publishLogPath}", + $"-p:Configuration={config}"); + } - CommandResult result; - if (publish) - (result, _) = BlazorPublish(new BlazorBuildOptions(id, config, ExpectSuccess: false)); - else - (result, _) = BlazorBuild(new BlazorBuildOptions(id, config, ExpectSuccess: false)); + private void CheckWorkloadRequired(Configuration config, string extraProperties, bool workloadNeeded, bool publish) + { + string prefix = $"props_req_workload_{(publish ? "publish" : "build")}"; + string insertAtEnd = @" + + "; + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, prefix, extraProperties: extraProperties, insertAtEnd: insertAtEnd); + (string _, string output) = publish ? + PublishProject(info, config, new PublishOptions(ExpectSuccess: false)) : + BuildProject(info, config, new BuildOptions(ExpectSuccess: false)); if (workloadNeeded) { - Assert.Contains("following workloads must be installed: wasm-tools", result.Output); - Assert.DoesNotContain("error : Stopping the build", result.Output); + Assert.Contains("following workloads must be installed: wasm-tools", output); + Assert.DoesNotContain("error : Stopping the build", output); } else { - Assert.DoesNotContain("following workloads must be installed: wasm-tools", result.Output); - Assert.Contains("error : Stopping the build", result.Output); + Assert.DoesNotContain("following workloads must be installed: wasm-tools", output); + Assert.Contains("error : Stopping the build", output); } } private static string s_invariantCultureMethodForBlazor = """ - @code { + private int currentCount = 0; public int TestInvariantCulture() { // https://github.com/dotnet/runtime/blob/main/docs/design/features/globalization-invariant-mode.md#cultures-and-culture-data try { System.Globalization.CultureInfo culture = new ("es-ES", false); - System.Console.WriteLine($"es-ES: Is-LCID-InvariantCulture: {culture.LCID == System.Globalization.CultureInfo.InvariantCulture.LCID}, NativeName: {culture.NativeName}"); + System.Console.WriteLine($"TestOutput -> es-ES: Is-LCID-InvariantCulture: {culture.LCID == System.Globalization.CultureInfo.InvariantCulture.LCID}, NativeName: {culture.NativeName}"); } catch (System.Globalization.CultureNotFoundException cnfe) { - System.Console.WriteLine($"Could not create es-ES culture: {cnfe.Message}"); + System.Console.WriteLine($"TestOutput -> Could not create es-ES culture: {cnfe.Message}"); } - System.Console.WriteLine($"CurrentCulture.NativeName: {System.Globalization.CultureInfo.CurrentCulture.NativeName}"); + System.Console.WriteLine($"TestOutput -> CurrentCulture.NativeName: {System.Globalization.CultureInfo.CurrentCulture.NativeName}"); return 42; } - } """; } diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs index 0b6b7d3dda4130..be2e2dd13a29be 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs @@ -113,12 +113,12 @@ public async Task SpawnBrowserAsync( bool headless = true, int? timeout = null, int maxRetries = 3, - string language = "en-US" + string locale = "en-US" ) { var url = new Uri(browserUrl); Playwright = await Microsoft.Playwright.Playwright.CreateAsync(); // codespaces: ignore certificate error -> Microsoft.Playwright.PlaywrightException : net::ERR_CERT_AUTHORITY_INVALID - string[] chromeArgs = new[] { $"--explicitly-allowed-ports={url.Port}", "--ignore-certificate-errors", $"--lang={language}" }; + string[] chromeArgs = new[] { $"--explicitly-allowed-ports={url.Port}", "--ignore-certificate-errors", $"--lang={locale}" }; _testOutput.WriteLine($"Launching chrome ('{s_chromePath.Value}') via playwright with args = {string.Join(',', chromeArgs)}"); int attempt = 0; @@ -155,15 +155,15 @@ public async Task RunAsync( ToolCommand cmd, string args, bool headless = true, - string language = "en-US", - Action? onConsoleMessage = null, + string locale = "en-US", + Action? onConsoleMessage = null, Action? onServerMessage = null, Action? onError = null, Func? modifyBrowserUrl = null) { var urlString = await StartServerAndGetUrlAsync(cmd, args, onServerMessage); - var browser = await SpawnBrowserAsync(urlString, headless, language: language); - var context = await browser.NewContextAsync(new BrowserNewContextOptions { Locale = language }); + var browser = await SpawnBrowserAsync(urlString, headless, locale: locale); + var context = await browser.NewContextAsync(new BrowserNewContextOptions { Locale = locale }); return await RunAsync(context, urlString, headless, onConsoleMessage, onError, modifyBrowserUrl); } @@ -171,7 +171,7 @@ public async Task RunAsync( IBrowserContext context, string browserUrl, bool headless = true, - Action? onConsoleMessage = null, + Action? onConsoleMessage = null, Action? onError = null, Func? modifyBrowserUrl = null, bool resetExitedState = false @@ -192,26 +192,15 @@ public async Task RunAsync( { message = payloadMatch.Groups["payload"].Value; } - if (message.StartsWith("TestOutput -> ")) - { - lock (OutputLines) - { - OutputLines.Add(message); - } - } Match exitMatch = s_exitRegex.Match(message); if (exitMatch.Success) { - lock (OutputLines) - { - OutputLines.Add(message); - } int exitCode = int.Parse(exitMatch.Groups["exitCode"].Value); _exited.TrySetResult(exitCode); } if (onConsoleMessage is not null) { - onConsoleMessage(page, msg); + onConsoleMessage(msg.Type, message); } }; @@ -227,7 +216,7 @@ public async Task RunAsync( return page; } - public async Task WaitForExitMessageAsync(TimeSpan timeout) + public async Task WaitForExitMessageAsync(TimeSpan timeout) { if (RunTask is null || RunTask.IsCompleted) throw new Exception($"No run task, or already completed"); @@ -235,8 +224,9 @@ public async Task WaitForExitMessageAsync(TimeSpan timeout) await Task.WhenAny(RunTask!, _exited.Task, Task.Delay(timeout)); if (_exited.Task.IsCompleted) { - _testOutput.WriteLine ($"Exited with {await _exited.Task}"); - return; + int code = await _exited.Task; + _testOutput.WriteLine ($"Exited with {code}"); + return code; } throw new Exception($"Timed out after {timeout.TotalSeconds}s waiting for 'WASM EXIT' message"); diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/AssertBundleOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/AssertBundleOptions.cs new file mode 100644 index 00000000000000..c2fd74e11bf6d5 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/AssertBundleOptions.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System.IO; + +namespace Wasm.Build.Tests; + +public record AssertBundleOptions( + Configuration Configuration, + MSBuildOptions BuildOptions, + NativeFilesType ExpectedFileType, + string BinFrameworkDir, + bool ExpectSymbolsFile = true, + bool AssertIcuAssets = true, + bool AssertSymbolsFile = true +); diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BrowserRunOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BrowserRunOptions.cs new file mode 100644 index 00000000000000..98b8e9d0aef421 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BrowserRunOptions.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Threading.Tasks; +using Microsoft.Playwright; + +#nullable enable + +namespace Wasm.Build.Tests; + +public record BrowserRunOptions : RunOptions +{ + public string? TestScenario { get; init; } + + public BrowserRunOptions( + Configuration Configuration, + bool AOT = false, + RunHost Host = RunHost.DotnetRun, + bool DetectRuntimeFailures = true, + Dictionary? ServerEnvironment = null, + NameValueCollection? BrowserQueryString = null, + Action? OnConsoleMessage = null, + Action? OnServerMessage = null, + Action? OnErrorMessage = null, + string ExtraArgs = "", + string BrowserPath = "", + string Locale = "en-US", + int? ExpectedExitCode = 0, + string CustomBundleDir = "", + string? TestScenario = null + ) : base( + Configuration, + AOT, + Host, + DetectRuntimeFailures, + ServerEnvironment, + BrowserQueryString, + OnConsoleMessage, + OnServerMessage, + OnErrorMessage, + ExtraArgs, + BrowserPath, + Locale, + ExpectedExitCode, + CustomBundleDir + ) + { + this.TestScenario = TestScenario; + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildOptions.cs new file mode 100644 index 00000000000000..d573405986a9cd --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildOptions.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +#nullable enable + +namespace Wasm.Build.Tests; + +public record BuildOptions : MSBuildOptions +{ + public BuildOptions( + bool IsPublish = false, + bool AOT = false, + NativeFilesType ExpectedFileType = NativeFilesType.FromRuntimePack, + string TargetFramework = BuildTestBase.DefaultTargetFramework, + GlobalizationMode GlobalizationMode = GlobalizationMode.Sharded, + string CustomIcuFile = "", + bool UseCache = true, + bool ExpectSuccess = true, + bool AssertAppBundle = true, + string Label = "", + bool WarnAsError = true, + RuntimeVariant RuntimeType = RuntimeVariant.SingleThreaded, + IDictionary? ExtraBuildEnvironmentVariables = null, + string BootConfigFileName = "blazor.boot.json", + string NonDefaultFrameworkDir = "", + string ExtraMSBuildArgs = "" + ) : base( + IsPublish, + AOT, + ExpectedFileType, + TargetFramework, + GlobalizationMode, + CustomIcuFile, + UseCache, + ExpectSuccess, + AssertAppBundle, + Label, + WarnAsError, + RuntimeType, + ExtraBuildEnvironmentVariables, + BootConfigFileName, + NonDefaultFrameworkDir, + ExtraMSBuildArgs + ) + { + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildProduct.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildProduct.cs new file mode 100644 index 00000000000000..ba17af588d847a --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildProduct.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +namespace Wasm.Build.Tests; + +public record BuildResult( + string ProjectDir, + string LogFile, + bool Success, + string BuildOutput +); diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/MSBuildOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/MSBuildOptions.cs new file mode 100644 index 00000000000000..24ae96b0745816 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/MSBuildOptions.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +#nullable enable + +namespace Wasm.Build.Tests; + +public abstract record MSBuildOptions +( + bool IsPublish, + bool AOT = false, + NativeFilesType ExpectedFileType = NativeFilesType.FromRuntimePack, + string TargetFramework = BuildTestBase.DefaultTargetFramework, + GlobalizationMode GlobalizationMode = GlobalizationMode.Sharded, + string CustomIcuFile = "", + bool UseCache = true, + bool ExpectSuccess = true, + bool AssertAppBundle = true, + string Label = "", + bool WarnAsError = true, + RuntimeVariant RuntimeType = RuntimeVariant.SingleThreaded, + IDictionary? ExtraBuildEnvironmentVariables = null, + string BootConfigFileName = "blazor.boot.json", + string NonDefaultFrameworkDir = "", + string ExtraMSBuildArgs = "" +); diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/NativeFilesType.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/NativeFilesType.cs new file mode 100644 index 00000000000000..97095049741999 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/NativeFilesType.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +namespace Wasm.Build.Tests; + +public enum NativeFilesType { FromRuntimePack, Relinked, AOT }; + diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/PublishOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/PublishOptions.cs new file mode 100644 index 00000000000000..92516a9b3e7e22 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/PublishOptions.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +#nullable enable + +namespace Wasm.Build.Tests; + +public record PublishOptions : MSBuildOptions +{ + public bool BuildOnlyAfterPublish { get; init; } + public bool ExpectRelinkDirWhenPublishing { get; init; } + + public PublishOptions( + bool IsPublish = true, + bool AOT = false, + NativeFilesType ExpectedFileType = NativeFilesType.FromRuntimePack, + string TargetFramework = BuildTestBase.DefaultTargetFramework, + GlobalizationMode GlobalizationMode = GlobalizationMode.Sharded, + string CustomIcuFile = "", + bool UseCache = true, + bool ExpectSuccess = true, + bool AssertAppBundle = true, + string Label = "", + bool WarnAsError = true, + RuntimeVariant RuntimeType = RuntimeVariant.SingleThreaded, + IDictionary? ExtraBuildEnvironmentVariables = null, + string BootConfigFileName = "blazor.boot.json", + string NonDefaultFrameworkDir = "", + string ExtraMSBuildArgs = "", + bool BuildOnlyAfterPublish = true, + bool ExpectRelinkDirWhenPublishing = false + ) : base( + IsPublish, + AOT, + ExpectedFileType, + TargetFramework, + GlobalizationMode, + CustomIcuFile, + UseCache, + ExpectSuccess, + AssertAppBundle, + Label, + WarnAsError, + RuntimeType, + ExtraBuildEnvironmentVariables, + BootConfigFileName, + NonDefaultFrameworkDir, + ExtraMSBuildArgs + ) + { + this.IsPublish = IsPublish; + this.BuildOnlyAfterPublish = BuildOnlyAfterPublish; + this.ExpectRelinkDirWhenPublishing = ExpectRelinkDirWhenPublishing; + } +} \ No newline at end of file diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/RunOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/RunOptions.cs new file mode 100644 index 00000000000000..df379e86db30cd --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/RunOptions.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Threading.Tasks; +using Microsoft.Playwright; + +#nullable enable + +namespace Wasm.Build.Tests; +public abstract record RunOptions +( + Configuration Configuration, + bool AOT = false, + RunHost Host = RunHost.DotnetRun, + bool DetectRuntimeFailures = true, + + Dictionary? ServerEnvironment = null, + NameValueCollection? BrowserQueryString = null, + Action? OnConsoleMessage = null, + Action? OnServerMessage = null, + Action? OnErrorMessage = null, + string ExtraArgs = "", + string BrowserPath = "", + string Locale = "en-US", + int? ExpectedExitCode = 0, + string CustomBundleDir = "", + Func? ExecuteAfterLoaded = null +); + +public enum RunHost { DotnetRun, WebServer }; diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/SharedBuildPerTestClassFixture.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/SharedBuildPerTestClassFixture.cs new file mode 100644 index 00000000000000..c764ab6e419a89 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/SharedBuildPerTestClassFixture.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; + +#nullable enable + +// ToDo: should be common with Wasi.Build.Tests, copied here after Wasm.Build.Tests refactoring +namespace Wasm.Build.Tests +{ + public class SharedBuildPerTestClassFixture : IDisposable + { + public Dictionary _buildPaths = new(); + + public void CacheBuild(ProjectInfo buildArgs, BuildResult result) + { + if (result == null) + throw new ArgumentNullException(nameof(result)); + if (buildArgs == null) + throw new ArgumentNullException(nameof(buildArgs)); + _buildPaths.Add(buildArgs, result); + } + + public void RemoveFromCache(string buildPath, bool keepDir=true) + { + ProjectInfo? foundBuildArgs = _buildPaths.Where(kvp => kvp.Value.ProjectDir == buildPath).Select(kvp => kvp.Key).SingleOrDefault(); + if (foundBuildArgs is not null) + _buildPaths.Remove(foundBuildArgs); + + if (!keepDir) + RemoveDirectory(buildPath); + } + + public bool TryGetBuildFor(ProjectInfo buildArgs, [NotNullWhen(true)] out BuildResult? product) + => _buildPaths.TryGetValue(buildArgs, out product); + + public void Dispose() + { + Console.WriteLine ($"============== DELETING THE BUILDS ============="); + foreach (var kvp in _buildPaths.Values) + { + RemoveDirectory(kvp.ProjectDir); + } + } + + private void RemoveDirectory(string path) + { + if (EnvironmentVariables.SkipProjectCleanup == "1") + return; + + try + { + Directory.Delete(path, recursive: true); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Failed to delete '{path}' during test cleanup: {ex}"); + throw; + } + } + + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/TestAsset.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/TestAsset.cs new file mode 100644 index 00000000000000..4062b307cbc628 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/TestAsset.cs @@ -0,0 +1,7 @@ +public class TestAsset +{ + public string Name { get; init; } + public string RunnableProjectSubPath { get; init; } + public static readonly TestAsset WasmBasicTestApp = new() { Name = "WasmBasicTestApp", RunnableProjectSubPath = "App" }; + public static readonly TestAsset BlazorBasicTestApp = new() { Name = "BlazorBasicTestApp", RunnableProjectSubPath = "App" }; +} \ No newline at end of file diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildProjectOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BuildProjectOptions.cs deleted file mode 100644 index d0bd02a2289a57..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/BuildProjectOptions.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; - -#nullable enable - -namespace Wasm.Build.Tests; - -public record BuildProjectOptions -( - Action? InitProject = null, - bool? DotnetWasmFromRuntimePack = null, - GlobalizationMode GlobalizationMode = GlobalizationMode.Sharded, - string? CustomIcuFile = null, - bool UseCache = true, - bool ExpectSuccess = true, - bool AssertAppBundle = true, - bool CreateProject = true, - bool Publish = true, - bool BuildOnlyAfterPublish = true, - bool HasV8Script = true, - string? Verbosity = null, - string? Label = null, - string TargetFramework = BuildTestBase.DefaultTargetFramework, - string? MainJS = null, - bool IsBrowserProject = true, - IDictionary? ExtraBuildEnvironmentVariables = null, - string? BinFrameworkDir = null -); diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildPublishTests.cs b/src/mono/wasm/Wasm.Build.Tests/BuildPublishTests.cs index 31807b6d54bb7b..375b77d3501701 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildPublishTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildPublishTests.cs @@ -4,7 +4,7 @@ using System; using System.IO; using System.Linq; -using Wasm.Build.NativeRebuild.Tests; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; @@ -14,7 +14,7 @@ namespace Wasm.Build.Tests { - public class BuildPublishTests : NativeRebuildTestsBase + public class BuildPublishTests : WasmTemplateTestsBase { public BuildPublishTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -22,172 +22,97 @@ public BuildPublishTests(ITestOutputHelper output, SharedBuildPerTestClassFixtur } [Theory] - [BuildAndRun(host: RunHost.Chrome, aot: true, config: "Debug")] - public void Wasm_CannotAOT_InDebug(BuildArgs buildArgs, RunHost _, string id) + [BuildAndRun(config: Configuration.Debug, aot: true)] + public void Wasm_CannotAOT_InDebug(Configuration config, bool aot) { - string projectName = GetTestProjectPath(prefix: "no_aot_in_debug", config: buildArgs.Config); - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs); - (string projectDir, string buildOutput) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: true, - CreateProject: true, - Publish: true, - ExpectSuccess: false - )); - - Console.WriteLine($"buildOutput={buildOutput}"); - + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "no_aot_in_debug"); + (string _, string buildOutput) = PublishProject(info, config, new PublishOptions(AOT: aot, ExpectSuccess: false)); Assert.Contains("AOT is not supported in debug configuration", buildOutput); } [Theory] - [BuildAndRun(host: RunHost.Chrome, aot: false, config: "Release")] - [BuildAndRun(host: RunHost.Chrome, aot: false, config: "Debug")] - public void BuildThenPublishNoAOT(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun(config: Configuration.Release)] + [BuildAndRun(config: Configuration.Debug)] + public async Task BuildThenPublishNoAOT(Configuration config, bool aot) { - string projectName = GetTestProjectPath(prefix: "build_publish", config: buildArgs.Config); - - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs); - - // no relinking for build - bool relinked = false; - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: !relinked, - CreateProject: true, - Publish: false - )); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "build_publish"); + BuildProject(info, config); - Run(); + if (!_buildContext.TryGetBuildFor(info, out BuildResult? result)) + throw new XunitException($"Test bug: could not get the build result in the cache"); - if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product)) - throw new XunitException($"Test bug: could not get the build product in the cache"); - - File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog")); - - _testOutput.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}"); + BrowserRunOptions runOptions = new(config, TestScenario: "DotnetRun"); + await RunForBuildWithDotnetRun(runOptions); - // relink by default for Release+publish - relinked = buildArgs.Config == "Release"; - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - DotnetWasmFromRuntimePack: !relinked, - CreateProject: false, - Publish: true, - UseCache: false)); - - Run(); - - void Run() => RunAndTestWasmApp( - buildArgs, buildDir: _projectDir, expectedExitCode: 42, - test: output => {}, - host: host, id: id); + PublishProject(info, config, new PublishOptions(UseCache: false)); + await RunForPublishWithWebServer(runOptions); } [Theory] - [BuildAndRun(host: RunHost.Chrome, aot: true, config: "Release")] - public void BuildThenPublishWithAOT(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun(config: Configuration.Release, aot: true)] + public async Task BuildThenPublishWithAOT(Configuration config, bool aot) { - bool testUnicode = true; - string projectName = GetTestProjectPath( - prefix: "build_publish", config: buildArgs.Config, appendUnicode: testUnicode); - - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs); - - // no relinking for build - bool relinked = false; - (_, string output) = BuildProject(buildArgs, - id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: !relinked, - CreateProject: true, - Publish: false, - Label: "first_build")); - - BuildPaths paths = GetBuildPaths(buildArgs); - var pathsDict = _provider.GetFilesTable(buildArgs, paths, unchanged: false); - - string mainDll = $"{buildArgs.ProjectName}.dll"; - var firstBuildStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "build_publish"); + + bool isPublish = false; + (_, string output) = BuildProject(info, config, new BuildOptions(Label: "first_build", AOT: aot)); + + BuildPaths paths = GetBuildPaths(config, forPublish: isPublish); + IDictionary pathsDict = + GetFilesTable(info.ProjectName, aot, paths, unchanged: false); + + string mainDll = $"{info.ProjectName}.dll"; + var firstBuildStat = StatFiles(pathsDict); Assert.False(firstBuildStat["pinvoke.o"].Exists); Assert.False(firstBuildStat[$"{mainDll}.bc"].Exists); + + CheckOutputForNativeBuild(expectAOT: false, expectRelinking: isPublish, info.ProjectName, output); - CheckOutputForNativeBuild(expectAOT: false, expectRelinking: relinked, buildArgs, output, testUnicode); - - Run(expectAOT: false); - - if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product)) - throw new XunitException($"Test bug: could not get the build product in the cache"); + if (!_buildContext.TryGetBuildFor(info, out BuildResult? result)) + throw new XunitException($"Test bug: could not get the build result in the cache"); - File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog")); + BrowserRunOptions runOptions = new(config, TestScenario: "DotnetRun"); + await RunForBuildWithDotnetRun(runOptions); + File.Move(result!.LogFile, Path.ChangeExtension(result.LogFile!, ".first.binlog")); + _testOutput.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}"); - Dictionary publishStat = new(); // relink by default for Release+publish - (_, output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - DotnetWasmFromRuntimePack: false, - CreateProject: false, - Publish: true, - UseCache: false, - Label: "first_publish")); - - publishStat = (Dictionary)_provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + isPublish = true; + (_, output) = PublishProject(info, config, new PublishOptions(Label: "first_publish", UseCache: false, AOT: aot)); + + // publish has different paths ("for-publish", not "for-build") + paths = GetBuildPaths(config, forPublish: isPublish); + pathsDict = GetFilesTable(info.ProjectName, aot, paths, unchanged: false); + IDictionary publishStat = StatFiles(pathsDict); Assert.True(publishStat["pinvoke.o"].Exists); Assert.True(publishStat[$"{mainDll}.bc"].Exists); - CheckOutputForNativeBuild(expectAOT: true, expectRelinking: false, buildArgs, output, testUnicode); - _provider.CompareStat(firstBuildStat, publishStat, pathsDict.Values); - - Run(expectAOT: true); + CheckOutputForNativeBuild(expectAOT: true, expectRelinking: isPublish, info.ProjectName, output); + + // source maps are created for build but not for publish, make sure CompareStat won't expect them in publish: + pathsDict["dotnet.js.map"] = (pathsDict["dotnet.js.map"].fullPath, unchanged: false); + pathsDict["dotnet.runtime.js.map"] = (pathsDict["dotnet.runtime.js.map"].fullPath, unchanged: false); + CompareStat(firstBuildStat, publishStat, pathsDict); + await RunForPublishWithWebServer(runOptions); // second build - (_, output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: !relinked, - CreateProject: true, - Publish: false, - Label: "second_build")); - var secondBuildStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); - + isPublish = false; + (_, output) = BuildProject(info, config, new BuildOptions(Label: "second_build", UseCache: false, AOT: aot)); + var secondBuildStat = StatFiles(pathsDict); + // no relinking, or AOT - CheckOutputForNativeBuild(expectAOT: false, expectRelinking: false, buildArgs, output, testUnicode); + CheckOutputForNativeBuild(expectAOT: false, expectRelinking: isPublish, info.ProjectName, output); // no native files changed pathsDict.UpdateTo(unchanged: true); - _provider.CompareStat(publishStat, secondBuildStat, pathsDict.Values); - - void Run(bool expectAOT) => RunAndTestWasmApp( - buildArgs with { AOT = expectAOT }, - buildDir: _projectDir, expectedExitCode: 42, - host: host, id: id); + CompareStat(publishStat, secondBuildStat, pathsDict); } - void CheckOutputForNativeBuild(bool expectAOT, bool expectRelinking, BuildArgs buildArgs, string buildOutput, bool testUnicode) + void CheckOutputForNativeBuild(bool expectAOT, bool expectRelinking, string projectName, string buildOutput) { - if (testUnicode) - { - string projectNameCore = buildArgs.ProjectName.Replace(s_unicodeChars, ""); - TestUtils.AssertMatches(@$"{projectNameCore}\S+.dll -> {projectNameCore}\S+.dll.bc", buildOutput, contains: expectAOT); - TestUtils.AssertMatches(@$"{projectNameCore}\S+.dll.bc -> {projectNameCore}\S+.dll.o", buildOutput, contains: expectAOT); - } - else - { - TestUtils.AssertSubstring($"{buildArgs.ProjectName}.dll -> {buildArgs.ProjectName}.dll.bc", buildOutput, contains: expectAOT); - TestUtils.AssertSubstring($"{buildArgs.ProjectName}.dll.bc -> {buildArgs.ProjectName}.dll.o", buildOutput, contains: expectAOT); - } + TestUtils.AssertMatches(@$"{projectName}.dll -> {projectName}.dll.bc", buildOutput, contains: expectAOT); + TestUtils.AssertMatches(@$"{projectName}.dll.bc -> {projectName}.dll.o", buildOutput, contains: expectAOT); TestUtils.AssertMatches("pinvoke.c -> pinvoke.o", buildOutput, contains: expectRelinking || expectAOT); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index 4efbbe711e39e8..a8f36495f9032d 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -48,11 +48,10 @@ public abstract class BuildTestBase : IClassFixture skip automatic icu testing with Node // on Linux sharding does not work because we rely on LANG env var to check locale and emcc is overwriting it - protected static RunHost s_hostsForOSLocaleSensitiveTests = RunHost.Chrome; // FIXME: use an envvar to override this protected static int s_defaultPerTestTimeoutMs = s_isWindows ? 30 * 60 * 1000 : 15 * 60 * 1000; public static BuildEnvironment s_buildEnv; @@ -68,9 +67,9 @@ public static string GetNuGetConfigPathFor(string targetFramework) public TProvider GetProvider() where TProvider : ProjectProviderBase => (TProvider)_providerOfBaseType; - protected string? _projectDir + protected string _projectDir { - get => _providerOfBaseType.ProjectDir; + get => _providerOfBaseType.ProjectDir!; set => _providerOfBaseType.ProjectDir = value; } @@ -113,41 +112,37 @@ public BuildTestBase(ProjectProviderBase providerBase, ITestOutputHelper output, _providerOfBaseType = providerBase; } - public static IEnumerable> ConfigWithAOTData(bool aot, string? config = null, string? extraArgs = null) + public static IEnumerable> ConfigWithAOTData(bool aot, Configuration config = Configuration.Undefined) { - if (extraArgs == null) - extraArgs = string.Empty; - - if (config == null) + if (config == Configuration.Undefined) { return new IEnumerable[] { #if TEST_DEBUG_CONFIG_ALSO // list of each member data - for Debug+@aot - new object?[] { new BuildArgs("placeholder", "Debug", aot, "placeholder", extraArgs) }.AsEnumerable(), + new object?[] { Configuration.Debug, aot }.AsEnumerable(), #endif // list of each member data - for Release+@aot - new object?[] { new BuildArgs("placeholder", "Release", aot, "placeholder", extraArgs) }.AsEnumerable() + new object?[] { Configuration.Release, aot }.AsEnumerable() }.AsEnumerable(); } else { return new IEnumerable[] { - new object?[] { new BuildArgs("placeholder", config, aot, "placeholder", extraArgs) }.AsEnumerable() + new object?[] { config, aot }.AsEnumerable() }; } } public (CommandResult res, string logPath) BuildProjectWithoutAssert( - string id, - string config, - BuildProjectOptions buildProjectOptions, - params string[] extraArgs) + Configuration configuration, + string projectName, + MSBuildOptions buildOptions) { - string buildType = buildProjectOptions.Publish ? "publish" : "build"; - string logFileSuffix = buildProjectOptions.Label == null ? string.Empty : buildProjectOptions.Label.Replace(' ', '_') + "-"; - string logFilePath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-{logFileSuffix}{buildType}.binlog"); + string buildType = buildOptions.IsPublish ? "publish" : "build"; + string logFileSuffix = string.IsNullOrEmpty(buildOptions.Label) ? string.Empty : buildOptions.Label.Replace(' ', '_') + "-"; + string logFilePath = Path.Combine(_logPath, $"{projectName}-{logFileSuffix}{buildType}.binlog"); _testOutput.WriteLine($"{Environment.NewLine}** -------- {buildType} -------- **{Environment.NewLine}"); _testOutput.WriteLine($"Binlog path: {logFilePath}"); @@ -156,23 +151,23 @@ public BuildTestBase(ProjectProviderBase providerBase, ITestOutputHelper output, { buildType, $"-bl:{logFilePath}", - $"-p:Configuration={config}", + $"-p:Configuration={configuration}", "-nr:false" }; - commandLineArgs.AddRange(extraArgs); + commandLineArgs.AddRange(buildOptions.ExtraMSBuildArgs); - if (buildProjectOptions.Publish && buildProjectOptions.BuildOnlyAfterPublish) + if (buildOptions.IsPublish && buildOptions is PublishOptions po && po.BuildOnlyAfterPublish) commandLineArgs.Append("-p:WasmBuildOnlyAfterPublish=true"); using ToolCommand cmd = new DotNetCommand(s_buildEnv, _testOutput) - .WithWorkingDirectory(_projectDir!); + .WithWorkingDirectory(_projectDir); cmd.WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) - .WithEnvironmentVariables(buildProjectOptions.ExtraBuildEnvironmentVariables); + .WithEnvironmentVariables(buildOptions.ExtraBuildEnvironmentVariables); if (UseWBTOverridePackTargets && s_buildEnv.IsWorkload) cmd.WithEnvironmentVariable("WBTOverrideRuntimePack", "true"); CommandResult res = cmd.ExecuteWithCapturedOutput(commandLineArgs.ToArray()); - if (buildProjectOptions.ExpectSuccess) + if (buildOptions.ExpectSuccess) res.EnsureSuccessful(); else if (res.ExitCode == 0) throw new XunitException($"Build should have failed, but it didn't. Process exited with exitCode : {res.ExitCode}"); @@ -219,163 +214,8 @@ private string GetBinlogMessageContext(TextNode node) return string.Empty; } - protected bool IsDotnetWasmFromRuntimePack(BuildArgs buildArgs) => - !(buildArgs.AOT || (buildArgs.Config == "Release" && IsUsingWorkloads)); - - protected string RunAndTestWasmApp(BuildArgs buildArgs, - RunHost host, - string id, - Action? test = null, - string? buildDir = null, - string? bundleDir = null, - int expectedExitCode = 0, - string? args = null, - Dictionary? envVars = null, - string targetFramework = DefaultTargetFramework, - string? extraXHarnessMonoArgs = null, - string? extraXHarnessArgs = null, - string jsRelativePath = "test-main.js", - string environmentLocale = DefaultEnvironmentLocale) - { - buildDir ??= _projectDir; - envVars ??= new(); - envVars["XHARNESS_DISABLE_COLORED_OUTPUT"] = "true"; - if (buildArgs.AOT) - { - envVars["MONO_LOG_LEVEL"] = "debug"; - envVars["MONO_LOG_MASK"] = "aot"; - } - - if (s_buildEnv.EnvVars != null) - { - foreach (var kvp in s_buildEnv.EnvVars) - envVars[kvp.Key] = kvp.Value; - } - - bundleDir ??= Path.Combine(GetBinDir(baseDir: buildDir, config: buildArgs.Config, targetFramework: targetFramework), "AppBundle"); - IHostRunner hostRunner = GetHostRunnerFromRunHost(host); - if (!hostRunner.CanRunWBT()) - throw new InvalidOperationException("Running tests with V8 on windows isn't supported"); - - // Use wasm-console.log to get the xharness output for non-browser cases - string testCommand = hostRunner.GetTestCommand(); - XHarnessArgsOptions options = new XHarnessArgsOptions(jsRelativePath, environmentLocale, host); - string xharnessArgs = s_isWindows ? hostRunner.GetXharnessArgsWindowsOS(options) : hostRunner.GetXharnessArgsOtherOS(options); - bool useWasmConsoleOutput = hostRunner.UseWasmConsoleOutput(); - - extraXHarnessArgs += " " + xharnessArgs; - - string testLogPath = Path.Combine(_logPath, host.ToString()); - string output = RunWithXHarness( - testCommand, - testLogPath, - buildArgs.ProjectName, - bundleDir, - _testOutput, - envVars: envVars, - expectedAppExitCode: expectedExitCode, - extraXHarnessArgs: extraXHarnessArgs, - appArgs: args, - extraXHarnessMonoArgs: extraXHarnessMonoArgs, - useWasmConsoleOutput: useWasmConsoleOutput - ); - - TestUtils.AssertSubstring("AOT: image 'System.Private.CoreLib' found.", output, contains: buildArgs.AOT); - - if (s_isWindows && buildArgs.ProjectName.Contains(s_unicodeChars)) - { - // unicode chars in output on Windows are decoded in unknown way, so finding utf8 string is more complicated - string projectNameCore = buildArgs.ProjectName.Replace(s_unicodeChars, ""); - TestUtils.AssertMatches(@$"AOT: image '{projectNameCore}\S+' found.", output, contains: buildArgs.AOT); - } - else - { - TestUtils.AssertSubstring($"AOT: image '{buildArgs.ProjectName}' found.", output, contains: buildArgs.AOT); - } - - if (test != null) - test(output); - - return output; - } - - protected static string RunWithXHarness(string testCommand, string testLogPath, string projectName, string bundleDir, - ITestOutputHelper _testOutput, IDictionary? envVars = null, - int expectedAppExitCode = 0, int xharnessExitCode = 0, string? extraXHarnessArgs = null, - string? appArgs = null, string? extraXHarnessMonoArgs = null, bool useWasmConsoleOutput = false) - { - _testOutput.WriteLine($"============== {testCommand} ============="); - Directory.CreateDirectory(testLogPath); - - StringBuilder args = new(); - args.Append(s_xharnessRunnerCommand); - args.Append($" {testCommand}"); - args.Append($" --app=."); - args.Append($" --output-directory={testLogPath}"); - args.Append($" --expected-exit-code={expectedAppExitCode}"); - args.Append($" {extraXHarnessArgs ?? string.Empty}"); - args.Append(" --browser-arg=--disable-gpu"); - args.Append(" --pageLoadStrategy=none"); - - // `/.dockerenv` - is to check if this is running in a codespace - if (File.Exists("/.dockerenv")) - args.Append(" --browser-arg=--no-sandbox"); - - args.Append(" -- "); - if (extraXHarnessMonoArgs != null) - { - args.Append($" {extraXHarnessMonoArgs}"); - } - // App arguments - if (envVars != null) - { - var setenv = string.Join(' ', envVars - .Where(ev => ev.Key != "PATH") - .Select(kvp => $"\"--setenv={kvp.Key}={kvp.Value}\"").ToArray()); - args.Append($" {setenv}"); - } - - args.Append($" --run {projectName}.dll"); - args.Append($" {appArgs ?? string.Empty}"); - - _testOutput.WriteLine(string.Empty); - _testOutput.WriteLine($"---------- Running with {testCommand} ---------"); - var (exitCode, output) = RunProcess(s_buildEnv.DotNet, _testOutput, - args: args.ToString(), - workingDir: bundleDir, - envVars: envVars, - label: testCommand, - timeoutMs: s_defaultPerTestTimeoutMs); - - File.WriteAllText(Path.Combine(testLogPath, $"xharness.log"), output); - if (useWasmConsoleOutput) - { - string wasmConsolePath = Path.Combine(testLogPath, "wasm-console.log"); - try - { - if (File.Exists(wasmConsolePath)) - output = File.ReadAllText(wasmConsolePath); - else - _testOutput.WriteLine($"Warning: Could not find {wasmConsolePath}. Ignoring."); - } - catch (IOException ioex) - { - _testOutput.WriteLine($"Warning: Could not read {wasmConsolePath}: {ioex}"); - } - } - - if (exitCode != xharnessExitCode) - { - _testOutput.WriteLine($"Exit code: {exitCode}"); - if (exitCode != expectedAppExitCode) - throw new XunitException($"[{testCommand}] Exit code, expected {expectedAppExitCode} but got {exitCode} for command: {args}"); - } - - return output; - } - [MemberNotNull(nameof(_projectDir), nameof(_logPath))] - protected void InitPaths(string id) + protected (string, string) InitPaths(string id) { if (_projectDir == null) _projectDir = Path.Combine(BuildEnvironment.TmpPath, id); @@ -387,10 +227,13 @@ protected void InitPaths(string id) Directory.CreateDirectory(_nugetPackagesDir!); Directory.CreateDirectory(_logPath); + return (_logPath, _nugetPackagesDir); } protected void InitProjectDir(string dir, bool addNuGetSourceForLocalPackages = true, string targetFramework = DefaultTargetFramework) { + if (Directory.Exists(dir)) + Directory.Delete(dir, recursive: true); Directory.CreateDirectory(dir); File.WriteAllText(Path.Combine(dir, "Directory.Build.props"), s_buildEnv.DirectoryBuildPropsContents); File.WriteAllText(Path.Combine(dir, "Directory.Build.targets"), s_buildEnv.DirectoryBuildTargetsContents); @@ -411,38 +254,6 @@ protected void InitProjectDir(string dir, bool addNuGetSourceForLocalPackages = } } - protected const string SimpleProjectTemplate = - @$" - - {DefaultTargetFramework} - browser-wasm - Exe - true - test-main.js - ##EXTRA_PROPERTIES## - - - ##EXTRA_ITEMS## - - ##INSERT_AT_END## - "; - - protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProperties = "", string extraItems = "", string insertAtEnd = "", string projectTemplate = SimpleProjectTemplate) - { - if (buildArgs.AOT) - { - extraProperties = $"{extraProperties}\ntrue"; - extraProperties += $"\n{s_isWindows}\n"; - } - - extraItems += ""; - - string projectContents = projectTemplate - .Replace("##EXTRA_PROPERTIES##", extraProperties) - .Replace("##EXTRA_ITEMS##", extraItems) - .Replace("##INSERT_AT_END##", insertAtEnd); - return buildArgs with { ProjectFileContents = projectContents }; - } protected static string GetNuGetConfigWithLocalPackagesPath(string templatePath, string localNuGetsPath) { @@ -453,153 +264,11 @@ protected static string GetNuGetConfigWithLocalPackagesPath(string templatePath, return contents.Replace(s_nugetInsertionTag, $@""); } - protected string GetBinDir(string config, string targetFramework = DefaultTargetFramework, string? baseDir = null) - { - var dir = baseDir ?? _projectDir; - Assert.NotNull(dir); - return Path.Combine(dir!, "bin", config, targetFramework, "browser-wasm"); - } - - protected string GetObjDir(string config, string targetFramework = DefaultTargetFramework, string? baseDir = null) - { - var dir = baseDir ?? _projectDir; - Assert.NotNull(dir); - return Path.Combine(dir!, "obj", config, targetFramework, "browser-wasm"); - } - - public static (int exitCode, string buildOutput) RunProcess(string path, - ITestOutputHelper _testOutput, - string args = "", - IDictionary? envVars = null, - string? workingDir = null, - string? label = null, - int? timeoutMs = null) - { - var t = RunProcessAsync(path, _testOutput, args, envVars, workingDir, label, timeoutMs); - t.Wait(); - return t.Result; - } - - public static async Task<(int exitCode, string buildOutput)> RunProcessAsync(string path, - ITestOutputHelper _testOutput, - string args = "", - IDictionary? envVars = null, - string? workingDir = null, - string? label = null, - int? timeoutMs = null) - { - _testOutput.WriteLine($"Running {path} {args}"); - _testOutput.WriteLine($"WorkingDirectory: {workingDir}"); - StringBuilder outputBuilder = new(); - object syncObj = new(); - - var processStartInfo = new ProcessStartInfo - { - FileName = path, - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardError = true, - RedirectStandardOutput = true, - Arguments = args, - }; - - if (workingDir == null || !Directory.Exists(workingDir)) - throw new Exception($"Working directory {workingDir} not found"); - - if (workingDir != null) - processStartInfo.WorkingDirectory = workingDir; - - if (envVars != null) - { - if (envVars.Count > 0) - _testOutput.WriteLine("Setting environment variables for execution:"); - - foreach (KeyValuePair envVar in envVars) - { - processStartInfo.EnvironmentVariables[envVar.Key] = envVar.Value; - _testOutput.WriteLine($"\t{envVar.Key} = {envVar.Value}"); - } - - // runtime repo sets this, which interferes with the tests - processStartInfo.RemoveEnvironmentVariables("MSBuildSDKsPath"); - } - - Process process = new(); - process.StartInfo = processStartInfo; - process.EnableRaisingEvents = true; - - // AutoResetEvent resetEvent = new (false); - // process.Exited += (_, _) => { _testOutput.WriteLine ($"- exited called"); resetEvent.Set(); }; - - if (!process.Start()) - throw new ArgumentException("No process was started: process.Start() return false."); - - try - { - DataReceivedEventHandler logStdErr = (sender, e) => LogData($"[{label}-stderr]", e.Data); - DataReceivedEventHandler logStdOut = (sender, e) => LogData($"[{label}]", e.Data); - - process.ErrorDataReceived += logStdErr; - process.OutputDataReceived += logStdOut; - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - - using CancellationTokenSource cts = new(); - cts.CancelAfter(timeoutMs ?? s_defaultPerTestTimeoutMs); - - await process.WaitForExitAsync(cts.Token); - - if (cts.IsCancellationRequested) - { - // process didn't exit - process.Kill(entireProcessTree: true); - lock (syncObj) - { - var lastLines = outputBuilder.ToString().Split('\r', '\n').TakeLast(20); - throw new XunitException($"Process timed out. Last 20 lines of output:{Environment.NewLine}{string.Join(Environment.NewLine, lastLines)}"); - } - } - - // this will ensure that all the async event handling has completed - // and should be called after process.WaitForExit(int) - // https://learn.microsoft.com/dotnet/api/system.diagnostics.process.waitforexit?view=net-5.0#System_Diagnostics_Process_WaitForExit_System_Int32_ - process.WaitForExit(); - - process.ErrorDataReceived -= logStdErr; - process.OutputDataReceived -= logStdOut; - process.CancelErrorRead(); - process.CancelOutputRead(); - - lock (syncObj) - { - var exitCode = process.ExitCode; - return (process.ExitCode, outputBuilder.ToString().Trim('\r', '\n')); - } - } - catch (Exception ex) - { - _testOutput.WriteLine($"-- exception -- {ex}"); - throw; - } - - void LogData(string label, string? message) - { - lock (syncObj) - { - if (message != null) - { - _testOutput.WriteLine($"{label} {message}"); - } - outputBuilder.AppendLine($"{label} {message}"); - } - } - } - - public static string AddItemsPropertiesToProject(string projectFile, string? extraProperties = null, string? extraItems = null, string? atTheEnd = null) + public static string AddItemsPropertiesToProject(string projectFile, string? extraProperties = null, string? extraItems = null, string? insertAtEnd = null) { if (!File.Exists(projectFile)) throw new Exception($"{projectFile} does not exist"); - if (extraProperties == null && extraItems == null && atTheEnd == null) + if (extraProperties == null && extraItems == null && insertAtEnd == null) return projectFile; XmlDocument doc = new(); @@ -620,10 +289,10 @@ public static string AddItemsPropertiesToProject(string projectFile, string? ext root.AppendChild(node); } - if (atTheEnd != null) + if (insertAtEnd != null) { XmlNode node = doc.CreateNode(XmlNodeType.DocumentFragment, "foo", null); - node.InnerXml = atTheEnd; + node.InnerXml = insertAtEnd; root.InsertAfter(node, root.LastChild); } @@ -640,15 +309,6 @@ public void Dispose() public static string GetRandomId() => TestUtils.FixupSymbolName(Path.GetRandomFileName()); - internal BuildPaths GetBuildPaths(BuildArgs buildArgs, bool forPublish = true) - { - string objDir = GetObjDir(buildArgs.Config); - string bundleDir = Path.Combine(GetBinDir(baseDir: _projectDir, config: buildArgs.Config), "AppBundle"); - string wasmDir = Path.Combine(objDir, "wasm", forPublish ? "for-publish" : "for-build"); - - return new BuildPaths(wasmDir, objDir, GetBinDir(buildArgs.Config), bundleDir); - } - protected static string GetSkiaSharpReferenceItems() => @" @@ -661,21 +321,5 @@ public static int Main() return 42; } }"; - - private static IHostRunner GetHostRunnerFromRunHost(RunHost host) => host switch - { - RunHost.V8 => new V8HostRunner(), - _ => new BrowserHostRunner(), - }; } - - public record BuildArgs(string ProjectName, - string Config, - bool AOT, - string Id, - string? ExtraBuildArgs, - string? ProjectFileContents=null); - public record BuildProduct(string ProjectDir, string LogFile, bool Result, string BuildOutput); - - public enum NativeFilesType { FromRuntimePack, Relinked, AOT }; } diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/AssertBundleOptionsBase.cs b/src/mono/wasm/Wasm.Build.Tests/Common/AssertBundleOptionsBase.cs deleted file mode 100644 index bf5301a53c7628..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Common/AssertBundleOptionsBase.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System.IO; - -namespace Wasm.Build.Tests; - -public abstract record AssertBundleOptionsBase( - string Config, - bool IsPublish, - string TargetFramework, - string BinFrameworkDir, - string? CustomIcuFile, - string BundleDirName = "wwwroot", - GlobalizationMode GlobalizationMode = GlobalizationMode.Sharded, - string BootJsonFileName = "blazor.boot.json", - NativeFilesType ExpectedFileType = NativeFilesType.FromRuntimePack, - RuntimeVariant RuntimeType = RuntimeVariant.SingleThreaded, - bool ExpectFingerprintOnDotnetJs = false, - bool ExpectSymbolsFile = true, - bool AssertIcuAssets = true, - bool AssertSymbolsFile = true) -{ - public bool DotnetWasmFromRuntimePack => ExpectedFileType == NativeFilesType.FromRuntimePack; - public bool AOT => ExpectedFileType == NativeFilesType.AOT; - public string BundleDir => Path.Combine(BinFrameworkDir, ".."); -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/AssertTestMainJsAppBundleOptions.cs b/src/mono/wasm/Wasm.Build.Tests/Common/AssertTestMainJsAppBundleOptions.cs deleted file mode 100644 index 2924281dbdc433..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Common/AssertTestMainJsAppBundleOptions.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -namespace Wasm.Build.Tests; - -public record AssertTestMainJsAppBundleOptions( - string Config, - bool IsPublish, - string TargetFramework, - string BinFrameworkDir, - string? CustomIcuFile, - string ProjectName, - string MainJS, - GlobalizationMode GlobalizationMode = GlobalizationMode.Sharded, - string BootJsonFileName = "blazor.boot.json", - NativeFilesType ExpectedFileType = NativeFilesType.FromRuntimePack, - RuntimeVariant RuntimeType = RuntimeVariant.SingleThreaded, - bool ExpectFingerprintOnDotnetJs = false, - bool ExpectSymbolsFile = true, - bool AssertIcuAssets = true, - bool AssertSymbolsFile = true, - bool HasV8Script = false, - bool IsBrowserProject = true) - : AssertWasmSdkBundleOptions( - Config: Config, - IsPublish: IsPublish, - TargetFramework: TargetFramework, - BinFrameworkDir: BinFrameworkDir, - CustomIcuFile: CustomIcuFile, - GlobalizationMode: GlobalizationMode, - ExpectedFileType: ExpectedFileType, - RuntimeType: RuntimeType, - BootConfigFileName: BootJsonFileName, - ExpectFingerprintOnDotnetJs: ExpectFingerprintOnDotnetJs, - ExpectSymbolsFile: ExpectSymbolsFile, - AssertIcuAssets: AssertIcuAssets, - AssertSymbolsFile: AssertSymbolsFile) -{ -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/AssertWasmSdkBundleOptions.cs b/src/mono/wasm/Wasm.Build.Tests/Common/AssertWasmSdkBundleOptions.cs deleted file mode 100644 index e489c41f7b15d5..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Common/AssertWasmSdkBundleOptions.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -namespace Wasm.Build.Tests; - -// Identical to AssertBundleOptionsBase currently -public record AssertWasmSdkBundleOptions( - string Config, - bool IsPublish, - string TargetFramework, - string BinFrameworkDir, - string? CustomIcuFile, - GlobalizationMode GlobalizationMode = GlobalizationMode.Sharded, - string BootConfigFileName = "blazor.boot.json", - NativeFilesType ExpectedFileType = NativeFilesType.FromRuntimePack, - RuntimeVariant RuntimeType = RuntimeVariant.SingleThreaded, - bool ExpectFingerprintOnDotnetJs = false, - bool ExpectSymbolsFile = true, - bool AssertIcuAssets = true, - bool AssertSymbolsFile = true) - : AssertBundleOptionsBase( - Config: Config, - IsPublish: IsPublish, - TargetFramework: TargetFramework, - BinFrameworkDir: BinFrameworkDir, - CustomIcuFile: CustomIcuFile, - GlobalizationMode: GlobalizationMode, - ExpectedFileType: ExpectedFileType, - RuntimeType: RuntimeType, - BootJsonFileName: BootConfigFileName, - ExpectFingerprintOnDotnetJs: ExpectFingerprintOnDotnetJs, - ExpectSymbolsFile: ExpectSymbolsFile, - AssertIcuAssets: AssertIcuAssets, - AssertSymbolsFile: AssertSymbolsFile) -{} diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/BuildAndRunAttribute.cs b/src/mono/wasm/Wasm.Build.Tests/Common/BuildAndRunAttribute.cs index 3ba21e075ce4a8..609834bc6bf6a2 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/BuildAndRunAttribute.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/BuildAndRunAttribute.cs @@ -14,7 +14,7 @@ namespace Wasm.Build.Tests /// /// Example usage: /// [BuildAndRun(aot: true, parameters: new object[] { arg1, arg2 })] - /// public void Test(BuildArgs, arg1, arg2, RunHost, id) + /// public void Test(ProjectInfo, arg1, arg2, RunHost, id) /// [DataDiscoverer("Xunit.Sdk.DataDiscoverer", "xunit.core")] [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] @@ -22,26 +22,23 @@ public class BuildAndRunAttribute : DataAttribute { private readonly IEnumerable _data; - public BuildAndRunAttribute(BuildArgs buildArgs, RunHost host = RunHost.All, params object?[] parameters) + +#if TARGET_WASI + // remove when wasi is refectored and use Configuration + public BuildAndRunAttribute(bool aot=false, string? config=null, params object?[] parameters) { - _data = new IEnumerable[] - { - new object?[] { buildArgs }.AsEnumerable(), - } - .AsEnumerable() + _data = BuildTestBase.ConfigWithAOTData(aot, config) .Multiply(parameters) - .WithRunHosts(host) .UnwrapItemsAsArrays().ToList(); } - - public BuildAndRunAttribute(bool aot=false, RunHost host = RunHost.All, string? config=null, params object?[] parameters) +#else + public BuildAndRunAttribute(bool aot=false, Configuration config=Configuration.Undefined, params object?[] parameters) { _data = BuildTestBase.ConfigWithAOTData(aot, config) .Multiply(parameters) - .WithRunHosts(host) .UnwrapItemsAsArrays().ToList(); } - +#endif public override IEnumerable GetData(MethodInfo testMethod) => _data; } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/BuildPaths.cs b/src/mono/wasm/Wasm.Build.Tests/Common/BuildPaths.cs index 1affeed9a37bb8..3f08f890eb383d 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/BuildPaths.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/BuildPaths.cs @@ -4,4 +4,4 @@ #nullable enable namespace Wasm.Build.Tests; -public record BuildPaths(string ObjWasmDir, string ObjDir, string BinDir, string BundleDir); +public record BuildPaths(string ObjWasmDir, string ObjDir, string BinDir, string BinFrameworkDir); diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/Configuration.cs b/src/mono/wasm/Wasm.Build.Tests/Common/Configuration.cs new file mode 100644 index 00000000000000..151d21bbdcc6b5 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/Common/Configuration.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +namespace Wasm.Build.Tests; + +public enum Configuration +{ + Release, + Debug, + Undefined +} diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs b/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs index d928fc1e1b4069..1e4df1407b692e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs @@ -23,6 +23,7 @@ internal static class EnvironmentVariables internal static readonly bool ShowBuildOutput = IsRunningOnCI || Environment.GetEnvironmentVariable("SHOW_BUILD_OUTPUT") is not null; internal static readonly bool UseWebcil = Environment.GetEnvironmentVariable("USE_WEBCIL_FOR_TESTS") is "true"; internal static readonly bool UseFingerprinting = Environment.GetEnvironmentVariable("USE_FINGERPRINTING_FOR_TESTS") is "true"; + internal static readonly bool UseFingerprintingDotnetJS = Environment.GetEnvironmentVariable("WASM_FINGERPRINT_DOTNET_JS") is "true"; internal static readonly string? SdkDirName = Environment.GetEnvironmentVariable("SDK_DIR_NAME"); internal static readonly string? WasiSdkPath = Environment.GetEnvironmentVariable("WASI_SDK_PATH"); internal static readonly bool WorkloadsTestPreviousVersions = Environment.GetEnvironmentVariable("WORKLOADS_TEST_PREVIOUS_VERSIONS") is "true"; diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/HelperExtensions.cs b/src/mono/wasm/Wasm.Build.Tests/Common/HelperExtensions.cs index 35023c6174c2f9..130ddd5e81c4e8 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/HelperExtensions.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/HelperExtensions.cs @@ -61,49 +61,6 @@ public static class HelperExtensions public static IEnumerable> MultiplyWithSingleArgs(this IEnumerable> data, params object?[] arrayOfArgs) => data.SelectMany(row => arrayOfArgs.Select(argCol => row.Concat(new[] { argCol }))); - public static object?[] Enumerate(this RunHost host) - { - if (host == RunHost.None) - return Array.Empty(); - - var list = new List(); - foreach (var value in Enum.GetValues()) - { - if (value == RunHost.None) - continue; - - if (value == RunHost.V8 && OperatingSystem.IsWindows()) - { - // Don't run tests with V8 on windows - continue; - } - - // Ignore any combos like RunHost.All from Enum.GetValues - // by ignoring any @value that has more than 1 bit set - if (((int)value & ((int)value - 1)) != 0) - continue; - - if ((host & value) == value) - list.Add(value); - } - return list.ToArray(); - } - - public static IEnumerable> WithRunHosts(this IEnumerable> data, RunHost hosts) - { - IEnumerable hostsEnumerable = hosts.Enumerate(); - if (hosts == RunHost.None) - return data.Select(d => d.Append((object?) BuildTestBase.GetRandomId())); - - return data.SelectMany(d => - { - string runId = BuildTestBase.GetRandomId(); - return hostsEnumerable.Select(o => - d.Append((object?)o) - .Append((object?)runId)); - }); - } - public static void UpdateTo(this IDictionary dict, bool unchanged, params string[] filenames) { IEnumerable keys = filenames.Length == 0 ? dict.Keys.ToList() : filenames; diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/ProjectInfo.cs b/src/mono/wasm/Wasm.Build.Tests/Common/ProjectInfo.cs new file mode 100644 index 00000000000000..70fc3cbe426cf8 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/Common/ProjectInfo.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +namespace Wasm.Build.Tests; + +public record ProjectInfo( + // string Configuration, + // bool AOT, + string ProjectName, + string ProjectFilePath, + string LogPath, + string NugetDir +); diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/RunHost.cs b/src/mono/wasm/Wasm.Build.Tests/Common/RunHost.cs deleted file mode 100644 index 0aa1ae14ea8120..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Common/RunHost.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; - -namespace Wasm.Build.Tests -{ - [Flags] - public enum RunHost - { - None = 0, - V8 = 1, - Chrome = 2, - Safari = 4, - Firefox = 8, - - All = V8 | Chrome//| Firefox//Safari - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/RunResult.cs b/src/mono/wasm/Wasm.Build.Tests/Common/RunResult.cs new file mode 100644 index 00000000000000..b947d92143836e --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/Common/RunResult.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Wasm.Build.Tests; + +public record RunResult( + int ExitCode, + IReadOnlyCollection TestOutput, + IReadOnlyCollection ConsoleOutput, + IReadOnlyCollection ServerOutput +); \ No newline at end of file diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/Template.cs b/src/mono/wasm/Wasm.Build.Tests/Common/Template.cs new file mode 100644 index 00000000000000..b840205de00706 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/Common/Template.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +namespace Wasm.Build.Tests; + +public enum Template +{ + BlazorWasm, + WasmBrowser, + Wasi +} diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/TestUtils.cs b/src/mono/wasm/Wasm.Build.Tests/Common/TestUtils.cs index fdf88fecc9ee0f..aed3fbc235c8b5 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/TestUtils.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/TestUtils.cs @@ -65,6 +65,14 @@ public static string FindSubDirIgnoringCase(string parentDir, string dirName) return first ?? Path.Combine(parentDir, dirName); } + public static void AssertSubstring(string substring, IReadOnlyCollection full, bool contains) + { + if (contains) + Assert.Contains(full, m => m.Contains(substring)); + else + Assert.All(full, m => Assert.DoesNotContain(substring, m)); + } + public static void AssertSubstring(string substring, string full, bool contains) { if (contains) diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/Utils.cs b/src/mono/wasm/Wasm.Build.Tests/Common/Utils.cs index b6d087a0135759..54152700de9899 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/Utils.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/Utils.cs @@ -13,7 +13,7 @@ internal static class Utils { - public static void DirectoryCopy(string sourceDirName, string destDirName, Func? predicate=null, bool copySubDirs=true, bool silent=false, ITestOutputHelper? testOutput = null) + public static void DirectoryCopy(string sourceDirName, string destDirName, Func? predicate=null, bool copySubDirs=true, bool silent=false, ITestOutputHelper? testOutput = null, bool overwrite = false) { // Get the subdirectories for the specified directory. DirectoryInfo dir = new DirectoryInfo(sourceDirName); @@ -45,7 +45,7 @@ public static void DirectoryCopy(string sourceDirName, string destDirName, Func< string tempPath = Path.Combine(destDirName, file.Name); if (!silent) testOutput?.WriteLine($"Copying {fullPath} to {tempPath}"); - file.CopyTo(tempPath, false); + file.CopyTo(tempPath, overwrite); } // If copying subdirectories, copy them and their contents to new location. diff --git a/src/mono/wasm/Wasm.Build.Tests/ConfigSrcTests.cs b/src/mono/wasm/Wasm.Build.Tests/ConfigSrcTests.cs deleted file mode 100644 index e971485b91ef44..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/ConfigSrcTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.IO; -using Xunit; -using Xunit.Abstractions; - -#nullable enable - -namespace Wasm.Build.Tests; - -public class ConfigSrcTests : TestMainJsTestBase -{ - public ConfigSrcTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) - { } - - // NOTE: port number determinizes dynamically, so could not generate absolute URI - [Theory] - [BuildAndRun(host: RunHost.V8)] - public void ConfigSrcAbsolutePath(BuildArgs buildArgs, RunHost host, string id) - { - buildArgs = buildArgs with { ProjectName = $"configsrcabsolute_{buildArgs.Config}_{buildArgs.AOT}" }; - buildArgs = ExpandBuildArgs(buildArgs); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: IsDotnetWasmFromRuntimePack(buildArgs))); - - string binDir = GetBinDir(baseDir: _projectDir!, config: buildArgs.Config); - string bundleDir = Path.Combine(binDir, "AppBundle"); - string configSrc = Path.GetFullPath(Path.Combine(bundleDir, "_framework", "blazor.boot.json")); - - RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id, extraXHarnessMonoArgs: $"--config-src=\"{configSrc}\""); - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/DebugLevelTests.cs b/src/mono/wasm/Wasm.Build.Tests/DebugLevelTests.cs index 8fc90b9376ec59..47d1bc3c0426bf 100644 --- a/src/mono/wasm/Wasm.Build.Tests/DebugLevelTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/DebugLevelTests.cs @@ -8,118 +8,108 @@ using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; -using Wasm.Build.Tests.TestAppScenarios; #nullable enable namespace Wasm.Build.Tests; -// ToDo: fix to be based on WasmTemplateTestBase -public class DebugLevelTests : AppTestBase +public class DebugLevelTests : WasmTemplateTestsBase { public DebugLevelTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - private void AssertDebugLevel(string result, int value) - => Assert.Contains($"WasmDebugLevel: {value}", result); + private void AssertDebugLevel(IReadOnlyCollection result, int value) + => Assert.Contains(result, m => m.Contains($"WasmDebugLevel: {value}")); - private BuildProjectOptions GetProjectOptions(bool isPublish = false) => - new BuildProjectOptions( - DotnetWasmFromRuntimePack: !isPublish, - CreateProject: false, - MainJS: "main.js", - HasV8Script: false, - Publish: isPublish, - AssertAppBundle: false, - UseCache: false - ); - - private string BuildPublishProject(string projectName, string config, bool isPublish = false, params string[] extraArgs) + [Theory] + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public async Task BuildWithDefaultLevel(Configuration configuration) { - var buildArgs = new BuildArgs(projectName, config, false, projectName, null); - buildArgs = ExpandBuildArgs(buildArgs); - (string _, string output) = BuildTemplateProject(buildArgs, - buildArgs.Id, - GetProjectOptions(isPublish), - extraArgs: extraArgs + ProjectInfo info = CopyTestAsset( + configuration, + aot: false, + asset: TestAsset.WasmBasicTestApp, + idPrefix: "DebugLevelTests_BuildWithDefaultLevel" ); - return output; + BuildProject(info, configuration); + BrowserRunOptions options = new(configuration, TestScenario: "DebugLevelTest", ExpectedExitCode: 42); + RunResult result = await RunForBuildWithDotnetRun(options); + AssertDebugLevel(result.TestOutput, -1); } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public async Task BuildWithDefaultLevel(string configuration) + [InlineData(Configuration.Debug, 1)] + [InlineData(Configuration.Release, 1)] + [InlineData(Configuration.Debug, 0)] + [InlineData(Configuration.Release, 0)] + public async Task BuildWithExplicitValue(Configuration configuration, int debugLevel) { - string testAssetName = "WasmBasicTestApp"; - string projectFile = $"{testAssetName}.csproj"; - CopyTestAsset(testAssetName, $"DebugLevelTests_BuildWithDefaultLevel_{configuration}", "App"); - BuildPublishProject(projectFile, configuration); - - string result = await RunBuiltBrowserApp(configuration, projectFile, testScenario: "DebugLevelTest"); - AssertDebugLevel(result, -1); + ProjectInfo info = CopyTestAsset( + configuration, + aot: false, + asset: TestAsset.WasmBasicTestApp, + idPrefix: "DebugLevelTests_BuildWithExplicitValue" + ); + BuildProject(info, configuration, new BuildOptions(ExtraMSBuildArgs: $"-p:WasmDebugLevel={debugLevel}")); + BrowserRunOptions options = new(configuration, TestScenario: "DebugLevelTest", ExpectedExitCode: 42); + RunResult result = await RunForBuildWithDotnetRun(options); + AssertDebugLevel(result.TestOutput, debugLevel); } [Theory] - [InlineData("Debug", 1)] - [InlineData("Release", 1)] - [InlineData("Debug", 0)] - [InlineData("Release", 0)] - public async Task BuildWithExplicitValue(string configuration, int debugLevel) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public async Task PublishWithDefaultLevel(Configuration configuration) { - string testAssetName = "WasmBasicTestApp"; - string projectFile = $"{testAssetName}.csproj"; - CopyTestAsset(testAssetName, $"DebugLevelTests_BuildWithExplicitValue_{configuration}", "App"); - BuildPublishProject(projectFile, configuration, extraArgs: $"-p:WasmDebugLevel={debugLevel}"); - - string result = await RunBuiltBrowserApp(configuration, projectFile, testScenario: "DebugLevelTest"); - AssertDebugLevel(result, debugLevel); + ProjectInfo info = CopyTestAsset( + configuration, + aot: false, + asset: TestAsset.WasmBasicTestApp, + idPrefix: "DebugLevelTests_PublishWithDefaultLevel" + ); + PublishProject(info, configuration); + BrowserRunOptions options = new(configuration, TestScenario: "DebugLevelTest", ExpectedExitCode: 42); + RunResult result = await RunForPublishWithWebServer(options); + AssertDebugLevel(result.TestOutput, 0); } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public async Task PublishWithDefaultLevel(string configuration) + [InlineData(Configuration.Debug, 1)] + [InlineData(Configuration.Release, 1)] + [InlineData(Configuration.Debug, -1)] + [InlineData(Configuration.Release, -1)] + public async Task PublishWithExplicitValue(Configuration configuration, int debugLevel) { - string testAssetName = "WasmBasicTestApp"; - string projectFile = $"{testAssetName}.csproj"; - CopyTestAsset(testAssetName, $"DebugLevelTests_PublishWithDefaultLevel_{configuration}", "App"); - BuildPublishProject(projectFile, configuration, isPublish: true); - - string result = await RunPublishedBrowserApp(configuration, testScenario: "DebugLevelTest"); - AssertDebugLevel(result, 0); + ProjectInfo info = CopyTestAsset( + configuration, + aot: false, + asset: TestAsset.WasmBasicTestApp, + idPrefix: "DebugLevelTests_PublishWithExplicitValue" + ); + PublishProject(info, configuration, new PublishOptions(ExtraMSBuildArgs: $"-p:WasmDebugLevel={debugLevel}")); + BrowserRunOptions options = new(configuration, TestScenario: "DebugLevelTest", ExpectedExitCode: 42); + RunResult result = await RunForPublishWithWebServer(options); + AssertDebugLevel(result.TestOutput, debugLevel); } - [Theory] - [InlineData("Debug", 1)] - [InlineData("Release", 1)] - [InlineData("Debug", -1)] - [InlineData("Release", -1)] - public async Task PublishWithExplicitValue(string configuration, int debugLevel) - { - string testAssetName = "WasmBasicTestApp"; - string projectFile = $"{testAssetName}.csproj"; - CopyTestAsset(testAssetName, $"DebugLevelTests_PublishWithExplicitValue_{configuration}", "App"); - BuildPublishProject(projectFile, configuration, isPublish: true, extraArgs: $"-p:WasmDebugLevel={debugLevel}"); - - string result = await RunBuiltBrowserApp(configuration, projectFile, testScenario: "DebugLevelTest"); - AssertDebugLevel(result, debugLevel); - } - [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public async Task PublishWithDefaultLevelAndPdbs(string configuration) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public async Task PublishWithDefaultLevelAndPdbs(Configuration configuration) { - string testAssetName = "WasmBasicTestApp"; - string projectFile = $"{testAssetName}.csproj"; - CopyTestAsset(testAssetName, $"DebugLevelTests_PublishWithDefaultLevelAndPdbs_{configuration}", "App"); - BuildPublishProject(projectFile, configuration, isPublish: true, extraArgs: $"-p:CopyOutputSymbolsToPublishDirectory=true"); - - var result = await RunPublishedBrowserApp(configuration, testScenario: "DebugLevelTest"); - AssertDebugLevel(result, -1); + ProjectInfo info = CopyTestAsset( + configuration, + aot: false, + asset: TestAsset.WasmBasicTestApp, + idPrefix: "DebugLevelTests_PublishWithDefaultLevelAndPdbs" + ); + PublishProject(info, configuration, new PublishOptions(ExtraMSBuildArgs: $"-p:CopyOutputSymbolsToPublishDirectory=true")); + BrowserRunOptions options = new(configuration, TestScenario: "DebugLevelTest", ExpectedExitCode: 42); + RunResult result = await RunForPublishWithWebServer(options); + AssertDebugLevel(result.TestOutput, -1); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/DllImportTests.cs b/src/mono/wasm/Wasm.Build.Tests/DllImportTests.cs new file mode 100644 index 00000000000000..8f5dfcf0d9b7e3 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/DllImportTests.cs @@ -0,0 +1,181 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +#nullable enable + +namespace Wasm.Build.Tests +{ + public class DllImportTests : PInvokeTableGeneratorTestsBase + { + public DllImportTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + [Theory] + [BuildAndRun(aot: false)] + public async void NativeLibraryWithVariadicFunctions(Configuration config, bool aot) + { + ProjectInfo info = PrepareProjectForVariadicFunction(config, aot, "variadic"); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "VariadicFunctions.cs")); + string output = PublishForVariadicFunctionTests(info, config, aot); + Assert.Matches("warning.*native function.*sum.*varargs", output); + Assert.Contains("System.Int32 sum_one(System.Int32)", output); + Assert.Contains("System.Int32 sum_two(System.Int32, System.Int32)", output); + Assert.Contains("System.Int32 sum_three(System.Int32, System.Int32, System.Int32)", output); + + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.Contains("Main running", result.TestOutput); + } + + [Theory] + [BuildAndRun()] + public async void DllImportWithFunctionPointersCompilesWithoutWarning(Configuration config, bool aot) + { + ProjectInfo info = PrepareProjectForVariadicFunction(config, aot, "fnptr"); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "DllImportNoWarning.cs")); + string output = PublishForVariadicFunctionTests(info, config, aot); + + Assert.DoesNotMatch("warning\\sWASM0001.*Could\\snot\\sget\\spinvoke.*Parsing\\sfunction\\spointer\\stypes", output); + Assert.DoesNotMatch("warning\\sWASM0001.*Skipping.*using_sum_one.*because.*function\\spointer", output); + + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.Contains("Main running", result.TestOutput); + } + + [Theory] + [BuildAndRun()] + public async void DllImportWithFunctionPointers_ForVariadicFunction_CompilesWithWarning(Configuration config, bool aot) + { + ProjectInfo info = PrepareProjectForVariadicFunction(config, aot, "fnptr_variadic"); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "DllImportWarning.cs")); + string output = PublishForVariadicFunctionTests(info, config, aot); + + Assert.DoesNotMatch("warning\\sWASM0001.*Could\\snot\\sget\\spinvoke.*Parsing\\sfunction\\spointer\\stypes", output); + Assert.DoesNotMatch("warning\\sWASM0001.*Skipping.*using_sum_one.*because.*function\\spointer", output); + + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.Contains("Main running", result.TestOutput); + } + + [Theory] + [BuildAndRun()] + public async void DllImportWithFunctionPointers_WarningsAsMessages(Configuration config, bool aot) + { + string extraProperties = "$(MSBuildWarningsAsMessage);WASM0001"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "fnptr", extraProperties: extraProperties); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "FunctionPointers.cs")); + + string output = PublishForVariadicFunctionTests(info, config, aot); + Assert.DoesNotContain("warning WASM0001", output); + + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.Contains("Main running", result.TestOutput); + } + + [Theory] + [BuildAndRun()] + public void UnmanagedCallback_WithFunctionPointers_CompilesWithWarnings(Configuration config, bool aot) + { + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "cb_fnptr"); + string programRelativePath = Path.Combine("Common", "Program.cs"); + ReplaceFile(programRelativePath, Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "FunctionPointers.cs")); + UpdateFile(programRelativePath, new Dictionary { { "[DllImport(\"someting\")]", "[UnmanagedCallersOnly]" } }); + string output = PublishForVariadicFunctionTests(info, config, aot); + Assert.DoesNotMatch("warning\\sWASM0001.*Skipping.*Test::SomeFunction1.*because.*function\\spointer", output); + } + + [Theory] + [BuildAndRun(parameters: new object[] { new object[] { + "with-hyphen", + "with#hash-and-hyphen", + "with.per.iod", + "with🚀unicode#" + } })] + public async void CallIntoLibrariesWithNonAlphanumericCharactersInTheirNames(Configuration config, bool aot, string[] libraryNames) + { + var extraItems = @""; + string extraProperties = aot ? string.Empty : "true"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "abi", extraItems: extraItems, extraProperties: extraProperties); + + int baseArg = 10; + GenerateSourceFiles(_projectDir, baseArg); + bool isPublish = aot; + (_, string output) = isPublish ? + PublishProject(info, config, new PublishOptions(AOT: aot), isNativeBuild: true): + BuildProject(info, config, new BuildOptions(AOT: aot), isNativeBuild: true); + + var runOptions = new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 42); + RunResult result = isPublish ? await RunForPublishWithWebServer(runOptions) : await RunForBuildWithDotnetRun(runOptions); + + for (int i = 0; i < libraryNames.Length; i ++) + { + Assert.Contains($"square_{i}: {(i + baseArg) * (i + baseArg)}", result.TestOutput); + } + + void GenerateSourceFiles(string outputPath, int baseArg) + { + StringBuilder csBuilder = new($@" + using System; + using System.Runtime.InteropServices; + "); + + StringBuilder dllImportsBuilder = new(); + for (int i = 0; i < libraryNames.Length; i ++) + { + dllImportsBuilder.AppendLine($"[DllImport(\"{libraryNames[i]}\")] static extern int square_{i}(int x);"); + csBuilder.AppendLine($@"Console.WriteLine($""TestOutput -> square_{i}: {{square_{i}({i + baseArg})}}"");"); + + string nativeCode = $@" + #include + + int square_{i}(int x) + {{ + return x * x; + }}"; + File.WriteAllText(Path.Combine(outputPath, $"{libraryNames[i]}.c"), nativeCode); + } + + csBuilder.AppendLine("return 42;"); + csBuilder.Append(dllImportsBuilder); + + UpdateFile(Path.Combine("Common", "Program.cs"), csBuilder.ToString()); + } + } + + private ProjectInfo PrepareProjectForVariadicFunction(Configuration config, bool aot, string prefix, string extraProperties = "") + { + string objectFilename = "variadic.o"; + extraProperties += "true<_WasmDevel>true"; + string extraItems = $""; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, prefix, extraItems: extraItems, extraProperties: extraProperties); + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", objectFilename), Path.Combine(_projectDir, objectFilename)); + return info; + } + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/DownloadThenInitTests.cs b/src/mono/wasm/Wasm.Build.Tests/DownloadThenInitTests.cs similarity index 67% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/DownloadThenInitTests.cs rename to src/mono/wasm/Wasm.Build.Tests/DownloadThenInitTests.cs index aaa2df9b596557..6d8f0a6167602b 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/DownloadThenInitTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/DownloadThenInitTests.cs @@ -10,9 +10,9 @@ #nullable enable -namespace Wasm.Build.Tests.TestAppScenarios; +namespace Wasm.Build.Tests; -public class DownloadThenInitTests : AppTestBase +public class DownloadThenInitTests : WasmTemplateTestsBase { public DownloadThenInitTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -20,16 +20,16 @@ public DownloadThenInitTests(ITestOutputHelper output, SharedBuildPerTestClassFi } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public async Task NoResourcesReFetchedAfterDownloadFinished(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public async Task NoResourcesReFetchedAfterDownloadFinished(Configuration config) { - CopyTestAsset("WasmBasicTestApp", "DownloadThenInitTests", "App"); - BuildProject(config); - - var result = await RunSdkStyleAppForBuild(new(Configuration: config, TestScenario: "DownloadThenInit")); + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.WasmBasicTestApp, "DownloadThenInitTests"); + BuildProject(info, config); + BrowserRunOptions options = new(config, TestScenario: "DownloadThenInit"); + RunResult result = await RunForBuildWithDotnetRun(options); var resultTestOutput = result.TestOutput.ToList(); - int index = resultTestOutput.FindIndex(s => s == "download finished"); + int index = resultTestOutput.FindIndex(s => s.Contains("download finished")); Assert.True(index > 0); // number of fetched resources cannot be 0 var afterDownload = resultTestOutput.Skip(index + 1).Where(s => s.StartsWith("fetching")).ToList(); if (afterDownload.Count > 0) diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/BrowserHostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/BrowserHostRunner.cs deleted file mode 100644 index a27e9be8929082..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/HostRunner/BrowserHostRunner.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -namespace Wasm.Build.Tests; - -using System; -using System.IO; - -public class BrowserHostRunner : IHostRunner -{ - private static string? s_binaryPathArg; - private static string BinaryPathArg - { - get - { - if (s_binaryPathArg is null) - { - if (!string.IsNullOrEmpty(EnvironmentVariables.ChromePathForTests)) - { - if (!File.Exists(EnvironmentVariables.ChromePathForTests)) - throw new Exception($"Cannot find CHROME_PATH_FOR_TESTS={EnvironmentVariables.ChromePathForTests}"); - s_binaryPathArg = $" --browser-path=\"{EnvironmentVariables.ChromePathForTests}\""; - } - else - { - s_binaryPathArg = ""; - } - } - return s_binaryPathArg; - } - } - - - public string GetTestCommand() => "wasm test-browser"; - public string GetXharnessArgsWindowsOS(XHarnessArgsOptions options) => $"-v trace -b {options.host} --browser-arg=--lang={options.environmentLocale} --web-server-use-cop {BinaryPathArg}"; // Windows: chrome.exe --lang=locale - public string GetXharnessArgsOtherOS(XHarnessArgsOptions options) => $"-v trace -b {options.host} --locale={options.environmentLocale} --web-server-use-cop {BinaryPathArg}"; // Linux: LANGUAGE=locale ./chrome - public bool UseWasmConsoleOutput() => false; - public bool CanRunWBT() => true; -} diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/IHostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/IHostRunner.cs deleted file mode 100644 index 9e41c5fa76616a..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/HostRunner/IHostRunner.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -namespace Wasm.Build.Tests; - -public record XHarnessArgsOptions(string jsRelativePath, string environmentLocale, RunHost host); - -interface IHostRunner -{ - string GetTestCommand(); - string GetXharnessArgsWindowsOS(XHarnessArgsOptions options); - string GetXharnessArgsOtherOS(XHarnessArgsOptions options); - bool UseWasmConsoleOutput(); - bool CanRunWBT(); -} diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs deleted file mode 100644 index 4153a7326c441d..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -namespace Wasm.Build.Tests; - -using System; -using System.IO; -using System.Runtime.InteropServices; - -public class V8HostRunner : IHostRunner -{ - private static string? s_binaryPathArg; - private static string BinaryPathArg - { - get - { - if (s_binaryPathArg is null) - { - if (!string.IsNullOrEmpty(EnvironmentVariables.V8PathForTests)) - { - if (!File.Exists(EnvironmentVariables.V8PathForTests)) - throw new Exception($"Cannot find V8_PATH_FOR_TESTS={EnvironmentVariables.V8PathForTests}"); - s_binaryPathArg += $" --js-engine-path=\"{EnvironmentVariables.V8PathForTests}\""; - } - else - { - s_binaryPathArg = ""; - } - } - return s_binaryPathArg; - } - } - - private string GetXharnessArgs(string jsRelativePath) => $"--js-file={jsRelativePath} --engine=V8 -v trace --engine-arg=--module {BinaryPathArg}"; - - public string GetTestCommand() => "wasm test"; - public string GetXharnessArgsWindowsOS(XHarnessArgsOptions options) => GetXharnessArgs(options.jsRelativePath); - public string GetXharnessArgsOtherOS(XHarnessArgsOptions options) => GetXharnessArgs(options.jsRelativePath); - public bool UseWasmConsoleOutput() => true; - public bool CanRunWBT() => !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); -} diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index 02abd96db46c21..e3ecb5a48717d0 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -19,15 +19,14 @@ public class IcuShardingTests : IcuTestsBase public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - public static IEnumerable IcuExpectedAndMissingCustomShardTestData(string config) => - from templateType in templateTypes - from aot in boolOptions + public static IEnumerable IcuExpectedAndMissingCustomShardTestData(Configuration config) => + from aot in boolOptions from onlyPredefinedCultures in boolOptions // isOnlyPredefinedCultures = true fails with wasmbrowser: https://github.com/dotnet/runtime/issues/108272 - where !(onlyPredefinedCultures && templateType == "wasmbrowser") - select new object[] { config, templateType, aot, CustomIcuPath, s_customIcuTestedLocales, onlyPredefinedCultures }; + where !(onlyPredefinedCultures) + select new object[] { config, aot, CustomIcuPath, s_customIcuTestedLocales, onlyPredefinedCultures }; - public static IEnumerable IcuExpectedAndMissingAutomaticShardTestData(string config) + public static IEnumerable IcuExpectedAndMissingAutomaticShardTestData(Configuration config) { var locales = new Dictionary { @@ -37,16 +36,16 @@ public static IEnumerable IcuExpectedAndMissingAutomaticShardTestData( }; return from aot in boolOptions from locale in locales - select new object[] { config, "wasmbrowser", aot, locale.Key, locale.Value }; + select new object[] { config, aot, locale.Key, locale.Value }; } [Theory] - [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { "Release" })] - public async Task CustomIcuShard(string config, string templateType, bool aot, string customIcuPath, string customLocales, bool onlyPredefinedCultures) => - await TestIcuShards(config, templateType, aot, customIcuPath, customLocales, GlobalizationMode.Custom, onlyPredefinedCultures); + [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { Configuration.Release })] + public async Task CustomIcuShard(Configuration config, bool aot, string customIcuPath, string customLocales, bool onlyPredefinedCultures) => + await TestIcuShards(config, Template.WasmBrowser, aot, customIcuPath, customLocales, GlobalizationMode.Custom, onlyPredefinedCultures); [Theory] - [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { "Release" })] - public async Task AutomaticShardSelectionDependingOnEnvLocale(string config, string templateType, bool aot, string environmentLocale, string testedLocales) => - await BuildAndRunIcuTest(config, templateType, aot, testedLocales, GlobalizationMode.Sharded, language: environmentLocale); + [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { Configuration.Release })] + public async Task AutomaticShardSelectionDependingOnEnvLocale(Configuration config, bool aot, string environmentLocale, string testedLocales) => + await PublishAndRunIcuTest(config, Template.WasmBrowser, aot, testedLocales, GlobalizationMode.Sharded, locale: environmentLocale); } diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests2.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests2.cs index 8f7e8ad459332a..5e28d2c0627a14 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests2.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests2.cs @@ -19,7 +19,7 @@ public class IcuShardingTests2 : IcuTestsBase public IcuShardingTests2(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - public static IEnumerable IcuExpectedAndMissingShardFromRuntimePackTestData(string config) + public static IEnumerable IcuExpectedAndMissingShardFromRuntimePackTestData(Configuration config) { var locales = new Dictionary { @@ -31,14 +31,13 @@ public static IEnumerable IcuExpectedAndMissingShardFromRuntimePackTes { "icudt_no_CJK.dat", GetNocjkTestedLocales() } }; return - from templateType in templateTypes from aot in boolOptions from locale in locales - select new object[] { config, templateType, aot, locale.Key, locale.Value }; + select new object[] { config, aot, locale.Key, locale.Value }; } [Theory] - [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { "Release" })] - public async Task DefaultAvailableIcuShardsFromRuntimePack(string config, string templateType, bool aot, string shardName, string testedLocales) => - await TestIcuShards(config, templateType, aot, shardName, testedLocales, GlobalizationMode.Custom); + [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { Configuration.Release })] + public async Task DefaultAvailableIcuShardsFromRuntimePack(Configuration config, bool aot, string shardName, string testedLocales) => + await TestIcuShards(config, Template.WasmBrowser, aot, shardName, testedLocales, GlobalizationMode.Custom); } \ No newline at end of file diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuTests.cs index d3f808c87a5143..825f00e3b9b6f0 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuTests.cs @@ -19,13 +19,12 @@ public class IcuTests : IcuTestsBase public IcuTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - public static IEnumerable FullIcuWithICustomIcuTestData(string config) => - from templateType in templateTypes - from aot in boolOptions + public static IEnumerable FullIcuWithICustomIcuTestData(Configuration config) => + from aot in boolOptions from fullIcu in boolOptions - select new object[] { config, templateType, aot, fullIcu }; + select new object[] { config, aot, fullIcu }; - public static IEnumerable FullIcuWithInvariantTestData(string config) + public static IEnumerable FullIcuWithInvariantTestData(Configuration config) { var locales = new object[][] { @@ -35,31 +34,29 @@ public static IEnumerable FullIcuWithInvariantTestData(string config) new object[] { false, false, GetEfigsTestedLocales() }, new object[] { false, true, s_fullIcuTestedLocales } }; - return from templateType in templateTypes - from aot in boolOptions + return from aot in boolOptions from locale in locales - select new object[] { config, templateType, aot, locale[0], locale[1], locale[2] }; + select new object[] { config, aot, locale[0], locale[1], locale[2] }; } - public static IEnumerable IncorrectIcuTestData(string config) + public static IEnumerable IncorrectIcuTestData(Configuration config) { var customFiles = new Dictionary { { "icudtNonExisting.dat", true }, { "incorrectName.dat", false } }; - return from templateType in templateTypes - from customFile in customFiles - select new object[] { config, templateType, customFile.Key, customFile.Value }; + return from customFile in customFiles + select new object[] { config, customFile.Key, customFile.Value }; } [Theory] - [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { "Release" })] - public async Task FullIcuFromRuntimePackWithInvariant(string config, string templateType, bool aot, bool invariant, bool fullIcu, string testedLocales) => - await BuildAndRunIcuTest( + [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { Configuration.Release })] + public async Task FullIcuFromRuntimePackWithInvariant(Configuration config=Configuration.Release, bool aot=false, bool invariant=true, bool fullIcu=true, string testedLocales="Array.Empty()") => + await PublishAndRunIcuTest( config, - templateType, + Template.WasmBrowser, aot, testedLocales, globalizationMode: invariant ? GlobalizationMode.Invariant : fullIcu ? GlobalizationMode.FullIcu : GlobalizationMode.Sharded, @@ -68,38 +65,35 @@ await BuildAndRunIcuTest( $"{invariant}{fullIcu}{aot}"); [Theory] - [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { "Release" })] - public async Task FullIcuFromRuntimePackWithCustomIcu(string config, string templateType, bool aot, bool fullIcu) + [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { Configuration.Release })] + public async Task FullIcuFromRuntimePackWithCustomIcu(Configuration config, bool aot, bool fullIcu) { - bool isBrowser = templateType == "wasmbrowser"; - string customIcuProperty = isBrowser ? "BlazorIcuDataFileName" : "WasmIcuDataFileName"; - string fullIcuProperty = isBrowser ? "BlazorWebAssemblyLoadAllGlobalizationData" : "WasmIncludeFullIcuData"; + string customIcuProperty = "BlazorIcuDataFileName"; + string fullIcuProperty = "BlazorWebAssemblyLoadAllGlobalizationData"; string extraProperties = $"<{customIcuProperty}>{CustomIcuPath}<{fullIcuProperty}>{fullIcu}{aot}"; string testedLocales = fullIcu ? s_fullIcuTestedLocales : s_customIcuTestedLocales; GlobalizationMode globalizationMode = fullIcu ? GlobalizationMode.FullIcu : GlobalizationMode.Custom; string customIcuFile = fullIcu ? "" : CustomIcuPath; - string output = await BuildAndRunIcuTest(config, templateType, aot, testedLocales, globalizationMode, extraProperties, icuFileName: customIcuFile); + string output = await PublishAndRunIcuTest(config, Template.WasmBrowser, aot, testedLocales, globalizationMode, extraProperties, icuFileName: customIcuFile); if (fullIcu) Assert.Contains($"$({customIcuProperty}) has no effect when $({fullIcuProperty}) is set to true.", output); } [Theory] - [MemberData(nameof(IncorrectIcuTestData), parameters: new object[] { "Release" })] - public void NonExistingCustomFileAssertError(string config, string templateType, string customIcu, bool isFilenameFormCorrect) + [MemberData(nameof(IncorrectIcuTestData), parameters: new object[] { Configuration.Release })] + public void NonExistingCustomFileAssertError(Configuration config, string customIcu, bool isFilenameFormCorrect) { string customIcuProperty = "BlazorIcuDataFileName"; string extraProperties = $"<{customIcuProperty}>{customIcu}"; - (BuildArgs buildArgs, string projectFile) = CreateIcuProject( - config, templateType, aot: false, "Array.Empty()", extraProperties); - string output = BuildIcuTest( - buildArgs, - GlobalizationMode.Custom, - customIcu, - expectSuccess: false, - assertAppBundle: false); - + ProjectInfo info = CreateIcuProject(config, Template.WasmBrowser, aot: false, "Array.Empty()", extraProperties); + (string _, string output) = BuildProject(info, config, new BuildOptions( + GlobalizationMode: GlobalizationMode.Custom, + CustomIcuFile: customIcu, + ExpectSuccess: false, + AssertAppBundle: false + )); if (isFilenameFormCorrect) { Assert.Contains($"Could not find $({customIcuProperty})={customIcu}, or when used as a path relative to the runtime pack", output); diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/IcuTestsBase.cs index 25a03ad781dcec..1bf7e100314e5b 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuTestsBase.cs @@ -106,92 +106,59 @@ protected string GetProgramText(string testedLocales, bool onlyPredefinedCulture public record Locale(string Code, string? SundayName); "; - protected async Task TestIcuShards(string config, string templateType, bool aot, string shardName, string testedLocales, GlobalizationMode globalizationMode, bool onlyPredefinedCultures=false) + protected async Task TestIcuShards(Configuration config, Template templateType, bool aot, string shardName, string testedLocales, GlobalizationMode globalizationMode, bool onlyPredefinedCultures=false) { string icuProperty = "BlazorIcuDataFileName"; // https://github.com/dotnet/runtime/issues/94133 // by default, we remove resource strings from an app. ICU tests are checking exception messages contents -> resource string keys are not enough string extraProperties = $"<{icuProperty}>{shardName}false{aot}"; if (onlyPredefinedCultures) extraProperties = $"{extraProperties}true"; - await BuildAndRunIcuTest(config, templateType, aot, testedLocales, globalizationMode, extraProperties, onlyPredefinedCultures, icuFileName: shardName); + await PublishAndRunIcuTest(config, templateType, aot, testedLocales, globalizationMode, extraProperties, onlyPredefinedCultures, icuFileName: shardName); } - protected (BuildArgs buildArgs, string projectFile) CreateIcuProject( - string config, - string templateType, + protected ProjectInfo CreateIcuProject( + Configuration config, + Template templateType, bool aot, string testedLocales, string extraProperties = "", bool onlyPredefinedCultures=false) { - string id = $"icu_{config}_{aot}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, templateType); - string projectDirectory = Path.GetDirectoryName(projectFile)!; - string projectName = Path.GetFileNameWithoutExtension(projectFile); - var buildArgs = new BuildArgs(projectName, config, aot, id, null); - buildArgs = ExpandBuildArgs(buildArgs); - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); - + ProjectInfo info = CreateWasmTemplateProject(templateType, config, aot, "icu", extraProperties: extraProperties); + string projectDirectory = Path.GetDirectoryName(info.ProjectFilePath)!; string programPath = Path.Combine(projectDirectory, "Program.cs"); string programText = GetProgramText(testedLocales, onlyPredefinedCultures); File.WriteAllText(programPath, programText); _testOutput.WriteLine($"----- Program: -----{Environment.NewLine}{programText}{Environment.NewLine}-------"); - - string mainPath = Path.Combine("wwwroot", "main.js"); - var replacements = new Dictionary { - { "runMain", "runMainAndExit" }, - { ".create()", ".withConsoleForwarding().withElementOnExit().withExitCodeLogging().create()" } - }; - UpdateFile(mainPath, replacements); - RemoveContentsFromProjectFile(mainPath, ".create();", "await runMainAndExit();"); - return (buildArgs, projectFile); - } - - protected string BuildIcuTest( - BuildArgs buildArgs, - GlobalizationMode globalizationMode, - string icuFileName = "", - bool expectSuccess = true, - bool assertAppBundle = true) - { - bool dotnetWasmFromRuntimePack = IsDotnetWasmFromRuntimePack(buildArgs); - (string _, string buildOutput) = BuildTemplateProject(buildArgs, - id: buildArgs.Id, - new BuildProjectOptions( - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, - CreateProject: false, - HasV8Script: false, - MainJS: "main.js", - Publish: true, - TargetFramework: BuildTestBase.DefaultTargetFramework, - UseCache: false, - IsBrowserProject: true, - GlobalizationMode: globalizationMode, - CustomIcuFile: icuFileName, - ExpectSuccess: expectSuccess, - AssertAppBundle: assertAppBundle - )); - return buildOutput; + + UpdateBrowserMainJs(); + return info; } - protected async Task BuildAndRunIcuTest( - string config, - string templateType, + protected async Task PublishAndRunIcuTest( + Configuration config, + Template templateType, bool aot, string testedLocales, GlobalizationMode globalizationMode, string extraProperties = "", bool onlyPredefinedCultures=false, - string language = "en-US", + string locale = "en-US", string icuFileName = "") { try { - (BuildArgs buildArgs, string projectFile) = CreateIcuProject( + ProjectInfo info = CreateIcuProject( config, templateType, aot, testedLocales, extraProperties, onlyPredefinedCultures); - string buildOutput = BuildIcuTest(buildArgs, globalizationMode, icuFileName); - string runOutput = await RunBuiltBrowserApp(buildArgs.Config, projectFile, language); - return $"{buildOutput}\n{runOutput}"; + bool triggersNativeBuild = globalizationMode == GlobalizationMode.Invariant; + (string _, string buildOutput) = PublishProject(info, + config, + new PublishOptions(GlobalizationMode: globalizationMode, CustomIcuFile: icuFileName), + isNativeBuild: triggersNativeBuild ? true : null); + + BrowserRunOptions runOptions = new(config, Locale: locale, ExpectedExitCode: 42); + RunResult runOutput = await RunForPublishWithWebServer(runOptions); + return $"{buildOutput}\n{runOutput.TestOutput}"; } catch(Exception ex) { diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/InterpPgoTests.cs b/src/mono/wasm/Wasm.Build.Tests/InterpPgoTests.cs similarity index 92% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/InterpPgoTests.cs rename to src/mono/wasm/Wasm.Build.Tests/InterpPgoTests.cs index 9399ee93ec87ce..4af6f409aeedb7 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/InterpPgoTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InterpPgoTests.cs @@ -13,9 +13,9 @@ #nullable enable -namespace Wasm.Build.Tests.TestAppScenarios; +namespace Wasm.Build.Tests; -public class InterpPgoTests : AppTestBase +public class InterpPgoTests : WasmTemplateTestsBase { public InterpPgoTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -25,28 +25,28 @@ public InterpPgoTests(ITestOutputHelper output, SharedBuildPerTestClassFixture b [Theory] // Interpreter PGO is not meaningful to enable in debug builds - tiering is inactive there so all methods // would get added to the PGO table instead of just hot ones. - [InlineData("Release")] + [InlineData(Configuration.Release)] [ActiveIssue("https://github.com/dotnet/runtime/issues/105733")] - public async Task FirstRunGeneratesTableAndSecondRunLoadsIt(string config) + public async Task FirstRunGeneratesTableAndSecondRunLoadsIt(Configuration config) { // We need to invoke Greeting enough times to cause BCL code to tier so we can exercise interpreter PGO // Invoking it too many times makes the test meaningfully slower. const int iterationCount = 70; _testOutput.WriteLine("/// Creating project"); - CopyTestAsset("WasmBasicTestApp", "InterpPgoTest", "App"); + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "InterpPgoTest"); _testOutput.WriteLine("/// Building"); - BuildProject(config, extraArgs: "-p:WasmDebugLevel=0"); + BuildProject(info, config, new BuildOptions(ExtraMSBuildArgs: "-p:WasmDebugLevel=0")); _testOutput.WriteLine("/// Starting server"); // Create a single browser instance and single context to host all our pages. // If we don't do this, each page will have its own unique cache and the table won't be loaded. using var runCommand = new RunCommand(s_buildEnv, _testOutput) - .WithWorkingDirectory(_projectDir!); + .WithWorkingDirectory(_projectDir); await using var runner = new BrowserRunner(_testOutput); - var url = await runner.StartServerAndGetUrlAsync(runCommand, $"run --no-silent -c {config} --no-build --project \"{_projectDir!}\" --forward-console"); + var url = await runner.StartServerAndGetUrlAsync(runCommand, $"run --no-silent -c {config} --no-build --project \"{_projectDir}\" --forward-console"); url = $"{url}?test=InterpPgoTest&iterationCount={iterationCount}"; _testOutput.WriteLine($"/// Spawning browser at URL {url}"); diff --git a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs index 212d2205987dd9..28253df6cea2c2 100644 --- a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -10,68 +12,66 @@ namespace Wasm.Build.Tests { - public class InvariantGlobalizationTests : TestMainJsTestBase + public class InvariantGlobalizationTests : WasmTemplateTestsBase { public InvariantGlobalizationTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - public static IEnumerable InvariantGlobalizationTestData(bool aot, RunHost host) + public static IEnumerable InvariantGlobalizationTestData(bool aot) => ConfigWithAOTData(aot) .Multiply( new object?[] { null }, new object?[] { false }, new object?[] { true }) - .WithRunHosts(host) + .Where(item => !(item.ElementAt(0) is Configuration config && config == Configuration.Debug && item.ElementAt(1) is bool aotValue && aotValue)) .UnwrapItemsAsArrays(); // TODO: check that icu bits have been linked out [Theory] - [MemberData(nameof(InvariantGlobalizationTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - [MemberData(nameof(InvariantGlobalizationTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - public void AOT_InvariantGlobalization(BuildArgs buildArgs, bool? invariantGlobalization, RunHost host, string id) - => TestInvariantGlobalization(buildArgs, invariantGlobalization, host, id); + [MemberData(nameof(InvariantGlobalizationTestData), parameters: new object[] { /*aot*/ false })] + [MemberData(nameof(InvariantGlobalizationTestData), parameters: new object[] { /*aot*/ true })] + public async Task AOT_InvariantGlobalization(Configuration config, bool aot, bool? invariantGlobalization) + => await TestInvariantGlobalization(config, aot, invariantGlobalization); // TODO: What else should we use to verify a relinked build? [Theory] - [MemberData(nameof(InvariantGlobalizationTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - public void RelinkingWithoutAOT(BuildArgs buildArgs, bool? invariantGlobalization, RunHost host, string id) - => TestInvariantGlobalization(buildArgs, invariantGlobalization, host, id, - extraProperties: "true", - dotnetWasmFromRuntimePack: false); + [MemberData(nameof(InvariantGlobalizationTestData), parameters: new object[] { /*aot*/ false })] + public async Task RelinkingWithoutAOT(Configuration config, bool aot, bool? invariantGlobalization) + => await TestInvariantGlobalization(config, aot, invariantGlobalization, isNativeBuild: true); - private void TestInvariantGlobalization(BuildArgs buildArgs, bool? invariantGlobalization, - RunHost host, string id, string extraProperties="", bool? dotnetWasmFromRuntimePack=null) + private async Task TestInvariantGlobalization(Configuration config, bool aot, bool? invariantGlobalization, bool? isNativeBuild = null) { - string projectName = $"invariant_{invariantGlobalization?.ToString() ?? "unset"}"; + string extraProperties = isNativeBuild == true ? "true" : ""; if (invariantGlobalization != null) + { extraProperties = $"{extraProperties}{invariantGlobalization}"; + } + if (invariantGlobalization == true) + { + if (isNativeBuild == false) + throw new System.ArgumentException("InvariantGlobalization=true requires a native build"); + // -p:InvariantGlobalization=true triggers native build, isNativeBuild is not undefined anymore + isNativeBuild = true; + } - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, extraProperties); - - if (dotnetWasmFromRuntimePack == null) - dotnetWasmFromRuntimePack = IsDotnetWasmFromRuntimePack(buildArgs); + string prefix = $"invariant_{invariantGlobalization?.ToString() ?? "unset"}"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, prefix, extraProperties: extraProperties); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "InvariantGlobalization.cs")); - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "Wasm.Buid.Tests.Programs", "InvariantGlobalization.cs"), Path.Combine(_projectDir!, "Program.cs")), - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, - GlobalizationMode: invariantGlobalization == true ? GlobalizationMode.Invariant : GlobalizationMode.Sharded)); + var globalizationMode = invariantGlobalization == true ? GlobalizationMode.Invariant : GlobalizationMode.Sharded; + PublishProject(info, config, new PublishOptions(GlobalizationMode: globalizationMode, AOT: aot), isNativeBuild: isNativeBuild); + RunResult output = await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 42)); if (invariantGlobalization == true) { - string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Could not create es-ES culture", output); - Assert.Contains("CurrentCulture.NativeName: Invariant Language (Invariant Country)", output); + Assert.Contains(output.TestOutput, m => m.Contains("Could not create es-ES culture")); + Assert.Contains(output.TestOutput, m => m.Contains("CurrentCulture.NativeName: Invariant Language (Invariant Country)")); } else { - string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id, args: "nativename=\"espa\u00F1ol (Espa\u00F1a)\""); - Assert.Contains("es-ES: Is Invariant LCID: False", output); - + Assert.Contains(output.TestOutput, m => m.Contains("es-ES: Is Invariant LCID: False")); // ignoring the last line of the output which prints the current culture } } diff --git a/src/mono/wasm/Wasm.Build.Tests/InvariantTimezoneTests.cs b/src/mono/wasm/Wasm.Build.Tests/InvariantTimezoneTests.cs index 75a1af9cd3caff..d515dfea8312ae 100644 --- a/src/mono/wasm/Wasm.Build.Tests/InvariantTimezoneTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InvariantTimezoneTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -10,63 +11,61 @@ namespace Wasm.Build.Tests { - public class InvariantTimezoneTests : TestMainJsTestBase + public class InvariantTimezoneTests : WasmTemplateTestsBase { public InvariantTimezoneTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - public static IEnumerable InvariantTimezoneTestData(bool aot, RunHost host) + public static IEnumerable InvariantTimezoneTestData(bool aot) => ConfigWithAOTData(aot) .Multiply( new object?[] { null }, new object?[] { false }, new object?[] { true }) - .WithRunHosts(host) .UnwrapItemsAsArrays(); [Theory] - [MemberData(nameof(InvariantTimezoneTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - [MemberData(nameof(InvariantTimezoneTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - public void AOT_InvariantTimezone(BuildArgs buildArgs, bool? invariantTimezone, RunHost host, string id) - => TestInvariantTimezone(buildArgs, invariantTimezone, host, id); + [MemberData(nameof(InvariantTimezoneTestData), parameters: new object[] { /*aot*/ false, })] + [MemberData(nameof(InvariantTimezoneTestData), parameters: new object[] { /*aot*/ true })] + public async Task AOT_InvariantTimezone(Configuration config, bool aot, bool? invariantTimezone) + => await TestInvariantTimezone(config, aot, invariantTimezone); [Theory] - [MemberData(nameof(InvariantTimezoneTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - public void RelinkingWithoutAOT(BuildArgs buildArgs, bool? invariantTimezone, RunHost host, string id) - => TestInvariantTimezone(buildArgs, invariantTimezone, host, id, - extraProperties: "true", - dotnetWasmFromRuntimePack: false); + [MemberData(nameof(InvariantTimezoneTestData), parameters: new object[] { /*aot*/ false })] + public async Task RelinkingWithoutAOT(Configuration config, bool aot, bool? invariantTimezone) + => await TestInvariantTimezone(config, aot, invariantTimezone, isNativeBuild: true); - private void TestInvariantTimezone(BuildArgs buildArgs, bool? invariantTimezone, - RunHost host, string id, string extraProperties="", bool? dotnetWasmFromRuntimePack=null) + private async Task TestInvariantTimezone(Configuration config, bool aot, bool? invariantTimezone, bool? isNativeBuild = null) { - string projectName = $"invariant_{invariantTimezone?.ToString() ?? "unset"}"; + string extraProperties = isNativeBuild == true ? "true" : ""; if (invariantTimezone != null) + { extraProperties = $"{extraProperties}{invariantTimezone}"; + } + if (invariantTimezone == true) + { + if (isNativeBuild == false) + throw new System.ArgumentException("InvariantTimezone=true requires a native build"); + // -p:InvariantTimezone=true triggers native build, isNativeBuild is not undefined anymore + isNativeBuild = true; + } - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, extraProperties); - - if (dotnetWasmFromRuntimePack == null) - dotnetWasmFromRuntimePack = IsDotnetWasmFromRuntimePack(buildArgs); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "Wasm.Buid.Tests.Programs", "InvariantTimezone.cs"), Path.Combine(_projectDir!, "Program.cs")), - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack)); + string prefix = $"invariant_{invariantTimezone?.ToString() ?? "unset"}"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, prefix, extraProperties: extraProperties); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "InvariantTimezone.cs")); + PublishProject(info, config, isNativeBuild: isNativeBuild); - string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id); - Assert.Contains("UTC BaseUtcOffset is 0", output); + RunResult output = await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 42)); + Assert.Contains(output.TestOutput, m => m.Contains("UTC BaseUtcOffset is 0")); if (invariantTimezone == true) { - Assert.Contains("Could not find Asia/Tokyo", output); + Assert.Contains(output.TestOutput, m => m.Contains("Could not find Asia/Tokyo")); } else { - Assert.Contains("Asia/Tokyo BaseUtcOffset is 09:00:00", output); + Assert.Contains(output.TestOutput, m => m.Contains("Asia/Tokyo BaseUtcOffset is 09:00:00")); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs similarity index 60% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs rename to src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs index eb98b3d6f6e0a3..65011962374221 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -11,9 +12,9 @@ #nullable enable -namespace Wasm.Build.Tests.TestAppScenarios; +namespace Wasm.Build.Tests; -public class LazyLoadingTests : AppTestBase +public class LazyLoadingTests : WasmTemplateTestsBase { public LazyLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -30,17 +31,18 @@ public LazyLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture [MemberData(nameof(LoadLazyAssemblyBeforeItIsNeededData))] public async Task LoadLazyAssemblyBeforeItIsNeeded(string lazyLoadingTestExtension, string[] allLazyLoadingTestExtensions) { - CopyTestAsset("WasmBasicTestApp", "LazyLoadingTests", "App"); - BuildProject("Debug", extraArgs: $"-p:LazyLoadingTestExtension={lazyLoadingTestExtension}"); + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "LazyLoadingTests"); + BuildProject(info, config, new BuildOptions(ExtraMSBuildArgs: $"-p:LazyLoadingTestExtension={lazyLoadingTestExtension} -p:TestLazyLoading=true")); // We are running the app and passing all possible lazy extensions to test matrix of all possibilities. // We don't need to rebuild the application to test how client is trying to load the assembly. foreach (var clientLazyLoadingTestExtension in allLazyLoadingTestExtensions) { - var result = await RunSdkStyleAppForBuild(new( - Configuration: "Debug", - TestScenario: "LazyLoadingTest", - BrowserQueryString: new Dictionary { ["lazyLoadingTestExtension"] = clientLazyLoadingTestExtension } + RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions( + config, + TestScenario: "LazyLoadingTest", + BrowserQueryString: new NameValueCollection { {"lazyLoadingTestExtension", clientLazyLoadingTestExtension } } )); Assert.True(result.TestOutput.Any(m => m.Contains("FirstName")), "The lazy loading test didn't emit expected message with JSON"); @@ -51,15 +53,16 @@ public async Task LoadLazyAssemblyBeforeItIsNeeded(string lazyLoadingTestExtensi [Fact] public async Task FailOnMissingLazyAssembly() { - CopyTestAsset("WasmBasicTestApp", "LazyLoadingTests", "App"); - PublishProject("Debug"); + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "LazyLoadingTests"); - var result = await RunSdkStyleAppForPublish(new( - Configuration: "Debug", + PublishProject(info, config, new PublishOptions(ExtraMSBuildArgs: "-p:TestLazyLoading=true")); + BrowserRunOptions options = new( + config, TestScenario: "LazyLoadingTest", - BrowserQueryString: new Dictionary { ["loadRequiredAssembly"] = "false" }, - ExpectedExitCode: 1 - )); + BrowserQueryString: new NameValueCollection { {"loadRequiredAssembly", "false" } }, + ExpectedExitCode: 1); + RunResult result = await RunForPublishWithWebServer(options); Assert.True(result.ConsoleOutput.Any(m => m.Contains("Could not load file or assembly") && m.Contains("Json")), "The lazy loading test didn't emit expected error message"); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs b/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs similarity index 57% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs rename to src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs index 5e351203596d43..b60bf5c858f31b 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Text; @@ -14,9 +15,9 @@ #nullable enable -namespace Wasm.Build.Tests.TestAppScenarios; +namespace Wasm.Build.Tests; -public partial class LibraryInitializerTests : AppTestBase +public partial class LibraryInitializerTests : WasmTemplateTestsBase { public LibraryInitializerTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -26,10 +27,10 @@ public LibraryInitializerTests(ITestOutputHelper output, SharedBuildPerTestClass [Fact] public async Task LoadLibraryInitializer() { - CopyTestAsset("WasmBasicTestApp", "LibraryInitializerTests_LoadLibraryInitializer", "App"); - PublishProject("Debug"); - - var result = await RunSdkStyleAppForPublish(new(Configuration: "Debug", TestScenario: "LibraryInitializerTest")); + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "LibraryInitializerTests_LoadLibraryInitializer"); + PublishProject(info, config); + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "LibraryInitializerTest")); Assert.Collection( result.TestOutput, m => Assert.Equal("LIBRARY_INITIALIZER_TEST = 1", m) @@ -42,15 +43,16 @@ public async Task LoadLibraryInitializer() [Fact] public async Task AbortStartupOnError() { - CopyTestAsset("WasmBasicTestApp", "LibraryInitializerTests_AbortStartupOnError", "App"); - PublishProject("Debug"); + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "LibraryInitializerTests_AbortStartupOnError"); + PublishProject(info, config); - var result = await RunSdkStyleAppForPublish(new( - Configuration: "Debug", + BrowserRunOptions options = new( + config, TestScenario: "LibraryInitializerTest", - BrowserQueryString: new Dictionary { ["throwError"] = "true" }, - ExpectedExitCode: 1 - )); + BrowserQueryString: new NameValueCollection { {"throwError", "true" } }, + ExpectedExitCode: 1); + RunResult result = await RunForPublishWithWebServer(options); Assert.True(result.ConsoleOutput.Any(m => AbortStartupOnErrorRegex().IsMatch(m)), "The library initializer test didn't emit expected error message"); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/MainWithArgsTests.cs b/src/mono/wasm/Wasm.Build.Tests/MainWithArgsTests.cs index 5fb7887e962cfa..83e79e6955dddf 100644 --- a/src/mono/wasm/Wasm.Build.Tests/MainWithArgsTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/MainWithArgsTests.cs @@ -3,7 +3,10 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; +using System.Linq; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -11,95 +14,53 @@ namespace Wasm.Build.Tests { - public class MainWithArgsTests : TestMainJsTestBase + public class MainWithArgsTests : WasmTemplateTestsBase { public MainWithArgsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - public static IEnumerable MainWithArgsTestData(bool aot, RunHost host) + public static IEnumerable MainWithArgsTestData(bool aot) => ConfigWithAOTData(aot).Multiply( new object?[] { new object?[] { "abc", "foobar"} }, - new object?[] { new object?[0] } - ).WithRunHosts(host).UnwrapItemsAsArrays(); + new object?[] { new object?[0] }) + .Where(item => !(item.ElementAt(0) is Configuration config && config == Configuration.Debug && item.ElementAt(1) is bool aotValue && aotValue)) + .UnwrapItemsAsArrays(); [Theory] - [MemberData(nameof(MainWithArgsTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - [MemberData(nameof(MainWithArgsTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - public void AsyncMainWithArgs(BuildArgs buildArgs, string[] args, RunHost host, string id) - => TestMainWithArgs("async_main_with_args", @" - public class TestClass { - public static async System.Threading.Tasks.Task Main(string[] args) - { - ##CODE## - return await System.Threading.Tasks.Task.FromResult(42 + count); - } - }", - buildArgs, args, host, id); + [MemberData(nameof(MainWithArgsTestData), parameters: new object[] { /*aot*/ false })] + [MemberData(nameof(MainWithArgsTestData), parameters: new object[] { /*aot*/ true })] + public async Task AsyncMainWithArgs(Configuration config, bool aot, string[] args) + => await TestMainWithArgs(config, aot, "async_main_with_args", "AsyncMainWithArgs.cs", args); [Theory] - [MemberData(nameof(MainWithArgsTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - [MemberData(nameof(MainWithArgsTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - public void NonAsyncMainWithArgs(BuildArgs buildArgs, string[] args, RunHost host, string id) - => TestMainWithArgs("non_async_main_args", @" - public class TestClass { - public static int Main(string[] args) - { - ##CODE## - return 42 + count; - } - }", buildArgs, args, host, id); + [MemberData(nameof(MainWithArgsTestData), parameters: new object[] { /*aot*/ false })] + [MemberData(nameof(MainWithArgsTestData), parameters: new object[] { /*aot*/ true })] + public async Task NonAsyncMainWithArgs(Configuration config, bool aot, string[] args) + => await TestMainWithArgs(config, aot, "non_async_main_args", "SyncMainWithArgs.cs", args); - void TestMainWithArgs(string projectNamePrefix, - string projectContents, - BuildArgs buildArgs, - string[] args, - RunHost host, - string id, - bool? dotnetWasmFromRuntimePack=null) + async Task TestMainWithArgs(Configuration config, + bool aot, + string projectNamePrefix, + string projectContentsName, + string[] args) { - string projectName = $"{projectNamePrefix}_{buildArgs.Config}_{buildArgs.AOT}"; - string code = @" - int count = args == null ? 0 : args.Length; - System.Console.WriteLine($""args#: {args?.Length}""); - foreach (var arg in args ?? System.Array.Empty()) - System.Console.WriteLine($""arg: {arg}""); - "; - string programText = projectContents.Replace("##CODE##", code); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, projectNamePrefix); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", projectContentsName)); - buildArgs = buildArgs with { ProjectName = projectName, ProjectFileContents = programText }; - buildArgs = ExpandBuildArgs(buildArgs); - if (dotnetWasmFromRuntimePack == null) - dotnetWasmFromRuntimePack = IsDotnetWasmFromRuntimePack(buildArgs); + var queryArgs = new NameValueCollection(); + foreach (var arg in args) + queryArgs.Add("arg", arg); + PublishProject(info, config, new PublishOptions(AOT: aot)); - _testOutput.WriteLine ($"-- args: {buildArgs}, name: {projectName}"); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack)); - - // Because we get extra "-verbosity", "Debug" from XHarness int argsCount = args.Length; - bool isBrowser = host == RunHost.Chrome || host == RunHost.Firefox || host == RunHost.Safari; - if (isBrowser) - argsCount += 2; - - RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42 + argsCount, args: string.Join(' ', args), - test: output => - { - Assert.Contains($"args#: {argsCount}", output); - foreach (var arg in args) - Assert.Contains($"arg: {arg}", output); - - if (isBrowser) - { - Assert.Contains($"arg: -verbosity", output); - Assert.Contains($"arg: Debug", output); - } - }, host: host, id: id); + int expectedCode = 42 + argsCount; + RunResult output = await RunForPublishWithWebServer( + new BrowserRunOptions(config, TestScenario: "MainWithArgs", BrowserQueryString: queryArgs, ExpectedExitCode: expectedCode)); + Assert.Contains(output.TestOutput, m => m.Contains($"args#: {argsCount}")); + foreach (var arg in args) + Assert.Contains(output.TestOutput, m => m.Contains($"arg: {arg}")); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MaxParallelDownloadsTests.cs b/src/mono/wasm/Wasm.Build.Tests/MaxParallelDownloadsTests.cs similarity index 61% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MaxParallelDownloadsTests.cs rename to src/mono/wasm/Wasm.Build.Tests/MaxParallelDownloadsTests.cs index d9ae89ef0c6e5a..5c7c13c9b7db2a 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MaxParallelDownloadsTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/MaxParallelDownloadsTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using System.Threading.Tasks; using Xunit.Abstractions; @@ -11,9 +12,9 @@ #nullable enable -namespace Wasm.Build.Tests.TestAppScenarios; +namespace Wasm.Build.Tests; -public class MaxParallelDownloadsTests : AppTestBase +public class MaxParallelDownloadsTests : WasmTemplateTestsBase { public MaxParallelDownloadsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -21,18 +22,18 @@ public MaxParallelDownloadsTests(ITestOutputHelper output, SharedBuildPerTestCla } [Theory] - [InlineData("Release", "1")] - [InlineData("Release", "4")] - public async Task NeverFetchMoreThanMaxAllowed(string config, string maxParallelDownloads) + [InlineData(Configuration.Release, "1")] + [InlineData(Configuration.Release, "4")] + public async Task NeverFetchMoreThanMaxAllowed(Configuration config, string maxParallelDownloads) { - CopyTestAsset("WasmBasicTestApp", "MaxParallelDownloadsTests", "App"); - BuildProject(config); - - var result = await RunSdkStyleAppForBuild(new( - Configuration: config, + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "MaxParallelDownloadsTests"); + BuildProject(info, config); + RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions( + config, TestScenario: "MaxParallelDownloads", - BrowserQueryString: new Dictionary { ["maxParallelDownloads"] = maxParallelDownloads } + BrowserQueryString: new NameValueCollection { {"maxParallelDownloads", maxParallelDownloads } } )); + var resultTestOutput = result.TestOutput.ToList(); var regex = new Regex(@"Active downloads: (\d+)"); foreach (var line in resultTestOutput) diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MemoryTests.cs b/src/mono/wasm/Wasm.Build.Tests/MemoryTests.cs similarity index 62% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MemoryTests.cs rename to src/mono/wasm/Wasm.Build.Tests/MemoryTests.cs index d0589c04f1ddd1..39d7f5be61c5d8 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MemoryTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/MemoryTests.cs @@ -11,9 +11,9 @@ #nullable enable -namespace Wasm.Build.Tests.TestAppScenarios; +namespace Wasm.Build.Tests; -public class MemoryTests : AppTestBase +public class MemoryTests : WasmTemplateTestsBase { public MemoryTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -28,27 +28,34 @@ public async Task AllocateLargeHeapThenRepeatedlyInterop_NoWorkload() => [Fact] public async Task AllocateLargeHeapThenRepeatedlyInterop() { - string config = "Release"; - CopyTestAsset("WasmBasicTestApp", "MemoryTests", "App"); + Configuration config = Configuration.Release; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "MemoryTests"); string extraArgs = "-p:EmccMaximumHeapSize=4294901760"; - BuildProject(config, assertAppBundle: false, extraArgs: extraArgs, expectSuccess: BuildTestBase.IsUsingWorkloads); + BuildProject(info, + config, + new BuildOptions(ExtraMSBuildArgs: extraArgs, ExpectSuccess: BuildTestBase.IsUsingWorkloads), + // using EmccMaximumHeapSize forces native rebuild + isNativeBuild: true); if (BuildTestBase.IsUsingWorkloads) { - await RunSdkStyleAppForBuild(new (Configuration: config, TestScenario: "AllocateLargeHeapThenInterop")); + await RunForBuildWithDotnetRun(new BrowserRunOptions( + Configuration: config, + TestScenario: "AllocateLargeHeapThenInterop" + )); } } [Fact] public async Task RunSimpleAppWithProfiler() { - string config = "Release"; - CopyTestAsset("WasmBasicTestApp", "ProfilerTest", "App"); + Configuration config = Configuration.Release; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "ProfilerTest"); // are are linking all 3 profilers, but below we only initialize log profiler and test it string extraArgs = $"-p:WasmProfilers=\"aot+browser+log\" -p:WasmBuildNative=true"; - BuildProject(config, assertAppBundle: false, extraArgs: extraArgs); + BuildProject(info, config, new BuildOptions(ExtraMSBuildArgs: extraArgs, AssertAppBundle: false), isNativeBuild: true); - var result = await RunSdkStyleAppForBuild(new (Configuration: config, TestScenario: "ProfilerTest")); + var result = await RunForBuildWithDotnetRun(new BrowserRunOptions(Configuration: config, TestScenario: "ProfilerTest")); Regex regex = new Regex(@"Profile data of size (\d+) bytes"); var match = result.TestOutput .Select(line => regex.Match(line)) diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/ModuleConfigTests.cs b/src/mono/wasm/Wasm.Build.Tests/ModuleConfigTests.cs similarity index 58% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/ModuleConfigTests.cs rename to src/mono/wasm/Wasm.Build.Tests/ModuleConfigTests.cs index e5abf407dbd9d7..a825a1dadbac92 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/ModuleConfigTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ModuleConfigTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -11,9 +12,9 @@ #nullable enable -namespace Wasm.Build.Tests.TestAppScenarios; +namespace Wasm.Build.Tests; -public class ModuleConfigTests : AppTestBase +public class ModuleConfigTests : WasmTemplateTestsBase { public ModuleConfigTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -25,13 +26,14 @@ public ModuleConfigTests(ITestOutputHelper output, SharedBuildPerTestClassFixtur [InlineData(true)] public async Task DownloadProgressFinishes(bool failAssemblyDownload) { - CopyTestAsset("WasmBasicTestApp", $"ModuleConfigTests_DownloadProgressFinishes_{failAssemblyDownload}", "App"); - PublishProject("Debug"); + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, $"ModuleConfigTests_DownloadProgressFinishes_{failAssemblyDownload}"); + PublishProject(info, config); - var result = await RunSdkStyleAppForPublish(new( - Configuration: "Debug", + var result = await RunForPublishWithWebServer(new BrowserRunOptions( + Configuration: config, TestScenario: "DownloadResourceProgressTest", - BrowserQueryString: new Dictionary { ["failAssemblyDownload"] = failAssemblyDownload.ToString().ToLowerInvariant() } + BrowserQueryString: new NameValueCollection { {"failAssemblyDownload", failAssemblyDownload.ToString().ToLowerInvariant() } } )); Assert.True( result.TestOutput.Any(m => m.Contains("DownloadResourceProgress: Finished")), @@ -58,11 +60,12 @@ public async Task DownloadProgressFinishes(bool failAssemblyDownload) [Fact] public async Task OutErrOverrideWorks() { - CopyTestAsset("WasmBasicTestApp", $"ModuleConfigTests_OutErrOverrideWorks", "App"); - PublishProject("Debug"); + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "ModuleConfigTests_OutErrOverrideWorks"); + PublishProject(info, config); - var result = await RunSdkStyleAppForPublish(new( - Configuration: "Debug", + var result = await RunForPublishWithWebServer(new BrowserRunOptions( + Configuration: Configuration.Debug, TestScenario: "OutErrOverrideWorks" )); Assert.True( @@ -76,25 +79,27 @@ public async Task OutErrOverrideWorks() } [Theory] - [InlineData("Release", true)] - [InlineData("Release", false)] - public async Task OverrideBootConfigName(string config, bool isPublish) + [InlineData(Configuration.Release, true)] + [InlineData(Configuration.Release, false)] + public async Task OverrideBootConfigName(Configuration config, bool isPublish) { - CopyTestAsset("WasmBasicTestApp", $"OverrideBootConfigName", "App"); + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "OverrideBootConfigName"); + (string _, string _) = isPublish ? + PublishProject(info, config) : + BuildProject(info, config); - string[] extraArgs = ["-p:WasmBootConfigFileName=boot.json"]; - if (isPublish) - PublishProject(config, bootConfigFileName: "boot.json", extraArgs: extraArgs); - else - BuildProject(config, bootConfigFileName: "boot.json", extraArgs: extraArgs); + string extraArgs = "-p:WasmBootConfigFileName=boot.json"; + (string _, string _) = isPublish ? + PublishProject(info, config, new PublishOptions(BootConfigFileName: "boot.json", UseCache: false, ExtraMSBuildArgs: extraArgs)) : + BuildProject(info, config, new BuildOptions(BootConfigFileName: "boot.json", UseCache: false, ExtraMSBuildArgs: extraArgs)); - var runOptions = new RunOptions( + var runOptions = new BrowserRunOptions( Configuration: config, TestScenario: "OverrideBootConfigName" ); var result = await (isPublish - ? RunSdkStyleAppForPublish(runOptions) - : RunSdkStyleAppForBuild(runOptions) + ? RunForPublishWithWebServer(runOptions) + : RunForBuildWithDotnetRun(runOptions) ); Assert.Collection( diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs b/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs index f7c7483a949d3f..87b6c0cf0929ec 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Data; using System.IO; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; @@ -12,7 +13,7 @@ namespace Wasm.Build.Tests { - public class NativeBuildTests : TestMainJsTestBase + public class NativeBuildTests : WasmTemplateTestsBase { public NativeBuildTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -20,48 +21,44 @@ public NativeBuildTests(ITestOutputHelper output, SharedBuildPerTestClassFixture } [Theory] - [BuildAndRun] - public void SimpleNativeBuild(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun(aot: false)] + public async Task SimpleNativeBuild(Configuration config, bool aot) { - string projectName = $"simple_native_build_{buildArgs.Config}_{buildArgs.AOT}"; - - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, extraProperties: "true"); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: false)); - - RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, - test: output => { }, - host: host, id: id); + ProjectInfo info = CreateWasmTemplateProject( + Template.WasmBrowser, + config, + aot, + "simple_native_build", + extraProperties: "true"); + + UpdateBrowserProgramFile(); + UpdateBrowserMainJs(); + + (string _, string buildOutput) = PublishProject(info, config, isNativeBuild: true); + await RunForPublishWithWebServer(new BrowserRunOptions(config, ExpectedExitCode: 42)); } [Theory] - [BuildAndRun(aot: true, host: RunHost.None)] - public void AOTNotSupportedWithNoTrimming(BuildArgs buildArgs, string id) + [BuildAndRun(aot: true)] + public void AOTNotSupportedWithNoTrimming(Configuration config, bool aot) { - string projectName = $"mono_aot_cross_{buildArgs.Config}_{buildArgs.AOT}"; - - buildArgs = buildArgs with { ProjectName = projectName, ExtraBuildArgs = "-p:PublishTrimmed=false" }; - buildArgs = ExpandBuildArgs(buildArgs); - - (_, string output) = BuildProject( - buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: false, - ExpectSuccess: false)); - + ProjectInfo info = CreateWasmTemplateProject( + Template.WasmBrowser, + config, + aot, + "mono_aot_cross", + extraProperties: "false"); + + UpdateBrowserProgramFile(); + UpdateBrowserMainJs(); + + (string _, string output) = PublishProject(info, config, new PublishOptions(ExpectSuccess: false, AOT: aot)); Assert.Contains("AOT is not supported without IL trimming", output); } [Theory] - [BuildAndRun(host: RunHost.None, aot: true)] - public void IntermediateBitcodeToObjectFilesAreNotLLVMIR(BuildArgs buildArgs, string id) + [BuildAndRun(config: Configuration.Release, aot: true)] + public void IntermediateBitcodeToObjectFilesAreNotLLVMIR(Configuration config, bool aot) { string printFileTypeTarget = @" @@ -77,39 +74,32 @@ public void IntermediateBitcodeToObjectFilesAreNotLLVMIR(BuildArgs buildArgs, st "" Importance=""High"" /> "; - string projectName = $"bc_to_o_{buildArgs.Config}"; - - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, insertAtEnd: printFileTypeTarget); - - (_, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: false)); - + + ProjectInfo info = CreateWasmTemplateProject( + Template.WasmBrowser, + config, + aot, + "bc_to_o", + insertAtEnd: printFileTypeTarget); + + (string _, string output) = PublishProject(info, config, new PublishOptions(AOT: aot)); if (!output.Contains("** wasm-dis exit code: 0")) throw new XunitException($"Expected to successfully run wasm-dis on System.Private.CoreLib.dll.o ." + " It might fail if it was incorrectly compiled to a bitcode file, instead of wasm."); } [Theory] - [BuildAndRun(host: RunHost.None, aot: true)] - public void NativeBuildIsRequired(BuildArgs buildArgs, string id) + [BuildAndRun(config: Configuration.Release, aot: true)] + public void NativeBuildIsRequired(Configuration config, bool aot) { - string projectName = $"native_build_{buildArgs.Config}_{buildArgs.AOT}"; - - buildArgs = buildArgs with { ProjectName = projectName, ExtraBuildArgs = "-p:WasmBuildNative=false -p:WasmSingleFileBundle=true" }; - buildArgs = ExpandBuildArgs(buildArgs); - - (_, string output) = BuildProject( - buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: false, - ExpectSuccess: false)); - + ProjectInfo info = CreateWasmTemplateProject( + Template.WasmBrowser, + config, + aot, + "native_build", + extraProperties: "falsetrue"); + + (string _, string output) = PublishProject(info, config, new PublishOptions(ExpectSuccess: false, AOT: aot)); Assert.Contains("WasmBuildNative is required", output); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeLibraryTests.cs b/src/mono/wasm/Wasm.Build.Tests/NativeLibraryTests.cs index 990d331f0c580c..f01fb37c5fe993 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeLibraryTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeLibraryTests.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -10,7 +11,7 @@ namespace Wasm.Build.Tests { - public class NativeLibraryTests : TestMainJsTestBase + public class NativeLibraryTests : WasmTemplateTestsBase { public NativeLibraryTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -19,153 +20,79 @@ public NativeLibraryTests(ITestOutputHelper output, SharedBuildPerTestClassFixtu [Theory] [BuildAndRun(aot: false)] - [BuildAndRun(aot: true)] - public void ProjectWithNativeReference(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun(config: Configuration.Release, aot: true)] + public async Task ProjectWithNativeReference(Configuration config, bool aot) { - string projectName = $"AppUsingNativeLib-a"; - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, extraItems: ""); - - if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? _)) - { - InitPaths(id); - if (Directory.Exists(_projectDir)) - Directory.Delete(_projectDir, recursive: true); - - Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "AppUsingNativeLib"), _projectDir); - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "native-lib.o"), Path.Combine(_projectDir, "native-lib.o")); - } - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions(DotnetWasmFromRuntimePack: false)); - - string output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 0, - test: output => {}, - host: host, id: id); - - Assert.Contains("print_line: 100", output); - Assert.Contains("from pinvoke: 142", output); + string objectFilename = "native-lib.o"; + string extraItems = $""; + string extraProperties = "true"; + + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "AppUsingNativeLib-a", extraItems: extraItems, extraProperties: extraProperties); + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", objectFilename), Path.Combine(_projectDir, objectFilename)); + Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "AppUsingNativeLib"), _projectDir, overwrite: true); + DeleteFile(Path.Combine(_projectDir, "Common", "Program.cs")); + + (string _, string buildOutput) = PublishProject(info, config, new PublishOptions(AOT: aot), isNativeBuild: true); + RunResult output = await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun")); + + Assert.Contains(output.TestOutput, m => m.Contains("print_line: 100")); + Assert.Contains(output.TestOutput, m => m.Contains("from pinvoke: 142")); } [Theory] [BuildAndRun(aot: false)] - [BuildAndRun(aot: true, config: "Release")] - public void ProjectUsingSkiaSharp(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun(config: Configuration.Release, aot: true)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/103566")] + public async Task ProjectUsingSkiaSharp(Configuration config, bool aot) { - string projectName = $"AppUsingSkiaSharp"; - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, - extraItems: @$" + string prefix = $"AppUsingSkiaSharp"; + string extraItems = @$" {GetSkiaSharpReferenceItems()} - "); - - string programText = @" -using System; -using SkiaSharp; - -public class Test -{ - public static int Main() - { - using SKFileStream skfs = new SKFileStream(""mono.png""); - using SKImage img = SKImage.FromEncodedData(skfs); - - Console.WriteLine ($""Size: {skfs.Length} Height: {img.Height}, Width: {img.Width}""); - return 0; - } -}"; - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), - DotnetWasmFromRuntimePack: false)); - - string output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 0, - test: output => {}, - host: host, id: id, - args: "mono.png"); - - Assert.Contains("Size: 26462 Height: 599, Width: 499", output); + "; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, prefix, extraItems: extraItems); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "SkiaSharp.cs")); + + PublishProject(info, config, new PublishOptions(AOT: aot)); + BrowserRunOptions runOptions = new(config, ExtraArgs: "mono.png"); + RunResult output = await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 0)); + Assert.Contains(output.TestOutput, m => m.Contains("Size: 26462 Height: 599, Width: 499")); } [Theory] - [BuildAndRun(aot: false, host: RunHost.Chrome)] - [BuildAndRun(aot: true, host: RunHost.Chrome)] - public void ProjectUsingBrowserNativeCrypto(BuildArgs buildArgs, RunHost host, string id) - { - string projectName = $"AppUsingBrowserNativeCrypto"; - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs); - - string programText = @" -using System; -using System.Security.Cryptography; - -public class Test -{ - public static int Main() - { - using (SHA256 mySHA256 = SHA256.Create()) + [BuildAndRun(aot: false)] + [BuildAndRun(config: Configuration.Release, aot: true)] + public async Task ProjectUsingBrowserNativeCrypto(Configuration config, bool aot) { - byte[] data = { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o' }; - byte[] hashed = mySHA256.ComputeHash(data); - string asStr = string.Join(' ', hashed); - Console.WriteLine(""Hashed: "" + asStr); - return 0; - } - } -}"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "AppUsingBrowserNativeCrypto"); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "NativeCrypto.cs")); - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), - DotnetWasmFromRuntimePack: !buildArgs.AOT && buildArgs.Config != "Release")); + (string _, string buildOutput) = PublishProject(info, config, new PublishOptions(AOT: aot)); + RunResult output = await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 0)); - string output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 0, - test: output => {}, - host: host, id: id); - - Assert.Contains( - "Hashed: 24 95 141 179 34 113 254 37 245 97 166 252 147 139 46 38 67 6 236 48 78 218 81 128 7 209 118 72 38 56 25 105", - output); + string hash = "Hashed: 24 95 141 179 34 113 254 37 245 97 166 252 147 139 46 38 67 6 236 48 78 218 81 128 7 209 118 72 38 56 25 105"; + Assert.Contains(output.TestOutput, m => m.Contains(hash)); string cryptoInitMsg = "MONO_WASM: Initializing Crypto WebWorker"; - Assert.DoesNotContain(cryptoInitMsg, output); + Assert.All(output.TestOutput, m => Assert.DoesNotContain(cryptoInitMsg, m)); } [Theory] [BuildAndRun(aot: false)] - [BuildAndRun(aot: true)] - public void ProjectWithNativeLibrary(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun(config: Configuration.Release, aot: true)] + public async Task ProjectWithNativeLibrary(Configuration config, bool aot) { - string projectName = $"AppUsingNativeLibrary-a"; - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, extraItems: "\n"); - - if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? _)) - { - InitPaths(id); - if (Directory.Exists(_projectDir)) - Directory.Delete(_projectDir, recursive: true); - - Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "AppUsingNativeLib"), _projectDir); - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "native-lib.o"), Path.Combine(_projectDir, "native-lib.o")); - } - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions(DotnetWasmFromRuntimePack: false)); - - string output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 0, - test: output => {}, - host: host, id: id); - - Assert.Contains("print_line: 100", output); - Assert.Contains("from pinvoke: 142", output); + string extraItems = "\n"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "AppUsingNativeLib-a", extraItems: extraItems); + Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "AppUsingNativeLib"), _projectDir, overwrite: true); + DeleteFile(Path.Combine(_projectDir, "Common", "Program.cs")); + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "native-lib.o"), Path.Combine(_projectDir, "native-lib.o")); + + (string _, string buildOutput) = PublishProject(info, config, new PublishOptions(AOT: aot), isNativeBuild: true); + RunResult output = await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 0)); + + Assert.Contains(output.TestOutput, m => m.Contains("print_line: 100")); + Assert.Contains(output.TestOutput, m => m.Contains("from pinvoke: 142")); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/FlagsChangeRebuildTest.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/FlagsChangeRebuildTest.cs index 9f18293b3c848e..76c55cf22287d2 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/FlagsChangeRebuildTest.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/FlagsChangeRebuildTest.cs @@ -20,50 +20,50 @@ public FlagsChangeRebuildTests(ITestOutputHelper output, SharedBuildPerTestClass } public static IEnumerable FlagsChangesForNativeRelinkingData(bool aot) - => ConfigWithAOTData(aot, config: "Release").Multiply( + => ConfigWithAOTData(aot, config: Configuration.Release).Multiply( new object[] { /*cflags*/ "/p:EmccExtraCFlags=-g", /*ldflags*/ "" }, new object[] { /*cflags*/ "", /*ldflags*/ "/p:EmccExtraLDFlags=-g" }, new object[] { /*cflags*/ "/p:EmccExtraCFlags=-g", /*ldflags*/ "/p:EmccExtraLDFlags=-g" } - ).WithRunHosts(RunHost.Chrome).UnwrapItemsAsArrays(); + ).UnwrapItemsAsArrays(); [Theory] [MemberData(nameof(FlagsChangesForNativeRelinkingData), parameters: /*aot*/ false)] [MemberData(nameof(FlagsChangesForNativeRelinkingData), parameters: /*aot*/ true)] - public void ExtraEmccFlagsSetButNoRealChange(BuildArgs buildArgs, string extraCFlags, string extraLDFlags, RunHost host, string id) + public async void ExtraEmccFlagsSetButNoRealChange(Configuration config, bool aot, string extraCFlags, string extraLDFlags) { - buildArgs = buildArgs with { ProjectName = $"rebuild_flags_{buildArgs.Config}" }; - (buildArgs, BuildPaths paths) = FirstNativeBuild(s_mainReturns42, nativeRelink: true, invariant: false, buildArgs, id); - var pathsDict = _provider.GetFilesTable(buildArgs, paths, unchanged: true); - if (extraLDFlags.Length > 0) + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "rebuild_flags"); + BuildPaths paths = await FirstNativeBuildAndRun(info, config, aot, requestNativeRelink: true, invariant: false); + var pathsDict = GetFilesTable(info.ProjectName, aot, paths, unchanged: true); + bool dotnetNativeFilesUnchanged = extraLDFlags.Length == 0; + if (!dotnetNativeFilesUnchanged) pathsDict.UpdateTo(unchanged: false, "dotnet.native.wasm", "dotnet.native.js"); - var originalStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + var originalStat = StatFiles(pathsDict); // Rebuild - - string mainAssembly = $"{buildArgs.ProjectName}.dll"; + string mainAssembly = $"{info.ProjectName}.dll"; string extraBuildArgs = $" {extraCFlags} {extraLDFlags}"; - string output = Rebuild(nativeRelink: true, invariant: false, buildArgs, id, extraBuildArgs: extraBuildArgs, verbosity: "normal"); - - var newStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); - _provider.CompareStat(originalStat, newStat, pathsDict.Values); + string output = Rebuild(info, config, aot, requestNativeRelink: true, invariant: false, extraBuildArgs: extraBuildArgs, assertAppBundle: dotnetNativeFilesUnchanged); + var newStat = StatFilesAfterRebuild(pathsDict); + CompareStat(originalStat, newStat, pathsDict); + // cflags: pinvoke get's compiled, but doesn't overwrite pinvoke.o // and thus doesn't cause relinking TestUtils.AssertSubstring("pinvoke.c -> pinvoke.o", output, contains: extraCFlags.Length > 0); - + // ldflags: link step args change, so it should trigger relink TestUtils.AssertSubstring("Linking with emcc", output, contains: extraLDFlags.Length > 0); - - if (buildArgs.AOT) + + if (aot) { // ExtraEmccLDFlags does not affect .bc files Assert.DoesNotContain("Compiling assembly bitcode files", output); } - - string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - TestUtils.AssertSubstring($"Found statically linked AOT module '{Path.GetFileNameWithoutExtension(mainAssembly)}'", runOutput, - contains: buildArgs.AOT); + + RunResult runOutput = await RunForPublishWithWebServer(new BrowserRunOptions(config, aot, TestScenario: "DotnetRun")); + TestUtils.AssertSubstring($"Found statically linked AOT module '{Path.GetFileNameWithoutExtension(mainAssembly)}'", runOutput.ConsoleOutput, + contains: aot); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs index 09fafe0df6939e..2b90d03136bb00 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs @@ -18,7 +18,7 @@ namespace Wasm.Build.NativeRebuild.Tests { // TODO: test for runtime components - public class NativeRebuildTestsBase : TestMainJsTestBase + public class NativeRebuildTestsBase : WasmTemplateTestsBase { public NativeRebuildTestsBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -35,82 +35,56 @@ public NativeRebuildTestsBase(ITestOutputHelper output, SharedBuildPerTestClassF // aot data.AddRange(GetData(aot: true, nativeRelinking: false, invariant: false)); - data.AddRange(GetData(aot: true, nativeRelinking: false, invariant: true)); return data; IEnumerable GetData(bool aot, bool nativeRelinking, bool invariant) => ConfigWithAOTData(aot) .Multiply(new object[] { nativeRelinking, invariant }) - .WithRunHosts(RunHost.Chrome) + // AOT in Debug is switched off + .Where(item => !(item.ElementAt(0) is Configuration config && config == Configuration.Debug && item.ElementAt(1) is bool aotValue && aotValue)) .UnwrapItemsAsArrays().ToList(); } - internal (BuildArgs BuildArgs, BuildPaths paths) FirstNativeBuild(string programText, bool nativeRelink, bool invariant, BuildArgs buildArgs, string id, string extraProperties="") + internal async Task FirstNativeBuildAndRun(ProjectInfo info, Configuration config, bool aot, bool requestNativeRelink, bool invariant, string extraBuildArgs="") { - buildArgs = GenerateProjectContents(buildArgs, nativeRelink, invariant, extraProperties); - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), - DotnetWasmFromRuntimePack: false, - GlobalizationMode: invariant ? GlobalizationMode.Invariant : GlobalizationMode.Sharded, - CreateProject: true)); - - RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: RunHost.Chrome, id: id); - return (buildArgs, GetBuildPaths(buildArgs)); + var extraArgs = $"-p:_WasmDevel=true {extraBuildArgs}"; + if (requestNativeRelink) + extraArgs += $" -p:WasmBuildNative={requestNativeRelink}"; + if (invariant) + extraArgs += $" -p:InvariantGlobalization={invariant}"; + bool? nativeBuildValue = (requestNativeRelink || invariant) ? true : null; + PublishProject(info, + config, + new PublishOptions(AOT: aot, GlobalizationMode: invariant ? GlobalizationMode.Invariant : GlobalizationMode.Sharded, ExtraMSBuildArgs: extraArgs), + isNativeBuild: nativeBuildValue); + await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun")); + return GetBuildPaths(config, forPublish: true); } - protected string Rebuild(bool nativeRelink, bool invariant, BuildArgs buildArgs, string id, string extraProperties="", string extraBuildArgs="", string? verbosity=null) + protected string Rebuild( + ProjectInfo info, Configuration config, bool aot, bool requestNativeRelink, bool invariant, string extraBuildArgs="", string verbosity="normal", bool assertAppBundle=true) { - if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product)) - throw new XunitException($"Test bug: could not get the build product in the cache"); - - File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog")); - - buildArgs = buildArgs with { ExtraBuildArgs = $"{buildArgs.ExtraBuildArgs} {extraBuildArgs}" }; - var newBuildArgs = GenerateProjectContents(buildArgs, nativeRelink, invariant, extraProperties); - - // key(buildArgs) being changed - _buildContext.RemoveFromCache(product.ProjectDir); - _buildContext.CacheBuild(newBuildArgs, product); - - if (buildArgs.ProjectFileContents != newBuildArgs.ProjectFileContents) - File.WriteAllText(Path.Combine(_projectDir!, $"{buildArgs.ProjectName}.csproj"), buildArgs.ProjectFileContents); - buildArgs = newBuildArgs; + if (!_buildContext.TryGetBuildFor(info, out BuildResult? result)) + throw new XunitException($"Test bug: could not get the build result in the cache"); + + File.Move(result!.LogFile, Path.ChangeExtension(result.LogFile!, ".first.binlog")); + + var extraArgs = $"-p:_WasmDevel=true -v:{verbosity} {extraBuildArgs}"; + if (requestNativeRelink) + extraArgs += $" -p:WasmBuildNative={requestNativeRelink}"; + if (invariant) + extraArgs += $" -p:InvariantGlobalization={invariant}"; // artificial delay to have new enough timestamps Thread.Sleep(5000); - _testOutput.WriteLine($"{Environment.NewLine}Rebuilding with no changes ..{Environment.NewLine}"); - (_, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - DotnetWasmFromRuntimePack: false, - GlobalizationMode: invariant ? GlobalizationMode.Invariant : GlobalizationMode.Sharded, - CreateProject: false, - UseCache: false, - Verbosity: verbosity)); - + bool? nativeBuildValue = (requestNativeRelink || invariant) ? true : null; + var globalizationMode = invariant ? GlobalizationMode.Invariant : GlobalizationMode.Sharded; + var options = new PublishOptions(AOT: aot, GlobalizationMode: globalizationMode, ExtraMSBuildArgs: extraArgs, UseCache: false, AssertAppBundle: assertAppBundle); + (string _, string output) = PublishProject(info, config, options, isNativeBuild: nativeBuildValue); return output; } - protected BuildArgs GenerateProjectContents(BuildArgs buildArgs, bool nativeRelink, bool invariant, string extraProperties) - { - StringBuilder propertiesBuilder = new(); - propertiesBuilder.Append("<_WasmDevel>true"); - if (nativeRelink) - propertiesBuilder.Append($"true"); - if (invariant) - propertiesBuilder.Append($"true"); - propertiesBuilder.Append(extraProperties); - - return ExpandBuildArgs(buildArgs, propertiesBuilder.ToString()); - } - - // appending UTF-8 char makes sure project build&publish under all types of paths is supported - protected string GetTestProjectPath(string prefix, string config, bool appendUnicode=true) => - appendUnicode ? $"{prefix}_{config}_{s_unicodeChars}" : $"{prefix}_{config}"; - } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs index 2a378573a7a2a3..dada93b41b0701 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs @@ -19,19 +19,30 @@ public NoopNativeRebuildTest(ITestOutputHelper output, SharedBuildPerTestClassFi [Theory] [MemberData(nameof(NativeBuildData))] - public void NoOpRebuildForNativeBuilds(BuildArgs buildArgs, bool nativeRelink, bool invariant, RunHost host, string id) + public async void NoOpRebuildForNativeBuilds(Configuration config, bool aot, bool nativeRelink, bool invariant) { - buildArgs = buildArgs with { ProjectName = $"rebuild_noop_{buildArgs.Config}" }; - (buildArgs, BuildPaths paths) = FirstNativeBuild(s_mainReturns42, nativeRelink: nativeRelink, invariant: invariant, buildArgs, id); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "rebuild_noop"); + BuildPaths paths = await FirstNativeBuildAndRun(info, config, aot, nativeRelink, invariant); - var pathsDict = _provider.GetFilesTable(buildArgs, paths, unchanged: true); - var originalStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + var pathsDict = GetFilesTable(info.ProjectName, aot, paths, unchanged: true); + var originalStat = StatFiles(pathsDict); - Rebuild(nativeRelink, invariant, buildArgs, id); - var newStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + Rebuild(info, config, aot, nativeRelink, invariant); + var newStat = StatFiles(pathsDict); - _provider.CompareStat(originalStat, newStat, pathsDict.Values); - RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); + CompareStat(originalStat, newStat, pathsDict); + await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun")); + } + + [Fact] + public void NativeRelinkFailsWithInvariant() + { + Configuration config = Configuration.Release; + string extraArgs = "-p:_WasmDevel=true -p:WasmBuildNative=false -p:InvariantGlobalization=true"; + ProjectInfo info = CopyTestAsset(config, aot: true, TestAsset.WasmBasicTestApp, "relink_fails"); + var options = new PublishOptions(ExpectSuccess: false, AOT: true, ExtraMSBuildArgs: extraArgs); + PublishProject(info, config, options); + Assert.Contains("WasmBuildNative is required because InvariantGlobalization=true, but WasmBuildNative is already set to 'false'", _testOutput.ToString()); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/OptimizationFlagChangeTests.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/OptimizationFlagChangeTests.cs index 5286664e4cb634..069b37cd62748e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/OptimizationFlagChangeTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/OptimizationFlagChangeTests.cs @@ -21,28 +21,29 @@ public OptimizationFlagChangeTests(ITestOutputHelper output, SharedBuildPerTestC } public static IEnumerable FlagsOnlyChangeData(bool aot) - => ConfigWithAOTData(aot, config: "Release").Multiply( + => ConfigWithAOTData(aot, config: Configuration.Release).Multiply( new object[] { /*cflags*/ "/p:EmccCompileOptimizationFlag=-O1", /*ldflags*/ "" }, new object[] { /*cflags*/ "", /*ldflags*/ "/p:EmccLinkOptimizationFlag=-O1" } - ).WithRunHosts(RunHost.Chrome).UnwrapItemsAsArrays(); + ).UnwrapItemsAsArrays(); [Theory] [MemberData(nameof(FlagsOnlyChangeData), parameters: /*aot*/ false)] [MemberData(nameof(FlagsOnlyChangeData), parameters: /*aot*/ true)] - public void OptimizationFlagChange(BuildArgs buildArgs, string cflags, string ldflags, RunHost host, string id) + public async void OptimizationFlagChange(Configuration config, bool aot, string cflags, string ldflags) { - // force _WasmDevel=false, so we don't get -O0 - buildArgs = buildArgs with { ProjectName = $"rebuild_flags_{buildArgs.Config}", ExtraBuildArgs = "/p:_WasmDevel=false" }; - (buildArgs, BuildPaths paths) = FirstNativeBuild(s_mainReturns42, nativeRelink: true, invariant: false, buildArgs, id); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "rebuild_flags"); + // force _WasmDevel=false, so we don't get -O0 but -O2 + string optElevationArg = "/p:_WasmDevel=false"; + BuildPaths paths = await FirstNativeBuildAndRun(info, config, aot, requestNativeRelink: true, invariant: false, extraBuildArgs: optElevationArg); - string mainAssembly = $"{buildArgs.ProjectName}.dll"; - var pathsDict = _provider.GetFilesTable(buildArgs, paths, unchanged: false); + string mainAssembly = $"{info.ProjectName}{ProjectProviderBase.WasmAssemblyExtension}"; + var pathsDict = GetFilesTable(info.ProjectName, aot, paths, unchanged: false); pathsDict.UpdateTo(unchanged: true, mainAssembly, "icall-table.h", "pinvoke-table.h", "driver-gen.c"); if (cflags.Length == 0) pathsDict.UpdateTo(unchanged: true, "pinvoke.o", "corebindings.o", "driver.o", "runtime.o"); pathsDict.Remove(mainAssembly); - if (buildArgs.AOT) + if (aot) { // link optimization flag change affects .bc->.o files too, but // it might result in only *some* files being *changed, @@ -55,17 +56,21 @@ public void OptimizationFlagChange(BuildArgs buildArgs, string cflags, string ld pathsDict.Remove(key); } } - - var originalStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + var originalStat = StatFiles(pathsDict); // Rebuild + string output = Rebuild(info, + config, + aot, + requestNativeRelink: true, + invariant: false, + extraBuildArgs: $" {cflags} {ldflags} {optElevationArg}", + assertAppBundle: false); // optimization flags change changes the size of dotnet.native.wasm + var newStat = StatFilesAfterRebuild(pathsDict); + CompareStat(originalStat, newStat, pathsDict); - string output = Rebuild(nativeRelink: true, invariant: false, buildArgs, id, extraBuildArgs: $" {cflags} {ldflags}", verbosity: "normal"); - var newStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); - _provider.CompareStat(originalStat, newStat, pathsDict.Values); - - string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - TestUtils.AssertSubstring($"Found statically linked AOT module '{Path.GetFileNameWithoutExtension(mainAssembly)}'", runOutput, - contains: buildArgs.AOT); + RunResult runOutput = await RunForPublishWithWebServer(new BrowserRunOptions(config, aot, TestScenario: "DotnetRun")); + TestUtils.AssertSubstring($"Found statically linked AOT module '{Path.GetFileNameWithoutExtension(mainAssembly)}'", runOutput.ConsoleOutput, + contains: aot); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/ReferenceNewAssemblyRebuildTest.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/ReferenceNewAssemblyRebuildTest.cs index e9b3198519fed2..7f39761591054c 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/ReferenceNewAssemblyRebuildTest.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/ReferenceNewAssemblyRebuildTest.cs @@ -21,40 +21,26 @@ public ReferenceNewAssemblyRebuildTest(ITestOutputHelper output, SharedBuildPerT [Theory] [MemberData(nameof(NativeBuildData))] - public void ReferenceNewAssembly(BuildArgs buildArgs, bool nativeRelink, bool invariant, RunHost host, string id) + public async void ReferenceNewAssembly(Configuration config, bool aot, bool nativeRelink, bool invariant) { - buildArgs = buildArgs with { ProjectName = $"rebuild_tasks_{buildArgs.Config}" }; - (buildArgs, BuildPaths paths) = FirstNativeBuild(s_mainReturns42, nativeRelink, invariant: invariant, buildArgs, id); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "rebuild_tasks"); + BuildPaths paths = await FirstNativeBuildAndRun(info, config, aot, nativeRelink, invariant); - var pathsDict = _provider.GetFilesTable(buildArgs, paths, unchanged: false); + var pathsDict = GetFilesTable(info.ProjectName, aot, paths, unchanged: false); pathsDict.UpdateTo(unchanged: true, "corebindings.o"); pathsDict.UpdateTo(unchanged: true, "driver.o"); - if (!buildArgs.AOT) // relinking + if (!aot) // relinking pathsDict.UpdateTo(unchanged: true, "driver-gen.c"); - var originalStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); - - string programText = - @$" - using System; - using System.Text.Json; - public class Test - {{ - public static int Main() - {{" + - @" string json = ""{ \""name\"": \""value\"" }"";" + - @" var jdoc = JsonDocument.Parse($""{json}"", new JsonDocumentOptions());" + - @$" Console.WriteLine($""json: {{jdoc}}""); - return 42; - }} - }}"; - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText); - - Rebuild(nativeRelink, invariant, buildArgs, id); - var newStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); - - _provider.CompareStat(originalStat, newStat, pathsDict.Values); - RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); + var originalStat = StatFiles(pathsDict); + + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "NativeRebuildNewAssembly.cs")); + + Rebuild(info, config, aot, nativeRelink, invariant, assertAppBundle: !aot); + var newStat = StatFilesAfterRebuild(pathsDict); + + CompareStat(originalStat, newStat, pathsDict); + await RunForPublishWithWebServer(new BrowserRunOptions(config, ExpectedExitCode: 42, TestScenario: "DotnetRun")); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/SimpleSourceChangeRebuildTest.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/SimpleSourceChangeRebuildTest.cs index 6e2c320cb3242f..e7207e26600664 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/SimpleSourceChangeRebuildTest.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/SimpleSourceChangeRebuildTest.cs @@ -20,37 +20,30 @@ public SimpleSourceChangeRebuildTest(ITestOutputHelper output, SharedBuildPerTes [Theory] [MemberData(nameof(NativeBuildData))] - public void SimpleStringChangeInSource(BuildArgs buildArgs, bool nativeRelink, bool invariant, RunHost host, string id) + public async void SimpleStringChangeInSource(Configuration config, bool aot, bool nativeRelink, bool invariant) { - buildArgs = buildArgs with { ProjectName = $"rebuild_simple_{buildArgs.Config}" }; - (buildArgs, BuildPaths paths) = FirstNativeBuild(s_mainReturns42, nativeRelink, invariant: invariant, buildArgs, id); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "rebuild_simple"); + BuildPaths paths = await FirstNativeBuildAndRun(info, config, aot, nativeRelink, invariant); - string mainAssembly = $"{buildArgs.ProjectName}.dll"; - var pathsDict = _provider.GetFilesTable(buildArgs, paths, unchanged: true); + string mainAssembly = $"{info.ProjectName}{ProjectProviderBase.WasmAssemblyExtension}"; + var pathsDict = GetFilesTable(info.ProjectName, aot, paths, unchanged: true); pathsDict.UpdateTo(unchanged: false, mainAssembly); - pathsDict.UpdateTo(unchanged: !buildArgs.AOT, "dotnet.native.wasm", "dotnet.native.js"); + bool dotnetFilesSizeUnchanged = !aot; + pathsDict.UpdateTo(unchanged: dotnetFilesSizeUnchanged, "dotnet.native.wasm", "dotnet.native.js"); + + if (aot) + pathsDict.UpdateTo(unchanged: false, $"{info.ProjectName}.dll.bc", $"{info.ProjectName}.dll.o"); - if (buildArgs.AOT) - pathsDict.UpdateTo(unchanged: false, $"{mainAssembly}.bc", $"{mainAssembly}.o"); + var originalStat = StatFiles(pathsDict); - var originalStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); - - // Changes - string mainResults55 = @" - public class TestClass { - public static int Main() - { - return 55; - } - }"; - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), mainResults55); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "SimpleSourceChange.cs")); // Rebuild - Rebuild(nativeRelink, invariant, buildArgs, id); - var newStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + Rebuild(info, config, aot, nativeRelink, invariant, assertAppBundle: dotnetFilesSizeUnchanged); + var newStat = StatFilesAfterRebuild(pathsDict); - _provider.CompareStat(originalStat, newStat, pathsDict.Values); - RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 55, host: host, id: id); + CompareStat(originalStat, newStat, pathsDict); + await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 55)); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NonWasmTemplateBuildTests.cs b/src/mono/wasm/Wasm.Build.Tests/NonWasmTemplateBuildTests.cs index e85a212f83d49f..f383864a4b4de7 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NonWasmTemplateBuildTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NonWasmTemplateBuildTests.cs @@ -11,7 +11,7 @@ namespace Wasm.Build.Tests; -public class NonWasmTemplateBuildTests : TestMainJsTestBase +public class NonWasmTemplateBuildTests : WasmTemplateTestsBase { public NonWasmTemplateBuildTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -23,8 +23,8 @@ public NonWasmTemplateBuildTests(ITestOutputHelper output, SharedBuildPerTestCla // So, copy the reference for latest TFM, and add that back with the // TFM=DefaultTargetFramework // - // This is useful for the case when we are on tfm=net7.0, but sdk, and packages - // are really 8.0 . + // This is useful for the case when we are on tfm=net8.0, but sdk, and packages + // are really 9.0 . private const string s_latestTargetFramework = "net9.0"; private const string s_previousTargetFramework = "net8.0"; private static string s_directoryBuildTargetsForPreviousTFM = @@ -55,8 +55,8 @@ public NonWasmTemplateBuildTests(ITestOutputHelper output, SharedBuildPerTestCla public static IEnumerable GetTestData() => new IEnumerable[] { - new object?[] { "Debug" }, - new object?[] { "Release" } + new object?[] { Configuration.Debug }, + new object?[] { Configuration.Release } } .AsEnumerable() .MultiplyWithSingleArgs @@ -79,7 +79,7 @@ public NonWasmTemplateBuildTests(ITestOutputHelper output, SharedBuildPerTestCla [Theory, TestCategory("no-workload")] [MemberData(nameof(GetTestData))] - public void NonWasmConsoleBuild_WithoutWorkload(string config, string extraBuildArgs, string targetFramework) + public void NonWasmConsoleBuild_WithoutWorkload(Configuration config, string extraBuildArgs, string targetFramework) => NonWasmConsoleBuild(config, extraBuildArgs, targetFramework, @@ -88,14 +88,14 @@ public void NonWasmConsoleBuild_WithoutWorkload(string config, string extraBuild [Theory] [MemberData(nameof(GetTestData))] - public void NonWasmConsoleBuild_WithWorkload(string config, string extraBuildArgs, string targetFramework) + public void NonWasmConsoleBuild_WithWorkload(Configuration config, string extraBuildArgs, string targetFramework) => NonWasmConsoleBuild(config, extraBuildArgs, targetFramework, // net6 is sdk would be needed to run the app shouldRun: targetFramework == s_latestTargetFramework); - private void NonWasmConsoleBuild(string config, + private void NonWasmConsoleBuild(Configuration config, string extraBuildArgs, string targetFramework, string? directoryBuildTargets = null, @@ -113,7 +113,7 @@ private void NonWasmConsoleBuild(string config, File.WriteAllText(Path.Combine(_projectDir, "Directory.Build.targets"), directoryBuildTargets); using ToolCommand cmd = new DotNetCommand(s_buildEnv, _testOutput, useDefaultArgs: false) - .WithWorkingDirectory(_projectDir!); + .WithWorkingDirectory(_projectDir); cmd.ExecuteWithCapturedOutput("new console --no-restore") .EnsureSuccessful(); diff --git a/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs b/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs index 0c99d38835e2c8..bd1b23f3a88764 100644 --- a/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Text; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -13,7 +14,7 @@ namespace Wasm.Build.Tests { - public class PInvokeTableGeneratorTests : TestMainJsTestBase + public class PInvokeTableGeneratorTests : PInvokeTableGeneratorTestsBase { public PInvokeTableGeneratorTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -21,314 +22,107 @@ public PInvokeTableGeneratorTests(ITestOutputHelper output, SharedBuildPerTestCl } [Theory] - [BuildAndRun(host: RunHost.Chrome)] - public void NativeLibraryWithVariadicFunctions(BuildArgs buildArgs, RunHost host, string id) - { - string code = @" - using System; - using System.Runtime.InteropServices; - public class Test - { - public static int Main(string[] args) - { - Console.WriteLine($""Main running""); - if (args.Length > 2) - { - // We don't want to run this, because we can't call variadic functions - Console.WriteLine($""sum_three: {sum_three(7, 14, 21)}""); - Console.WriteLine($""sum_two: {sum_two(3, 6)}""); - Console.WriteLine($""sum_one: {sum_one(5)}""); - } - return 42; - } - - [DllImport(""variadic"", EntryPoint=""sum"")] public static extern int sum_one(int a); - [DllImport(""variadic"", EntryPoint=""sum"")] public static extern int sum_two(int a, int b); - [DllImport(""variadic"", EntryPoint=""sum"")] public static extern int sum_three(int a, int b, int c); - }"; - - (buildArgs, string output) = BuildForVariadicFunctionTests(code, - buildArgs with { ProjectName = $"variadic_{buildArgs.Config}_{id}" }, - id); - Assert.Matches("warning.*native function.*sum.*varargs", output); - Assert.Contains("System.Int32 sum_one(System.Int32)", output); - Assert.Contains("System.Int32 sum_two(System.Int32, System.Int32)", output); - Assert.Contains("System.Int32 sum_three(System.Int32, System.Int32, System.Int32)", output); - - output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Main running", output); - } - - [Theory] - [BuildAndRun(host: RunHost.Chrome)] - public void DllImportWithFunctionPointersCompilesWithoutWarning(BuildArgs buildArgs, RunHost host, string id) - { - string code = - """ - using System; - using System.Runtime.InteropServices; - public class Test - { - public static int Main() - { - Console.WriteLine("Main running"); - return 42; - } - - [DllImport("variadic", EntryPoint="sum")] - public unsafe static extern int using_sum_one(delegate* unmanaged callback); - - [DllImport("variadic", EntryPoint="sum")] - public static extern int sum_one(int a, int b); - } - """; - - (buildArgs, string output) = BuildForVariadicFunctionTests(code, - buildArgs with { ProjectName = $"fnptr_{buildArgs.Config}_{id}" }, - id); - - Assert.DoesNotMatch("warning\\sWASM0001.*Could\\snot\\sget\\spinvoke.*Parsing\\sfunction\\spointer\\stypes", output); - Assert.DoesNotMatch("warning\\sWASM0001.*Skipping.*using_sum_one.*because.*function\\spointer", output); - - output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Main running", output); - } - - [Theory] - [BuildAndRun(host: RunHost.Chrome)] - public void DllImportWithFunctionPointers_ForVariadicFunction_CompilesWithWarning(BuildArgs buildArgs, RunHost host, string id) - { - string code = @" - using System; - using System.Runtime.InteropServices; - public class Test - { - public static int Main() - { - Console.WriteLine($""Main running""); - return 42; - } - - [DllImport(""variadic"", EntryPoint=""sum"")] - public unsafe static extern int using_sum_one(delegate* unmanaged callback); - }"; - - (buildArgs, string output) = BuildForVariadicFunctionTests(code, - buildArgs with { ProjectName = $"fnptr_variadic_{buildArgs.Config}_{id}" }, - id); - - Assert.DoesNotMatch("warning\\sWASM0001.*Could\\snot\\sget\\spinvoke.*Parsing\\sfunction\\spointer\\stypes", output); - Assert.DoesNotMatch("warning\\sWASM0001.*Skipping.*using_sum_one.*because.*function\\spointer", output); - - output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Main running", output); - } - - [Theory] - [BuildAndRun(host: RunHost.None)] + [BuildAndRun()] public void UnmanagedStructAndMethodIn_SameAssembly_WithoutDisableRuntimeMarshallingAttribute_NotConsideredBlittable - (BuildArgs buildArgs, string id) + (Configuration config, bool aot) { - (_, string output) = SingleProjectForDisabledRuntimeMarshallingTest( - withDisabledRuntimeMarshallingAttribute: false, - withAutoLayout: true, - expectSuccess: false, - buildArgs, - id - ); - + ProjectInfo info = PrepreProjectForBlittableTests( + config, aot, "not_blittable", disableRuntimeMarshalling: false, useAutoLayout: true); + (_, string output) = BuildProject(info, config, new BuildOptions(ExpectSuccess: false, AOT: aot)); Assert.Matches("error.*Parameter.*types.*pinvoke.*.*blittable", output); } [Theory] - [BuildAndRun(host: RunHost.None)] + [BuildAndRun()] public void UnmanagedStructAndMethodIn_SameAssembly_WithoutDisableRuntimeMarshallingAttribute_WithStructLayout_ConsideredBlittable - (BuildArgs buildArgs, string id) + (Configuration config, bool aot) { - (_, string output) = SingleProjectForDisabledRuntimeMarshallingTest( - withDisabledRuntimeMarshallingAttribute: false, - withAutoLayout: false, - expectSuccess: true, - buildArgs, - id - ); - + ProjectInfo info = PrepreProjectForBlittableTests( + config, aot, "blittable", disableRuntimeMarshalling: false, useAutoLayout: false); + (_, string output) = BuildProject(info, config, new BuildOptions(AOT: aot), isNativeBuild: true); Assert.DoesNotMatch("error.*Parameter.*types.*pinvoke.*.*blittable", output); } [Theory] - [BuildAndRun(host: RunHost.Chrome)] - public void UnmanagedStructAndMethodIn_SameAssembly_WithDisableRuntimeMarshallingAttribute_ConsideredBlittable - (BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun()] + public async void UnmanagedStructAndMethodIn_SameAssembly_WithDisableRuntimeMarshallingAttribute_ConsideredBlittable + (Configuration config, bool aot) { - (buildArgs, _) = SingleProjectForDisabledRuntimeMarshallingTest( - withDisabledRuntimeMarshallingAttribute: true, - withAutoLayout: true, - expectSuccess: true, - buildArgs, - id - ); - - string output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Main running 5", output); + ProjectInfo info = PrepreProjectForBlittableTests( + config, aot, "blittable", disableRuntimeMarshalling: true, useAutoLayout: true); + (_, string output) = BuildProject(info, config, new BuildOptions(AOT: aot), isNativeBuild: true); + RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.Contains(result.TestOutput, m => m.Contains("Main running")); } - private (BuildArgs buildArgs ,string output) SingleProjectForDisabledRuntimeMarshallingTest( - bool withDisabledRuntimeMarshallingAttribute, bool withAutoLayout, - bool expectSuccess, BuildArgs buildArgs, string id - ) { - string code = - """ - using System; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - """ - + (withDisabledRuntimeMarshallingAttribute ? "[assembly: DisableRuntimeMarshalling]" : "") - + """ - public class Test - { - public static int Main() - { - var x = new S { Value = 5 }; - - Console.WriteLine("Main running " + x.Value); - return 42; - } - """ - + (withAutoLayout ? "\n[StructLayout(LayoutKind.Auto)]\n" : "") - + """ - public struct S { public int Value; public float Value2; } + private ProjectInfo PrepreProjectForBlittableTests(Configuration config, bool aot, string prefix, bool disableRuntimeMarshalling, bool useAutoLayout = false) + { + string extraProperties = aot ? string.Empty : "true"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, prefix, extraProperties: extraProperties); + string programRelativePath = Path.Combine("Common", "Program.cs"); + ReplaceFile(programRelativePath, Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "BittableSameAssembly.cs")); - [UnmanagedCallersOnly] - public static void M(S myStruct) { } + var replacements = new Dictionary { }; + if (!disableRuntimeMarshalling) + { + replacements.Add("[assembly: DisableRuntimeMarshalling]", ""); } - """; - - buildArgs = ExpandBuildArgs( - buildArgs with { ProjectName = $"not_blittable_{buildArgs.Config}_{id}" }, - extraProperties: buildArgs.AOT - ? string.Empty - : "true" - ); - - (_, string output) = BuildProject( - buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), code); - }, - Publish: buildArgs.AOT, - DotnetWasmFromRuntimePack: false, - ExpectSuccess: expectSuccess - ) - ); - - return (buildArgs, output); + if (!useAutoLayout) + { + replacements.Add("[StructLayout(LayoutKind.Auto)]", ""); + } + if (replacements.Count > 0) + { + UpdateFile(programRelativePath, replacements); + } + return info; } - public static IEnumerable SeparateAssemblyWithDisableMarshallingAttributeTestData(string config) + public static IEnumerable SeparateAssemblyWithDisableMarshallingAttributeTestData(Configuration config) => ConfigWithAOTData(aot: false, config: config).Multiply( new object[] { /*libraryHasAttribute*/ false, /*appHasAttribute*/ false, /*expectSuccess*/ false }, new object[] { /*libraryHasAttribute*/ true, /*appHasAttribute*/ false, /*expectSuccess*/ false }, new object[] { /*libraryHasAttribute*/ false, /*appHasAttribute*/ true, /*expectSuccess*/ true }, new object[] { /*libraryHasAttribute*/ true, /*appHasAttribute*/ true, /*expectSuccess*/ true } - ).WithRunHosts(RunHost.Chrome).UnwrapItemsAsArrays(); + ).UnwrapItemsAsArrays(); [Theory] - [MemberData(nameof(SeparateAssemblyWithDisableMarshallingAttributeTestData), parameters: "Debug")] - [MemberData(nameof(SeparateAssemblyWithDisableMarshallingAttributeTestData), parameters: "Release")] - public void UnmanagedStructsAreConsideredBlittableFromDifferentAssembly - (BuildArgs buildArgs, bool libraryHasAttribute, bool appHasAttribute, bool expectSuccess, RunHost host, string id) - => SeparateAssembliesForDisableRuntimeMarshallingTest( - libraryHasAttribute: libraryHasAttribute, - appHasAttribute: appHasAttribute, - expectSuccess: expectSuccess, - buildArgs, - host, - id - ); - - private void SeparateAssembliesForDisableRuntimeMarshallingTest - (bool libraryHasAttribute, bool appHasAttribute, bool expectSuccess, BuildArgs buildArgs, RunHost host, string id) + [MemberData(nameof(SeparateAssemblyWithDisableMarshallingAttributeTestData), parameters: Configuration.Debug)] + [MemberData(nameof(SeparateAssemblyWithDisableMarshallingAttributeTestData), parameters: Configuration.Release)] + public async void UnmanagedStructsAreConsideredBlittableFromDifferentAssembly + (Configuration config, bool aot, bool libraryHasAttribute, bool appHasAttribute, bool expectSuccess) { - string code = - (libraryHasAttribute ? "[assembly: System.Runtime.CompilerServices.DisableRuntimeMarshalling]" : "") - + "public struct __NonBlittableTypeForAutomatedTests__ { } public struct S { public int Value; public __NonBlittableTypeForAutomatedTests__ NonBlittable; }"; - - var libraryBuildArgs = ExpandBuildArgs( - buildArgs with { ProjectName = $"blittable_different_library_{buildArgs.Config}_{id}" }, - extraProperties: "Library" - ); - - (string libraryDir, string output) = BuildProject( - libraryBuildArgs, - id: id + "_library", - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "S.cs"), code); - }, - Publish: buildArgs.AOT, - DotnetWasmFromRuntimePack: false, - AssertAppBundle: false - ) - ); - - code = - """ - using System; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - - """ - + (appHasAttribute ? "[assembly: DisableRuntimeMarshalling]" : "") - + """ - - public class Test + string extraProperties = aot ? string.Empty : "true"; + string extraItems = @$""; + string libRelativePath = Path.Combine("..", "Library", "Library.cs"); + string programRelativePath = Path.Combine("Common", "Program.cs"); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "blittable_different_library", extraProperties: extraProperties, extraItems: extraItems); + ReplaceFile(libRelativePath, Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "BittableDifferentAssembly_Lib.cs")); + ReplaceFile(programRelativePath, Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "BittableDifferentAssembly.cs")); + if (!libraryHasAttribute) { - public static int Main() - { - var x = new S { Value = 5 }; - - Console.WriteLine("Main running " + x.Value); - return 42; - } - - [UnmanagedCallersOnly] - public static void M(S myStruct) { } + UpdateFile(libRelativePath, new Dictionary { { "[assembly: System.Runtime.CompilerServices.DisableRuntimeMarshalling]", "" } }); } - """; - - buildArgs = ExpandBuildArgs( - buildArgs with { ProjectName = $"blittable_different_app_{buildArgs.Config}_{id}" }, - extraItems: $@"", - extraProperties: buildArgs.AOT - ? string.Empty - : "true" - ); - - _projectDir = null; - - (_, output) = BuildProject( - buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), code); - }, - Publish: buildArgs.AOT, - DotnetWasmFromRuntimePack: false, - ExpectSuccess: expectSuccess - ) - ); - + if (!appHasAttribute) + { + UpdateFile(programRelativePath, new Dictionary { { "[assembly: DisableRuntimeMarshalling]", "" } }); + } + (_, string output) = BuildProject(info, + config, + new BuildOptions(ExpectSuccess: expectSuccess, AOT: aot), + isNativeBuild: true); if (expectSuccess) { - output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Main running 5", output); + RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.Contains("Main running 5", result.TestOutput); } else { @@ -337,228 +131,58 @@ public static void M(S myStruct) { } } [Theory] - [BuildAndRun(host: RunHost.Chrome)] - public void DllImportWithFunctionPointers_WarningsAsMessages(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun()] + public async void UnmanagedCallback_InFileType(Configuration config, bool aot) { - string code = - """ - using System; - using System.Runtime.InteropServices; - public class Test - { - public static int Main() - { - Console.WriteLine("Main running"); - return 42; - } - - [DllImport("someting")] - public unsafe static extern void SomeFunction1(delegate* unmanaged callback); - } - """; - - (buildArgs, string output) = BuildForVariadicFunctionTests( - code, - buildArgs with { ProjectName = $"fnptr_{buildArgs.Config}_{id}" }, - id, - verbosity: "normal", - extraProperties: "$(MSBuildWarningsAsMessage);WASM0001" - ); - - Assert.DoesNotContain("warning WASM0001", output); - - output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Main running", output); - } - - [Theory] - [BuildAndRun(host: RunHost.None)] - public void UnmanagedCallback_WithFunctionPointers_CompilesWithWarnings(BuildArgs buildArgs, string id) - { - string code = - """ - using System; - using System.Runtime.InteropServices; - public class Test - { - public static int Main() - { - Console.WriteLine("Main running"); - return 42; - } - - [UnmanagedCallersOnly] - public unsafe static extern void SomeFunction1(delegate* unmanaged callback); - } - """; - - (_, string output) = BuildForVariadicFunctionTests( - code, - buildArgs with { ProjectName = $"cb_fnptr_{buildArgs.Config}" }, - id - ); - - Assert.DoesNotMatch("warning\\sWASM0001.*Skipping.*Test::SomeFunction1.*because.*function\\spointer", output); - } - - [Theory] - [BuildAndRun(host: RunHost.Chrome)] - public void UnmanagedCallback_InFileType(BuildArgs buildArgs, RunHost host, string id) - { - string code = - """ - using System; - using System.Runtime.InteropServices; - public class Test - { - public static int Main() - { - Console.WriteLine("Main running"); - return 42; - } - } - - file class Foo - { - [UnmanagedCallersOnly] - public unsafe static extern void SomeFunction1(int i); - } - """; - - (buildArgs, string output) = BuildForVariadicFunctionTests( - code, - buildArgs with { ProjectName = $"cb_filetype_{buildArgs.Config}" }, - id - ); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "cb_filetype"); + string programRelativePath = Path.Combine("Common", "Program.cs"); + ReplaceFile(programRelativePath, Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "UnmanagedCallbackInFile.cs")); + string output = PublishForVariadicFunctionTests(info, config, aot); Assert.DoesNotMatch(".*(warning|error).*>[A-Z0-9]+__Foo", output); - output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Main running", output); + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.Contains("Main running", result.TestOutput); } [Theory] - [BuildAndRun(host: RunHost.Chrome)] - public void UnmanagedCallersOnly_Namespaced(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun()] + public async void UnmanagedCallersOnly_Namespaced(Configuration config, bool aot) { - string code = - """ - using System; - using System.Runtime.InteropServices; - - public class Test - { - public unsafe static int Main() - { - ((delegate* unmanaged)&A.Conflict.C)(); - ((delegate* unmanaged)&B.Conflict.C)(); - ((delegate* unmanaged)&A.Conflict.C\u733f)(); - ((delegate* unmanaged)&B.Conflict.C\u733f)(); - return 42; - } - } - - namespace A { - public class Conflict { - [UnmanagedCallersOnly(EntryPoint = "A_Conflict_C")] - public static void C() { - Console.WriteLine("A.Conflict.C"); - } - - [UnmanagedCallersOnly(EntryPoint = "A_Conflict_C\u733f")] - public static void C\u733f() { - Console.WriteLine("A.Conflict.C_\U0001F412"); - } - } - } - - namespace B { - public class Conflict { - [UnmanagedCallersOnly(EntryPoint = "B_Conflict_C")] - public static void C() { - Console.WriteLine("B.Conflict.C"); - } - - [UnmanagedCallersOnly(EntryPoint = "B_Conflict_C\u733f")] - public static void C\u733f() { - Console.WriteLine("B.Conflict.C_\U0001F412"); - } - } - } - """; - - (buildArgs, string output) = BuildForVariadicFunctionTests( - code, - buildArgs with { ProjectName = $"cb_namespace_{buildArgs.Config}" }, - id - ); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "cb_namespace"); + string programRelativePath = Path.Combine("Common", "Program.cs"); + ReplaceFile(programRelativePath, Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "UnmanagedCallbackNamespaced.cs")); + string output = PublishForVariadicFunctionTests(info, config, aot); Assert.DoesNotMatch(".*(warning|error).*>[A-Z0-9]+__Foo", output); - output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.Contains("A.Conflict.C", output); - Assert.Contains("B.Conflict.C", output); + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.Contains("A.Conflict.C", result.TestOutput); + Assert.Contains("B.Conflict.C", result.TestOutput); if (OperatingSystem.IsWindows()) { // Windows console unicode support is not great - Assert.Contains("A.Conflict.C_", output); - Assert.Contains("B.Conflict.C_", output); + Assert.Contains(result.TestOutput, m => m.Contains("A.Conflict.C_")); + Assert.Contains(result.TestOutput, m => m.Contains("B.Conflict.C_")); } else { - Assert.Contains("A.Conflict.C_\U0001F412", output); - Assert.Contains("B.Conflict.C_\U0001F412", output); + Assert.Contains("A.Conflict.C_\U0001F412", result.TestOutput); + Assert.Contains("B.Conflict.C_\U0001F412", result.TestOutput); } } [Theory] - [BuildAndRun(host: RunHost.None)] - public void IcallWithOverloadedParametersAndEnum(BuildArgs buildArgs, string id) + [BuildAndRun()] + public void IcallWithOverloadedParametersAndEnum(Configuration config, bool aot) { - // Build a library containing icalls with overloaded parameters. - - string code = + string appendToTheEnd = """ - using System; - using System.Runtime.CompilerServices; - - public static class Interop - { - public enum Numbers { A, B, C, D } - - [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern void Square(Numbers x); - - [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern void Square(Numbers x, Numbers y); - - public static void Main() - { - // Noop - } - } - """; - - var libraryBuildArgs = ExpandBuildArgs( - buildArgs with { ProjectName = $"icall_enum_library_{buildArgs.Config}_{id}" } - ); - - (string libraryDir, string output) = BuildProject( - libraryBuildArgs, - id: id + "library", - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), code); - }, - Publish: false, - DotnetWasmFromRuntimePack: false, - AssertAppBundle: false - ) - ); - - // Build a project with ManagedToNativeGenerator task reading icalls from the above library and runtime-icall-table.h bellow. - - string projectCode = - """ - @@ -581,10 +205,20 @@ public static void Main() - """; - string AddAssembly(string name) => $""; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "icall_enum", insertAtEnd: appendToTheEnd); + // build a library containing icalls with overloaded parameters. + ReplaceFile(Path.Combine("..", "Library", "Library.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "ICall_Lib.cs")); + // temporarily change the project directory to build the library + _projectDir = Path.Combine(_projectDir, "..", "Library"); + bool isPublish = false; + // libraries do not have framework dirs + string hypotheticalFrameworkDir = Path.Combine(GetBinFrameworkDir(config, isPublish)); + string libAssemblyPath = Path.Combine(hypotheticalFrameworkDir, "..", ".."); + BuildProject(info, config, new BuildOptions(AssertAppBundle: false, AOT: aot)); + // restore the project directory + _projectDir = Path.Combine(_projectDir, "..", "App"); string icallTable = """ @@ -595,6 +229,7 @@ public static void Main() ] """; + UpdateFile(Path.Combine(_projectDir, "runtime-icall-table.h"), icallTable); string tasksDir = Path.Combine(s_buildEnv.WorkloadPacksDir, "Microsoft.NET.Runtime.WebAssembly.Sdk", @@ -622,343 +257,72 @@ public static void Main() _testOutput.WriteLine ("Using WasmAppBuilder.dll from {0}", taskPath); - projectCode = projectCode - .Replace("###WasmPInvokeModule###", AddAssembly("System.Private.CoreLib") + AddAssembly("System.Runtime") + AddAssembly(libraryBuildArgs.ProjectName)) - .Replace("###WasmAppBuilder###", taskPath); - - buildArgs = buildArgs with { ProjectName = $"icall_enum_{buildArgs.Config}_{id}", ProjectFileContents = projectCode }; - - _projectDir = null; - - (_, output) = BuildProject( - buildArgs, - id: id + "tasks", - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "runtime-icall-table.h"), icallTable); - }, - Publish: buildArgs.AOT, - DotnetWasmFromRuntimePack: false, - UseCache: false, - AssertAppBundle: false - ) - ); - + string AddAssembly(string assemblyLocation, string name) => $""; + string frameworkDir = Path.Combine(GetBinFrameworkDir(config, isPublish)); + string appAssemblyPath = Path.Combine(frameworkDir, "..", ".."); + string pinvokeReplacement = + AddAssembly(appAssemblyPath, "System.Private.CoreLib") + + AddAssembly(appAssemblyPath, "System.Runtime") + + AddAssembly(libAssemblyPath, "Library"); + UpdateFile("WasmBasicTestApp.csproj", new Dictionary { + { "###WasmPInvokeModule###", pinvokeReplacement }, + { "###WasmAppBuilder###", taskPath } + }); + + // Build a project with ManagedToNativeGenerator task reading icalls from the above library and runtime-icall-table.h + (_, string output) = BuildProject(info, config, new BuildOptions(UseCache: false, AOT: aot)); Assert.DoesNotMatch(".*warning.*Numbers", output); } [Theory] - [BuildAndRun(host: RunHost.Chrome, parameters: new object[] { "tr_TR.UTF-8" })] - public void BuildNativeInNonEnglishCulture(BuildArgs buildArgs, string culture, RunHost host, string id) + [BuildAndRun(parameters: new object[] { "tr_TR.UTF-8" })] + public async void BuildNativeInNonEnglishCulture(Configuration config, bool aot, string culture) { // Check that we can generate interp tables in non-english cultures // Prompted by https://github.com/dotnet/runtime/issues/71149 - string code = @" - using System; - using System.Runtime.InteropServices; - - Console.WriteLine($""square: {square(5)}""); - return 42; - - [DllImport(""simple"")] static extern int square(int x); - "; - - buildArgs = ExpandBuildArgs(buildArgs, - extraItems: @$"", - extraProperties: buildArgs.AOT - ? string.Empty - : "true"); + string extraItems = @$""; + string extraProperties = aot ? string.Empty : "true"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "buildNativeNonEng", extraItems: extraItems); + string programRelativePath = Path.Combine("Common", "Program.cs"); + ReplaceFile(programRelativePath, Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "BuildNative.cs")); + string cCodeFilename = "simple.c"; + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", cCodeFilename), Path.Combine(_projectDir, cCodeFilename)); var extraEnvVars = new Dictionary { { "LANG", culture }, { "LC_ALL", culture }, }; - (_, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), code); - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "simple.c"), - Path.Combine(_projectDir!, "simple.c")); - }, - Publish: buildArgs.AOT, - DotnetWasmFromRuntimePack: false, - ExtraBuildEnvironmentVariables: extraEnvVars - )); - - output = RunAndTestWasmApp(buildArgs, - buildDir: _projectDir, - expectedExitCode: 42, - host: host, - id: id, - envVars: extraEnvVars); - Assert.Contains("square: 25", output); - } - - [Theory] - [BuildAndRun(host: RunHost.Chrome, parameters: new object[] { new object[] { - "with-hyphen", - "with#hash-and-hyphen", - "with.per.iod", - "with🚀unicode#" - } })] - - public void CallIntoLibrariesWithNonAlphanumericCharactersInTheirNames(BuildArgs buildArgs, string[] libraryNames, RunHost host, string id) - { - buildArgs = ExpandBuildArgs(buildArgs, - extraItems: @$"", - extraProperties: buildArgs.AOT - ? string.Empty - : "true"); - - int baseArg = 10; - (_, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => GenerateSourceFiles(_projectDir!, baseArg), - Publish: buildArgs.AOT, - DotnetWasmFromRuntimePack: false - )); - - output = RunAndTestWasmApp(buildArgs, - buildDir: _projectDir, - expectedExitCode: 42, - host: host, - id: id); - - for (int i = 0; i < libraryNames.Length; i ++) - { - Assert.Contains($"square_{i}: {(i + baseArg) * (i + baseArg)}", output); - } - - void GenerateSourceFiles(string outputPath, int baseArg) - { - StringBuilder csBuilder = new($@" - using System; - using System.Runtime.InteropServices; - "); - - StringBuilder dllImportsBuilder = new(); - for (int i = 0; i < libraryNames.Length; i ++) - { - dllImportsBuilder.AppendLine($"[DllImport(\"{libraryNames[i]}\")] static extern int square_{i}(int x);"); - csBuilder.AppendLine($@"Console.WriteLine($""square_{i}: {{square_{i}({i + baseArg})}}"");"); - - string nativeCode = $@" - #include - - int square_{i}(int x) - {{ - return x * x; - }}"; - File.WriteAllText(Path.Combine(outputPath, $"{libraryNames[i]}.c"), nativeCode); - } - - csBuilder.AppendLine("return 42;"); - csBuilder.Append(dllImportsBuilder); - - File.WriteAllText(Path.Combine(outputPath, "Program.cs"), csBuilder.ToString()); - } - } - - private (BuildArgs, string) BuildForVariadicFunctionTests(string programText, BuildArgs buildArgs, string id, string? verbosity = null, string extraProperties = "") - { - extraProperties += "true<_WasmDevel>true"; - - string filename = "variadic.o"; - buildArgs = ExpandBuildArgs(buildArgs, - extraItems: $"", - extraProperties: extraProperties); - - (_, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText); - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", filename), - Path.Combine(_projectDir!, filename)); - }, - Publish: buildArgs.AOT, - Verbosity: verbosity, - DotnetWasmFromRuntimePack: false)); - - return (buildArgs, output); - } - - private void EnsureComInteropCompiles(BuildArgs buildArgs, RunHost host, string id) - { - string programText = @" - using System; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - using System.Runtime.InteropServices.ComTypes; - - public class Test - { - public static int Main(string[] args) - { - var s = new STGMEDIUM(); - ReleaseStgMedium(ref s); - return 42; - } - - [DllImport(""ole32.dll"")] - internal static extern void ReleaseStgMedium(ref STGMEDIUM medium); - } - - "; - - buildArgs = ExpandBuildArgs(buildArgs); - - (string libraryDir, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText); - }, - Publish: buildArgs.AOT, - DotnetWasmFromRuntimePack: true)); - - Assert.Contains("Generated app bundle at " + libraryDir, output); + (_, string output) = PublishProject(info, + config, + new PublishOptions(ExtraBuildEnvironmentVariables: extraEnvVars, AOT: aot), + isNativeBuild: true); + + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42, + Locale: culture + )); + Assert.Contains("square: 25", result.TestOutput); } - private void EnsureWasmAbiRulesAreFollowed(BuildArgs buildArgs, RunHost host, string id) + private async Task EnsureWasmAbiRulesAreFollowed(Configuration config, bool aot) { - string programText = @" - using System; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - - public struct SingleFloatStruct { - public float Value; - } - public struct SingleDoubleStruct { - public struct Nested1 { - // This field is private on purpose to ensure we treat visibility correctly - double Value; - } - public Nested1 Value; - } - public struct SingleI64Struct { - public Int64 Value; - } - public struct PairStruct { - public int A, B; - } - public unsafe struct MyFixedArray { - public fixed int elements[2]; - } - [System.Runtime.CompilerServices.InlineArray(2)] - public struct MyInlineArray { - public int element0; - } - - public class Test - { - public static unsafe int Main(string[] argv) - { - var i64_a = 0xFF00FF00FF00FF0L; - var i64_b = ~i64_a; - var resI = direct64(i64_a); - Console.WriteLine(""l (l)="" + resI); - - var sis = new SingleI64Struct { Value = i64_a }; - var resSI = indirect64(sis); - Console.WriteLine(""s (s)="" + resSI.Value); - - var resF = direct(3.14); - Console.WriteLine(""f (d)="" + resF); - - SingleDoubleStruct sds = default; - Unsafe.As(ref sds) = 3.14; - - resF = indirect_arg(sds); - Console.WriteLine(""f (s)="" + resF); - - var res = indirect(sds); - Console.WriteLine(""s (s)="" + res.Value); - - var pair = new PairStruct { A = 1, B = 2 }; - var paires = accept_and_return_pair(pair); - Console.WriteLine(""paires.B="" + paires.B); - - // This test is split into methods to simplify debugging issues with it - var ia = InlineArrayTest1(); - var iares = InlineArrayTest2(ia); - Console.WriteLine($""iares[0]={iares[0]} iares[1]={iares[1]}""); - - MyFixedArray fa = new (); - for (int i = 0; i < 2; i++) - fa.elements[i] = i; - var fares = accept_and_return_fixedarray(fa); - Console.WriteLine(""fares.elements[1]="" + fares.elements[1]); - - return (int)res.Value; - } - - public static unsafe MyInlineArray InlineArrayTest1 () { - MyInlineArray ia = new (); - for (int i = 0; i < 2; i++) - ia[i] = i; - return ia; - } - - public static unsafe MyInlineArray InlineArrayTest2 (MyInlineArray ia) { - return accept_and_return_inlinearray(ia); - } - - [DllImport(""wasm-abi"", EntryPoint=""accept_double_struct_and_return_float_struct"")] - public static extern SingleFloatStruct indirect(SingleDoubleStruct arg); - - [DllImport(""wasm-abi"", EntryPoint=""accept_double_struct_and_return_float_struct"")] - public static extern float indirect_arg(SingleDoubleStruct arg); - - [DllImport(""wasm-abi"", EntryPoint=""accept_double_struct_and_return_float_struct"")] - public static extern float direct(double arg); - - [DllImport(""wasm-abi"", EntryPoint=""accept_and_return_i64_struct"")] - public static extern SingleI64Struct indirect64(SingleI64Struct arg); - - [DllImport(""wasm-abi"", EntryPoint=""accept_and_return_i64_struct"")] - public static extern Int64 direct64(Int64 arg); - - [DllImport(""wasm-abi"", EntryPoint=""accept_and_return_pair"")] - public static extern PairStruct accept_and_return_pair(PairStruct arg); - - [DllImport(""wasm-abi"", EntryPoint=""accept_and_return_fixedarray"")] - public static extern MyFixedArray accept_and_return_fixedarray(MyFixedArray arg); - - [DllImport(""wasm-abi"", EntryPoint=""accept_and_return_inlinearray"")] - public static extern MyInlineArray accept_and_return_inlinearray(MyInlineArray arg); - }"; - - var extraProperties = "true<_WasmDevel>falsefalse"; var extraItems = @""; + var extraProperties = "true<_WasmDevel>falsefalse"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "abi", extraItems: extraItems, extraProperties: extraProperties); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "AbiRules.cs")); + string cCodeFilename = "wasm-abi.c"; + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", cCodeFilename), Path.Combine(_projectDir, cCodeFilename)); - buildArgs = ExpandBuildArgs(buildArgs, - extraItems: extraItems, - extraProperties: extraProperties); - - (string libraryDir, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText); - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "wasm-abi.c"), - Path.Combine(_projectDir!, "wasm-abi.c")); - }, - Publish: buildArgs.AOT, - // Verbosity: "diagnostic", - DotnetWasmFromRuntimePack: false)); + bool isPublish = aot; + (string _, string _) = isPublish ? + PublishProject(info, config, new PublishOptions(AOT: aot), isNativeBuild: true) : + BuildProject(info, config, new BuildOptions(AOT: aot), isNativeBuild: true); - string objDir = Path.Combine(_projectDir!, "obj", buildArgs.Config!, "net9.0", "browser-wasm", "wasm", buildArgs.AOT ? "for-publish" : "for-build"); + string objDir = Path.Combine(_projectDir, "obj", config.ToString(), DefaultTargetFramework, "wasm", isPublish ? "for-publish" : "for-build"); // Verify that the right signature was added for the pinvoke. We can't determine this by examining the m2n file // FIXME: Not possible in in-process mode for some reason, even with verbosity at "diagnostic" @@ -972,58 +336,60 @@ public static unsafe MyInlineArray InlineArrayTest2 (MyInlineArray ia) { Assert.Contains("float accept_double_struct_and_return_float_struct (double);", pinvokeTable); Assert.Contains("int64_t accept_and_return_i64_struct (int64_t);", pinvokeTable); - var runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 3, host: host, id: id); - Assert.Contains("l (l)=-1148435428713435121", runOutput); - Assert.Contains("s (s)=-1148435428713435121", runOutput); - Assert.Contains("f (d)=3.14", runOutput); - Assert.Contains("f (s)=3.14", runOutput); - Assert.Contains("s (s)=3.14", runOutput); - Assert.Contains("paires.B=4", runOutput); - Assert.Contains("iares[0]=32", runOutput); - Assert.Contains("iares[1]=2", runOutput); - Assert.Contains("fares.elements[1]=2", runOutput); + var runOptions = new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 3); + RunResult result = isPublish ? await RunForPublishWithWebServer(runOptions) : await RunForBuildWithDotnetRun(runOptions); + Assert.Contains("l (l)=-1148435428713435121", result.TestOutput); + Assert.Contains("s (s)=-1148435428713435121", result.TestOutput); + Assert.Contains("f (d)=3.14", result.TestOutput); + Assert.Contains("f (s)=3.14", result.TestOutput); + Assert.Contains("s (s)=3.14", result.TestOutput); + Assert.Contains("paires.B=4", result.TestOutput); + Assert.Contains(result.TestOutput, m => m.Contains("iares[0]=32")); + Assert.Contains(result.TestOutput, m => m.Contains("iares[1]=2")); + Assert.Contains("fares.elements[1]=2", result.TestOutput); } [Theory] - [BuildAndRun(host: RunHost.Chrome, aot: true)] - public void EnsureWasmAbiRulesAreFollowedInAOT(BuildArgs buildArgs, RunHost host, string id) => - EnsureWasmAbiRulesAreFollowed(buildArgs, host, id); + [BuildAndRun(aot: true, config: Configuration.Release)] + public async void EnsureWasmAbiRulesAreFollowedInAOT(Configuration config, bool aot) => + await EnsureWasmAbiRulesAreFollowed(config, aot); [Theory] - [BuildAndRun(host: RunHost.Chrome, aot: false)] - public void EnsureWasmAbiRulesAreFollowedInInterpreter(BuildArgs buildArgs, RunHost host, string id) => - EnsureWasmAbiRulesAreFollowed(buildArgs, host, id); + [BuildAndRun(aot: false)] + public async void EnsureWasmAbiRulesAreFollowedInInterpreter(Configuration config, bool aot) => + await EnsureWasmAbiRulesAreFollowed(config, aot); [Theory] - [BuildAndRun(host: RunHost.Chrome, aot: true)] - public void EnsureComInteropCompilesInAOT(BuildArgs buildArgs, RunHost host, string id) => - EnsureComInteropCompiles(buildArgs, host, id); + [BuildAndRun(aot: true, config: Configuration.Release)] + public void EnsureComInteropCompilesInAOT(Configuration config, bool aot) + { + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "com"); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "ComInterop.cs")); + bool isPublish = aot; + (string libraryDir, string output) = isPublish ? + PublishProject(info, config, new PublishOptions(AOT: aot)) : + BuildProject(info, config, new BuildOptions(AOT: aot)); + } [Theory] - [BuildAndRun(host: RunHost.Chrome, aot: false)] - public void UCOWithSpecialCharacters(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun(aot: false)] + public async void UCOWithSpecialCharacters(Configuration config, bool aot) { var extraProperties = "true"; var extraItems = @""; - - buildArgs = ExpandBuildArgs(buildArgs, - extraItems: extraItems, - extraProperties: extraProperties); - - (string libraryDir, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "Wasm.Buid.Tests.Programs", "UnmanagedCallback.cs"), Path.Combine(_projectDir!, "Program.cs")); - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "local.c"), Path.Combine(_projectDir!, "local.c")); - }, - Publish: true, - DotnetWasmFromRuntimePack: false)); - - var runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.DoesNotContain("Conflict.A.Managed8\u4F60Func(123) -> 123", runOutput); - Assert.Contains("ManagedFunc returned 42", runOutput); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "uoc", extraItems: extraItems, extraProperties: extraProperties); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "UnmanagedCallback.cs")); + string cCodeFilename = "local.c"; + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", cCodeFilename), Path.Combine(_projectDir, cCodeFilename)); + + PublishProject(info, config, new PublishOptions(AOT: aot), isNativeBuild: true); + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.DoesNotContain("Conflict.A.Managed8\u4F60Func(123) -> 123", result.TestOutput); + Assert.Contains("ManagedFunc returned 42", result.TestOutput); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTestsBase.cs new file mode 100644 index 00000000000000..983cd4742c8bd0 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTestsBase.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +#nullable enable + +namespace Wasm.Build.Tests +{ + public class PInvokeTableGeneratorTestsBase : WasmTemplateTestsBase + { + public PInvokeTableGeneratorTestsBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + protected string PublishForVariadicFunctionTests(ProjectInfo info, Configuration config, bool aot, string? verbosity = null, bool isNativeBuild = true) + { + string verbosityArg = verbosity == null ? string.Empty : $" -v:{verbosity}"; + // NativeFileReference forces native build + (_, string output) = PublishProject(info, + config, + new PublishOptions(ExtraMSBuildArgs: verbosityArg, AOT: aot), + isNativeBuild: isNativeBuild); + return output; + } + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs index 417bf4b8d2a51d..236d8f1901c0d3 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs @@ -20,6 +20,7 @@ namespace Wasm.Build.Tests; // For projects using WasmAppBuilder +// ToDo: REMOVE, use WasmSdkBasedProjectProvider only public abstract class ProjectProviderBase(ITestOutputHelper _testOutput, string? _projectDir) { public static string WasmAssemblyExtension = BuildTestBase.s_buildEnv.UseWebcil ? ".wasm" : ".dll"; @@ -38,12 +39,12 @@ public abstract class ProjectProviderBase(ITestOutputHelper _testOutput, string? protected BuildEnvironment _buildEnv = BuildTestBase.s_buildEnv; protected abstract string BundleDirName { get; } - public bool IsFingerprintingSupported { get; protected set; } + public bool IsFingerprintingEnabled => EnvironmentVariables.UseFingerprinting; - public bool IsFingerprintingEnabled => IsFingerprintingSupported && EnvironmentVariables.UseFingerprinting; + public bool IsFingerprintingOnDotnetJsEnabled => EnvironmentVariables.UseFingerprintingDotnetJS; // Returns the actual files on disk - public IReadOnlyDictionary AssertBasicBundle(AssertBundleOptionsBase assertOptions) + public IReadOnlyDictionary AssertBasicBundle(AssertBundleOptions assertOptions) { EnsureProjectDirIsSet(); var dotnetFiles = FindAndAssertDotnetFiles(assertOptions); @@ -77,17 +78,17 @@ public IReadOnlyDictionary AssertBasicBundle(AssertBundl return dotnetFiles; } - public IReadOnlyDictionary FindAndAssertDotnetFiles(AssertBundleOptionsBase assertOptions) + public IReadOnlyDictionary FindAndAssertDotnetFiles(AssertBundleOptions assertOptions) { EnsureProjectDirIsSet(); return FindAndAssertDotnetFiles(binFrameworkDir: assertOptions.BinFrameworkDir, - expectFingerprintOnDotnetJs: assertOptions.ExpectFingerprintOnDotnetJs, + expectFingerprintOnDotnetJs: IsFingerprintingOnDotnetJsEnabled, superSet: GetAllKnownDotnetFilesToFingerprintMap(assertOptions), expected: GetDotNetFilesExpectedSet(assertOptions)); } - protected abstract IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(AssertBundleOptionsBase assertOptions); - protected abstract IReadOnlySet GetDotNetFilesExpectedSet(AssertBundleOptionsBase assertOptions); + protected abstract IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(AssertBundleOptions assertOptions); + protected abstract IReadOnlySet GetDotNetFilesExpectedSet(AssertBundleOptions assertOptions); public IReadOnlyDictionary FindAndAssertDotnetFiles( string binFrameworkDir, @@ -201,34 +202,38 @@ private void AssertDotNetFilesSet( } } - public void CompareStat(IDictionary oldStat, IDictionary newStat, IEnumerable<(string fullpath, bool unchanged)> expected) + public void CompareStat(IDictionary oldStat, IDictionary newStat, IDictionary expected) { StringBuilder msg = new(); foreach (var expect in expected) { - string expectFilename = Path.GetFileName(expect.fullpath); - if (!oldStat.TryGetValue(expectFilename, out FileStat? oldFs)) + if (!oldStat.TryGetValue(expect.Key, out FileStat? oldFs)) { - msg.AppendLine($"Could not find an entry for {expectFilename} in old files"); + msg.AppendLine($"Could not find an entry for {expect.Key} in old files"); continue; } - if (!newStat.TryGetValue(expectFilename, out FileStat? newFs)) + if (!newStat.TryGetValue(expect.Key, out FileStat? newFs)) { - msg.AppendLine($"Could not find an entry for {expectFilename} in new files"); + msg.AppendLine($"Could not find an entry for {expect.Key} in new files"); continue; } - bool actualUnchanged = oldFs == newFs; - if (expect.unchanged && !actualUnchanged) + // files never existed existed => no change + // fingerprinting is enabled => can't compare paths + bool actualUnchanged = (!oldFs.Exists && !newFs.Exists) || + IsFingerprintingEnabled && (oldFs.Length == newFs.Length && oldFs.LastWriteTimeUtc == newFs.LastWriteTimeUtc) || + !IsFingerprintingEnabled && oldFs == newFs; + + if (expect.Value.unchanged && !actualUnchanged) { - msg.AppendLine($"[Expected unchanged file: {expectFilename}]{Environment.NewLine}" + + msg.AppendLine($"[Expected unchanged file: {expect.Key}]{Environment.NewLine}" + $" old: {oldFs}{Environment.NewLine}" + $" new: {newFs}"); } - else if (!expect.unchanged && actualUnchanged) + else if (!expect.Value.unchanged && actualUnchanged) { - msg.AppendLine($"[Expected changed file: {expectFilename}]{Environment.NewLine}" + + msg.AppendLine($"[Expected changed file: {expect.Key}]{Environment.NewLine}" + $" {newFs}"); } } @@ -237,31 +242,75 @@ public void CompareStat(IDictionary oldStat, IDictionary StatFiles(IEnumerable fullpaths) + public IDictionary StatFiles(IDictionary pathsDict) { Dictionary table = new(); - foreach (string file in fullpaths) + foreach (var fileInfo in pathsDict) { - if (File.Exists(file)) - table.Add(Path.GetFileName(file), new FileStat(FullPath: file, Exists: true, LastWriteTimeUtc: File.GetLastWriteTimeUtc(file), Length: new FileInfo(file).Length)); + string file = fileInfo.Value.fullPath; + string nameNoFingerprinting = fileInfo.Key; + bool exists = File.Exists(file); + if (exists) + { + table.Add(nameNoFingerprinting, new FileStat(FullPath: file, Exists: true, LastWriteTimeUtc: File.GetLastWriteTimeUtc(file), Length: new FileInfo(file).Length)); + } else - table.Add(Path.GetFileName(file), new FileStat(FullPath: file, Exists: false, LastWriteTimeUtc: DateTime.MinValue, Length: 0)); + { + table.Add(nameNoFingerprinting, new FileStat(FullPath: file, Exists: false, LastWriteTimeUtc: DateTime.MinValue, Length: 0)); + } } return table; } - public static string FindSubDirIgnoringCase(string parentDir, string dirName) + public IDictionary StatFilesAfterRebuild(IDictionary pathsDict) + { + if (!IsFingerprintingEnabled) + return StatFiles(pathsDict); + + // files are expected to be fingerprinted, so we cannot rely on the paths that come with pathsDict, an update is needed + Dictionary table = new(); + foreach (var fileInfo in pathsDict) + { + string file = fileInfo.Value.fullPath; + string nameNoFingerprinting = fileInfo.Key; + string[] filesMatchingName = GetFilesMatchingNameConsideringFingerprinting(file, nameNoFingerprinting); + if (filesMatchingName.Length > 1) + { + string? fileMatch = filesMatchingName.FirstOrDefault(f => f != file); + if (fileMatch != null) + { + table.Add(nameNoFingerprinting, new FileStat(FullPath: fileMatch, Exists: true, LastWriteTimeUtc: File.GetLastWriteTimeUtc(fileMatch), Length: new FileInfo(fileMatch).Length)); + } + } + if (filesMatchingName.Length == 0 || (filesMatchingName.Length == 1 && !File.Exists(file))) + { + table.Add(nameNoFingerprinting, new FileStat(FullPath: file, Exists: false, LastWriteTimeUtc: DateTime.MinValue, Length: 0)); + } + if (filesMatchingName.Length == 1 && File.Exists(file)) + { + table.Add(nameNoFingerprinting, new FileStat(FullPath: file, Exists: true, LastWriteTimeUtc: File.GetLastWriteTimeUtc(file), Length: new FileInfo(file).Length)); + } + } + return table; + } + + private string[] GetFilesMatchingNameConsideringFingerprinting(string filePath, string nameNoFingerprinting) { - IEnumerable matchingDirs = Directory.EnumerateDirectories(parentDir, - dirName, - new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }); + var directory = Path.GetDirectoryName(filePath); + if (directory == null) + return Array.Empty(); - string? first = matchingDirs.FirstOrDefault(); - if (matchingDirs.Count() > 1) - throw new Exception($"Found multiple directories with names that differ only in case. {string.Join(", ", matchingDirs.ToArray())}"); + string fileNameWithoutExtensionAndFingerprinting = Path.GetFileNameWithoutExtension(nameNoFingerprinting); + string fileExtension = Path.GetExtension(filePath); - return first ?? Path.Combine(parentDir, dirName); + // search for files that match the name in the directory, skipping fingerprinting + string[] files = Directory.GetFiles(directory, $"{fileNameWithoutExtensionAndFingerprinting}*{fileExtension}"); + + // filter files with a single fingerprint segment, e.g. "dotnet*.js" should not catch "dotnet.native.d1au9i.js" but should catch "dotnet.js" + string pattern = $@"^{Regex.Escape(fileNameWithoutExtensionAndFingerprinting)}(\.[^.]+)?{Regex.Escape(fileExtension)}$"; + var tmp = files.Where(f => Regex.IsMatch(Path.GetFileName(f), pattern)).ToArray(); + return tmp; } public IDictionary GetFilesTable(bool unchanged, params string[] baseDirs) @@ -276,11 +325,11 @@ public static string FindSubDirIgnoringCase(string parentDir, string dirName) return dict; } - public IDictionary GetFilesTable(BuildArgs buildArgs, BuildPaths paths, bool unchanged) + public IDictionary GetFilesTable(string projectName, bool isAOT, BuildPaths paths, bool unchanged) { List files = new() { - Path.Combine(paths.BinDir, "publish", $"{buildArgs.ProjectName}.dll"), + Path.Combine(paths.BinDir, "publish", BundleDirName, "_framework", $"{projectName}{WasmAssemblyExtension}"), Path.Combine(paths.ObjWasmDir, "driver.o"), Path.Combine(paths.ObjWasmDir, "runtime.o"), Path.Combine(paths.ObjWasmDir, "corebindings.o"), @@ -290,20 +339,20 @@ public static string FindSubDirIgnoringCase(string parentDir, string dirName) Path.Combine(paths.ObjWasmDir, "pinvoke-table.h"), Path.Combine(paths.ObjWasmDir, "driver-gen.c"), - Path.Combine(paths.BundleDir, "_framework", "dotnet.native.wasm"), - Path.Combine(paths.BundleDir, "_framework", "dotnet.native.js"), - Path.Combine(paths.BundleDir, "_framework", "dotnet.globalization.js"), + Path.Combine(paths.BinFrameworkDir, "dotnet.native.wasm"), + Path.Combine(paths.BinFrameworkDir, "dotnet.native.js"), + Path.Combine(paths.BinFrameworkDir, "dotnet.globalization.js"), }; - if (buildArgs.AOT) + if (isAOT) { files.AddRange(new[] { - Path.Combine(paths.ObjWasmDir, $"{buildArgs.ProjectName}.dll.bc"), - Path.Combine(paths.ObjWasmDir, $"{buildArgs.ProjectName}.dll.o"), + Path.Combine(paths.ObjWasmDir, $"{projectName}.dll.bc"), + Path.Combine(paths.ObjWasmDir, $"{projectName}.dll.o"), - Path.Combine(paths.ObjWasmDir, "System.Private.CoreLib.dll.bc"), - Path.Combine(paths.ObjWasmDir, "System.Private.CoreLib.dll.o"), + Path.Combine(paths.ObjWasmDir, $"System.Private.CoreLib.dll.bc"), + Path.Combine(paths.ObjWasmDir, $"System.Private.CoreLib.dll.o"), }); } @@ -312,12 +361,38 @@ public static string FindSubDirIgnoringCase(string parentDir, string dirName) dict[Path.GetFileName(file)] = (file, unchanged); // those files do not change on re-link - dict["dotnet.js"]=(Path.Combine(paths.BundleDir, "_framework", "dotnet.js"), true); - dict["dotnet.js.map"]=(Path.Combine(paths.BundleDir, "_framework", "dotnet.js.map"), true); - dict["dotnet.runtime.js"]=(Path.Combine(paths.BundleDir, "_framework", "dotnet.runtime.js"), true); - dict["dotnet.runtime.js.map"]=(Path.Combine(paths.BundleDir, "_framework", "dotnet.runtime.js.map"), true); - dict["dotnet.globalization.js"]=(Path.Combine(paths.BundleDir, "_framework", "dotnet.globalization.js"), true); + dict["dotnet.js"]=(Path.Combine(paths.BinFrameworkDir, "dotnet.js"), true); + dict["dotnet.js.map"]=(Path.Combine(paths.BinFrameworkDir, "dotnet.js.map"), true); + dict["dotnet.runtime.js"]=(Path.Combine(paths.BinFrameworkDir, "dotnet.runtime.js"), true); + dict["dotnet.runtime.js.map"]=(Path.Combine(paths.BinFrameworkDir, "dotnet.runtime.js.map"), true); + dict["dotnet.globalization.js"]=(Path.Combine(paths.BinFrameworkDir, "dotnet.globalization.js"), true); + + if (IsFingerprintingEnabled) + { + string bootJsonPath = Path.Combine(paths.BinFrameworkDir, "blazor.boot.json"); + BootJsonData bootJson = GetBootJson(bootJsonPath); + var keysToUpdate = new List(); + var updates = new List<(string oldKey, string newKey, (string fullPath, bool unchanged) value)>(); + foreach (var expectedItem in dict) + { + string filename = Path.GetFileName(expectedItem.Value.fullPath); + var expectedFingerprintedItem = bootJson.resources.fingerprinting + .Where(kv => kv.Value == filename) + .SingleOrDefault().Key; + + if (string.IsNullOrEmpty(expectedFingerprintedItem)) + continue; + if (filename != expectedFingerprintedItem) + { + string newKey = Path.Combine( + Path.GetDirectoryName(expectedItem.Value.fullPath) ?? "", + expectedFingerprintedItem + ); + dict[filename] = (newKey, expectedItem.Value.unchanged); + } + } + } return dict; } @@ -337,23 +412,23 @@ public static void AssertRuntimePackPath(string buildOutput, string targetFramew throw new XunitException($"Runtime pack path doesn't match.{Environment.NewLine}Expected: '{expectedRuntimePackDir}'{Environment.NewLine}Actual: '{actualPath}'"); } - public static void AssertDotNetJsSymbols(AssertBundleOptionsBase assertOptions) + public static void AssertDotNetJsSymbols(AssertBundleOptions assertOptions) { TestUtils.AssertFilesExist(assertOptions.BinFrameworkDir, new[] { "dotnet.native.js.symbols" }, expectToExist: assertOptions.ExpectSymbolsFile); - if (assertOptions.ExpectedFileType == NativeFilesType.FromRuntimePack) + if (assertOptions.BuildOptions.ExpectedFileType == NativeFilesType.FromRuntimePack) { TestUtils.AssertFile( - Path.Combine(BuildTestBase.s_buildEnv.GetRuntimeNativeDir(assertOptions.TargetFramework, assertOptions.RuntimeType), "dotnet.native.js.symbols"), + Path.Combine(BuildTestBase.s_buildEnv.GetRuntimeNativeDir(assertOptions.BuildOptions.TargetFramework, assertOptions.BuildOptions.RuntimeType), "dotnet.native.js.symbols"), Path.Combine(assertOptions.BinFrameworkDir, "dotnet.native.js.symbols"), same: true); } } - public void AssertIcuAssets(AssertBundleOptionsBase assertOptions, BootJsonData bootJson) + public void AssertIcuAssets(AssertBundleOptions assertOptions, BootJsonData bootJson) { List expected = new(); - switch (assertOptions.GlobalizationMode) + switch (assertOptions.BuildOptions.GlobalizationMode) { case GlobalizationMode.Invariant: break; @@ -365,11 +440,11 @@ public void AssertIcuAssets(AssertBundleOptionsBase assertOptions, BootJsonData expected.Add("segmentation-rules.json"); break; case GlobalizationMode.Custom: - if (string.IsNullOrEmpty(assertOptions.CustomIcuFile)) + if (string.IsNullOrEmpty(assertOptions.BuildOptions.CustomIcuFile)) throw new ArgumentException("WasmBuildTest is invalid, value for Custom globalization mode is required when GlobalizationMode=Custom."); // predefined ICU name can be identical with the icu files from runtime pack - expected.Add(Path.GetFileName(assertOptions.CustomIcuFile)); + expected.Add(Path.GetFileName(assertOptions.BuildOptions.CustomIcuFile)); break; case GlobalizationMode.Sharded: // icu shard chosen based on the locale @@ -378,11 +453,11 @@ public void AssertIcuAssets(AssertBundleOptionsBase assertOptions, BootJsonData expected.Add("icudt_no_CJK.dat"); break; default: - throw new NotImplementedException($"Unknown {nameof(assertOptions.GlobalizationMode)} = {assertOptions.GlobalizationMode}"); + throw new NotImplementedException($"Unknown {nameof(assertOptions.BuildOptions.GlobalizationMode)} = {assertOptions.BuildOptions.GlobalizationMode}"); } IEnumerable actual = Directory.EnumerateFiles(assertOptions.BinFrameworkDir, "icudt*dat"); - if (assertOptions.GlobalizationMode == GlobalizationMode.Hybrid) + if (assertOptions.BuildOptions.GlobalizationMode == GlobalizationMode.Hybrid) actual = actual.Union(Directory.EnumerateFiles(assertOptions.BinFrameworkDir, "segmentation-rules*json")); if (IsFingerprintingEnabled) @@ -401,24 +476,27 @@ public void AssertIcuAssets(AssertBundleOptionsBase assertOptions, BootJsonData } AssertFileNames(expected, actual); - if (assertOptions.GlobalizationMode is GlobalizationMode.Custom) + if (assertOptions.BuildOptions.GlobalizationMode is GlobalizationMode.Custom) { - string srcPath = assertOptions.CustomIcuFile!; - string runtimePackDir = BuildTestBase.s_buildEnv.GetRuntimeNativeDir(assertOptions.TargetFramework, assertOptions.RuntimeType); + string srcPath = assertOptions.BuildOptions.CustomIcuFile!; + string runtimePackDir = BuildTestBase.s_buildEnv.GetRuntimeNativeDir(assertOptions.BuildOptions.TargetFramework, assertOptions.BuildOptions.RuntimeType); if (!Path.IsPathRooted(srcPath)) - srcPath = Path.Combine(runtimePackDir, assertOptions.CustomIcuFile!); + srcPath = Path.Combine(runtimePackDir, assertOptions.BuildOptions.CustomIcuFile!); TestUtils.AssertSameFile(srcPath, actual.Single()); } } - public BootJsonData AssertBootJson(AssertBundleOptionsBase options) + private BootJsonData GetBootJson(string bootJsonPath) { - EnsureProjectDirIsSet(); - string binFrameworkDir = options.BinFrameworkDir; - string bootJsonPath = Path.Combine(binFrameworkDir, options.BootJsonFileName); Assert.True(File.Exists(bootJsonPath), $"Expected to find {bootJsonPath}"); + return ParseBootData(bootJsonPath); + } - BootJsonData bootJson = ParseBootData(bootJsonPath); + public BootJsonData AssertBootJson(AssertBundleOptions options) + { + EnsureProjectDirIsSet(); + string bootJsonPath = Path.Combine(options.BinFrameworkDir, options.BuildOptions.BootConfigFileName); + BootJsonData bootJson = GetBootJson(bootJsonPath); string spcExpectedFilename = $"System.Private.CoreLib{WasmAssemblyExtension}"; if (IsFingerprintingEnabled) @@ -459,7 +537,7 @@ public BootJsonData AssertBootJson(AssertBundleOptionsBase options) string extension = Path.GetExtension(expectedFilename).Substring(1); if (ShouldCheckFingerprint(expectedFilename: expectedFilename, - expectFingerprintOnDotnetJs: options.ExpectFingerprintOnDotnetJs, + expectFingerprintOnDotnetJs: IsFingerprintingOnDotnetJsEnabled, expectFingerprintForThisFile: expectFingerprint)) { return Regex.Match(item, $"{prefix}{s_dotnetVersionHashRegex}{extension}").Success; @@ -519,7 +597,7 @@ private void AssertFileNames(IEnumerable expected, IEnumerable a Assert.Equal(expected, actualFileNames); } - public virtual string FindBinFrameworkDir(string config, bool forPublish, string framework, string? projectDir = null) + public virtual string GetBinFrameworkDir(Configuration config, bool forPublish, string framework, string? projectDir = null) { throw new NotImplementedException(); } diff --git a/src/mono/wasm/Wasm.Build.Tests/README.md b/src/mono/wasm/Wasm.Build.Tests/README.md index 19fcaa84cf3c3a..be629ccae43140 100644 --- a/src/mono/wasm/Wasm.Build.Tests/README.md +++ b/src/mono/wasm/Wasm.Build.Tests/README.md @@ -18,7 +18,8 @@ Linux/macOS: `$ make -C src/mono/(browser|wasi) run-build-tests` Windows: `.\dotnet.cmd build .\src\mono\wasm\Wasm.Build.Tests\Wasm.Build.Tests.csproj -c Release -t:Test -p:TargetOS=browser -p:TargetArchitecture=wasm` - Specific tests can be run via `XUnitClassName`, and `XUnitMethodName` - - eg. `XUnitClassName=Wasm.Build.Tests.BlazorWasmTests` + - e.g. `XUnitClassName=Wasm.Build.Tests.BlazorWasmTests` for running class of tests + - e.g. `XUnitMethodName=Wasm.Build.Tests.Blazor.MiscTests3.WithDllImportInMainAssembly` for running a specific test. ## Running on helix @@ -32,9 +33,9 @@ Most of the tests are structured on the idea that for a given case (or combination of options), we want to: 1. build once -2. run the same build with different hosts, eg. V8, Chrome, Firefox etc. +2. run the same build with different hosts, eg. Chrome, Firefox etc. -For this, the builds get cached using `BuildArgs` as the key. +For this, the builds get cached using `ProjectInfo` as the key. ## notes: @@ -53,10 +54,15 @@ For this, the builds get cached using `BuildArgs` as the key. use the build bits from the usual locations in artifacts, without requiring regenerating the nugets, and workload re-install. -- Each test gets a randomly generated "id". This `id` can be used to find the - binlogs, or the test directories. +- Each test is saved in directory with randomly generated name and unique `ProjectInfo`. `ProjectInfo` can be used to find the binlogs, or cached builds. ## Useful environment variables - `SHOW_BUILD_OUTPUT` - will show the build output to the console - `SKIP_PROJECT_CLEANUP` - won't remove the temporary project directories generated for the tests + +## How to add tests + +Blazor specific tests should be located in `Blazor` directory. If you are adding a new class with tests, list it in `eng/testing/scenarios/BuildWasmAppsJobsList.txt`. If you are adding a new test to existing class, make sure it does not prolong the execution time significantly. Tests run on parallel on CI and having one class running much longer than the average prolongs the total execution time. + +If you want to test templating mechanism, use `CreateWasmTemplateProject`. Otherwise, use `CopyTestAsset` with either `WasmBasicTestApp` or `BlazorBasicTestApp`, adding your custom `TestScenario` or using a generic `DotnetRun` scenario in case of WASM app and adding a page with test in case of Blazor app. Bigger snippets of code should be saved in `src/mono/wasm/testassets` and placed in the application using methods: `ReplaceFile` or `File.Move`. Replacing existing small parts of code with custom lines is done with `UpdateFile`. \ No newline at end of file diff --git a/src/mono/wasm/Wasm.Build.Tests/RebuildTests.cs b/src/mono/wasm/Wasm.Build.Tests/RebuildTests.cs index 0a316a4b601c75..a3b8c189f26531 100644 --- a/src/mono/wasm/Wasm.Build.Tests/RebuildTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/RebuildTests.cs @@ -14,40 +14,28 @@ namespace Wasm.Build.Tests { - public class RebuildTests : TestMainJsTestBase + public class RebuildTests : WasmTemplateTestsBase { public RebuildTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - public static IEnumerable NonNativeDebugRebuildData() - => ConfigWithAOTData(aot: false, config: "Debug") - .WithRunHosts(RunHost.Chrome) - .UnwrapItemsAsArrays().ToList(); - [Theory] - [MemberData(nameof(NonNativeDebugRebuildData))] - public async Task NoOpRebuild(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun(aot: false, config: Configuration.Debug)] + public async Task NoOpRebuild(Configuration config, bool aot) { - string projectName = $"rebuild_{buildArgs.Config}_{buildArgs.AOT}"; - - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: true, - CreateProject: true)); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "rebuild"); + UpdateFile(Path.Combine("Common", "Program.cs"), s_mainReturns42); + PublishProject(info, config); - Run(); + BrowserRunOptions runOptions = new(config, TestScenario: "DotnetRun", ExpectedExitCode: 42); + await RunForPublishWithWebServer(runOptions); - if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product)) - throw new XunitException($"Test bug: could not get the build product in the cache"); + if (!_buildContext.TryGetBuildFor(info, out BuildResult? result)) + throw new XunitException($"Test bug: could not get the build result in the cache"); - File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog")); + File.Move(result!.LogFile, Path.ChangeExtension(result.LogFile!, ".first.binlog")); // artificial delay to have new enough timestamps await Task.Delay(5000); @@ -55,19 +43,8 @@ public async Task NoOpRebuild(BuildArgs buildArgs, RunHost host, string id) _testOutput.WriteLine($"{Environment.NewLine}Rebuilding with no changes ..{Environment.NewLine}"); // no-op Rebuild - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - DotnetWasmFromRuntimePack: true, - CreateProject: false, - UseCache: false)); - - Run(); - - void Run() => RunAndTestWasmApp( - buildArgs, buildDir: _projectDir, expectedExitCode: 42, - test: output => {}, - host: host, id: id); + PublishProject(info, config, new PublishOptions(UseCache: false)); + await RunForPublishWithWebServer(runOptions); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs b/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs index 0f2a4dc1365525..c74a21b82d86bc 100644 --- a/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs @@ -12,214 +12,117 @@ namespace Wasm.Build.Tests { - public class SatelliteAssembliesTests : TestMainJsTestBase + public class SatelliteAssembliesTests : WasmTemplateTestsBase { public SatelliteAssembliesTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - public static IEnumerable SatelliteAssemblyTestData(bool aot, bool relinking, RunHost host) + public static IEnumerable SatelliteAssemblyTestData(bool aot, bool relinking) => ConfigWithAOTData(aot) .Multiply( new object?[] { relinking, "es-ES" }, new object?[] { relinking, null }, new object?[] { relinking, "ja-JP" }) - .WithRunHosts(host) + .Where(item => !(item.ElementAt(0) is Configuration config && config == Configuration.Debug && item.ElementAt(1) is bool aotValue && aotValue)) .UnwrapItemsAsArrays(); [Theory] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ false, RunHost.All })] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ true, RunHost.All })] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ true, /*relinking*/ false, RunHost.All })] - public void ResourcesFromMainAssembly(BuildArgs buildArgs, - bool nativeRelink, - string? argCulture, - RunHost host, - string id) + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ false })] + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ true })] + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ true, /*relinking*/ false })] + public async void ResourcesFromMainAssembly(Configuration config, bool aot, bool nativeRelink, string? argCulture) { - string projectName = $"sat_asm_from_main_asm"; - // Release+publish defaults to native relinking - bool dotnetWasmFromRuntimePack = !nativeRelink && !buildArgs.AOT && buildArgs.Config != "Release"; - + string prefix = $"sat_asm_from_main_asm"; string extraProperties = (nativeRelink ? $"true" : string.Empty) // make ASSERTIONS=1 so that we test with it + $"-O0 -sASSERTIONS=1" + $"-O1"; - - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, - projectTemplate: s_resourcesProjectTemplate, - extraProperties: extraProperties); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "resx"), Path.Combine(_projectDir!, "resx")); - CreateProgramForCultureTest(_projectDir!, $"{projectName}.resx.words", "TestClass"); - }, - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack)); - - RunAndTestWasmApp( - buildArgs, expectedExitCode: 42, - args: argCulture, - host: host, id: id, - // check that downloading assets doesn't have timing race conditions - extraXHarnessMonoArgs: host is RunHost.Chrome ? "--fetch-random-delay=200" : string.Empty); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, prefix, extraProperties: extraProperties); + Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "resx"), Path.Combine(_projectDir, "resx")); + CreateProgramForCultureTest($"{info.ProjectName}.resx.words", "TestClass"); + + (_, string output) = PublishProject(info, config, new PublishOptions(UseCache: false, AOT: aot), isNativeBuild: nativeRelink ? true : null); + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42, + Locale: argCulture ?? "en-US", + // check that downloading assets doesn't have timing race conditions + ExtraArgs: "--fetch-random-delay=200" + )); } [Theory] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ false, RunHost.All })] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ true, RunHost.All })] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ true, /*relinking*/ false, RunHost.All })] - public void ResourcesFromProjectReference(BuildArgs buildArgs, - bool nativeRelink, - string? argCulture, - RunHost host, - string id) + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ false })] + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ true })] + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ true, /*relinking*/ false })] + public async void ResourcesFromProjectReference(Configuration config, bool aot, bool nativeRelink, string? argCulture) { - string projectName = $"SatelliteAssemblyFromProjectRef"; - bool dotnetWasmFromRuntimePack = !nativeRelink && !buildArgs.AOT; - + string prefix = $"SatelliteAssemblyFromProjectRef"; string extraProperties = $"{(nativeRelink ? "true" : "false")}" // make ASSERTIONS=1 so that we test with it + $"-O0 -sASSERTIONS=1" + $"-O1"; - - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, - projectTemplate: s_resourcesProjectTemplate, - extraProperties: extraProperties, - extraItems: $""); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, - InitProject: () => - { - string rootDir = _projectDir!; - _projectDir = Path.Combine(rootDir, projectName); - - Directory.CreateDirectory(_projectDir); - Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, projectName), rootDir); - - // D.B.* used for wasm projects should be moved next to the wasm project, so it doesn't - // affect the non-wasm library project - File.Move(Path.Combine(rootDir, "Directory.Build.props"), Path.Combine(_projectDir, "Directory.Build.props")); - File.Move(Path.Combine(rootDir, "Directory.Build.targets"), Path.Combine(_projectDir, "Directory.Build.targets")); - if (UseWBTOverridePackTargets) - File.Move(Path.Combine(rootDir, "WasmOverridePacks.targets"), Path.Combine(_projectDir, "WasmOverridePacks.targets")); - - CreateProgramForCultureTest(_projectDir, "LibraryWithResources.resx.words", "LibraryWithResources.Class1"); - - // The root D.B* should be empty - File.WriteAllText(Path.Combine(rootDir, "Directory.Build.props"), ""); - File.WriteAllText(Path.Combine(rootDir, "Directory.Build.targets"), ""); - })); - - RunAndTestWasmApp(buildArgs, - expectedExitCode: 42, - args: argCulture, - host: host, id: id); + string extraItems = $""; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, prefix, extraProperties: extraProperties, extraItems: extraItems); + // D.B.* used for wasm projects should be moved next to the wasm project, so it doesn't + // affect the non-wasm library project + File.Move(Path.Combine(_projectDir, "..", "Directory.Build.props"), Path.Combine(_projectDir, "Directory.Build.props")); + File.Move(Path.Combine(_projectDir, "..", "Directory.Build.targets"), Path.Combine(_projectDir, "Directory.Build.targets")); + if (UseWBTOverridePackTargets) + File.Move(Path.Combine(BuildEnvironment.TestDataPath, "WasmOverridePacks.targets"), Path.Combine(_projectDir, "WasmOverridePacks.targets")); + Utils.DirectoryCopy( + Path.Combine(BuildEnvironment.TestAssetsPath, "SatelliteAssemblyFromProjectRef/LibraryWithResources"), + Path.Combine(_projectDir, "..", "LibraryWithResources")); + CreateProgramForCultureTest("LibraryWithResources.resx.words", "LibraryWithResources.Class1"); + // move src/mono/wasm/testassets/SatelliteAssemblyFromProjectRef/LibraryWithResources to the test project + // The root D.B* should be empty + File.WriteAllText(Path.Combine(_projectDir, "..", "Directory.Build.props"), ""); + File.WriteAllText(Path.Combine(_projectDir, "..", "Directory.Build.targets"), ""); + // NativeFilesType dotnetWasmFileType = nativeRelink ? NativeFilesType.Relinked : aot ? NativeFilesType.AOT : NativeFilesType.FromRuntimePack; + + PublishProject(info, config, new PublishOptions(AOT: aot), isNativeBuild: nativeRelink); + + await RunForPublishWithWebServer( + new BrowserRunOptions(Configuration: config, TestScenario: "DotnetRun", ExpectedExitCode: 42, Locale: argCulture ?? "en-US")); } #pragma warning disable xUnit1026 [Theory] - [BuildAndRun(host: RunHost.None, aot: true)] - public void CheckThatSatelliteAssembliesAreNotAOTed(BuildArgs buildArgs, string id) + [BuildAndRun(aot: true, config: Configuration.Release)] + public void CheckThatSatelliteAssembliesAreNotAOTed(Configuration config, bool aot) { - string projectName = $"check_sat_asm_not_aot"; - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, - projectTemplate: s_resourcesProjectTemplate, - extraProperties: $@" - -O1 - -O1 - false", // -O0 can cause aot-instances.dll to blow up, and fail to compile, and it is not really needed here - extraItems: $""); + string extraProperties = $@"-O1 + -O1 + false"; // -O0 can cause aot-instances.dll to blow up, and fail to compile, and it is not really needed here + string extraItems = $""; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "check_sat_asm_not_aot", extraProperties: extraProperties, extraItems: extraItems); + CreateProgramForCultureTest($"{info.ProjectName}.words", "TestClass"); - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => CreateProgramForCultureTest(_projectDir!, $"{projectName}.words", "TestClass"), - DotnetWasmFromRuntimePack: false)); + PublishProject(info, config, new PublishOptions(AOT: aot)); - var bitCodeFileNames = Directory.GetFileSystemEntries(Path.Combine(_projectDir!, "obj"), "*.dll.bc", SearchOption.AllDirectories) + var bitCodeFileNames = Directory.GetFileSystemEntries(Path.Combine(_projectDir, "obj"), "*.dll.bc", SearchOption.AllDirectories) .Select(path => Path.GetFileName(path)) .ToArray(); // sanity check, in case we change file extensions - Assert.Contains($"{projectName}.dll.bc", bitCodeFileNames); + Assert.Contains($"{info.ProjectName}.dll.bc", bitCodeFileNames); Assert.Empty(bitCodeFileNames.Where(file => file.EndsWith(".resources.dll.bc"))); } #pragma warning restore xUnit1026 - private void CreateProgramForCultureTest(string dir, string resourceName, string typeName) - => File.WriteAllText(Path.Combine(dir, "Program.cs"), - s_cultureResourceTestProgram - .Replace("##RESOURCE_NAME##", resourceName) - .Replace("##TYPE_NAME##", typeName)); - - private const string s_resourcesProjectTemplate = - @$" - - {DefaultTargetFramework} - browser-wasm - Exe - true - test-main.js - ##EXTRA_PROPERTIES## - - - ##EXTRA_ITEMS## - - ##INSERT_AT_END## - "; - - private static string s_cultureResourceTestProgram = @" -using System; -using System.Runtime.CompilerServices; -using System.Globalization; -using System.Resources; -using System.Threading; - -namespace ResourcesTest -{ - public class TestClass - { - public static int Main(string[] args) + private void CreateProgramForCultureTest(string resourceName, string typeName) { - string expected; - if (args.Length == 1) - { - string cultureToTest = args[0]; - var newCulture = new CultureInfo(cultureToTest); - Thread.CurrentThread.CurrentCulture = newCulture; - Thread.CurrentThread.CurrentUICulture = newCulture; - - if (cultureToTest == ""es-ES"") - expected = ""hola""; - else if (cultureToTest == ""ja-JP"") - expected = ""\u3053\u3093\u306B\u3061\u306F""; - else - throw new Exception(""Cannot determine the expected output for {cultureToTest}""); - - } else { - expected = ""hello""; - } - - var currentCultureName = Thread.CurrentThread.CurrentCulture.Name; - - var rm = new ResourceManager(""##RESOURCE_NAME##"", typeof(##TYPE_NAME##).Assembly); - Console.WriteLine($""For '{currentCultureName}' got: {rm.GetString(""hello"")}""); - - return rm.GetString(""hello"") == expected ? 42 : -1; + string programRelativePath = Path.Combine("Common", "Program.cs"); + ReplaceFile(programRelativePath, Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "CultureResource.cs")); + var replacements = new Dictionary { + {"##RESOURCE_NAME##", resourceName}, + {"##TYPE_NAME##", typeName} + }; + UpdateFile(programRelativePath, replacements); } } -}"; - } } diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/SatelliteLoadingTests.cs similarity index 73% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs rename to src/mono/wasm/Wasm.Build.Tests/SatelliteLoadingTests.cs index ea1037e27872ac..1dcd957811738c 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/SatelliteLoadingTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Text; @@ -15,9 +16,9 @@ #nullable enable -namespace Wasm.Build.Tests.TestAppScenarios; +namespace Wasm.Build.Tests; -public class SatelliteLoadingTests : AppTestBase +public class SatelliteLoadingTests : WasmTemplateTestsBase { public SatelliteLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -29,13 +30,14 @@ public SatelliteLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFi [InlineData(true)] public async Task LoadSatelliteAssembly(bool loadAllSatelliteResources) { - CopyTestAsset("WasmBasicTestApp", "SatelliteLoadingTests", "App"); - BuildProject("Debug"); + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "SatelliteLoadingTests"); + BuildProject(info, config); - var result = await RunSdkStyleAppForBuild(new( - Configuration: "Debug", + var result = await RunForBuildWithDotnetRun(new BrowserRunOptions( + Configuration: config, TestScenario: "SatelliteAssembliesTest", - BrowserQueryString: new Dictionary { ["loadAllSatelliteResources"] = loadAllSatelliteResources.ToString().ToLowerInvariant() } + BrowserQueryString: new NameValueCollection { {"loadAllSatelliteResources", loadAllSatelliteResources.ToString().ToLowerInvariant() } } )); var expectedOutput = new List>(); @@ -59,10 +61,11 @@ public async Task LoadSatelliteAssembly(bool loadAllSatelliteResources) [Fact] public async Task LoadSatelliteAssemblyFromReference() { - CopyTestAsset("WasmBasicTestApp", "SatelliteLoadingTestsFromReference", "App"); + Configuration config = Configuration.Release; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "SatelliteLoadingTestsFromReference"); // Replace ProjectReference with Reference - var appCsprojPath = Path.Combine(_projectDir!, "WasmBasicTestApp.csproj"); + var appCsprojPath = Path.Combine(_projectDir, "WasmBasicTestApp.csproj"); var appCsproj = XDocument.Load(appCsprojPath); var projectReference = appCsproj.Descendants("ProjectReference").Where(pr => pr.Attribute("Include")?.Value?.Contains("ResourceLibrary") ?? false).Single(); @@ -76,7 +79,7 @@ public async Task LoadSatelliteAssemblyFromReference() appCsproj.Save(appCsprojPath); // Build the library - var libraryCsprojPath = Path.GetFullPath(Path.Combine(_projectDir!, "..", "ResourceLibrary")); + var libraryCsprojPath = Path.GetFullPath(Path.Combine(_projectDir, "..", "ResourceLibrary")); using DotNetCommand cmd = new DotNetCommand(s_buildEnv, _testOutput); CommandResult res = cmd.WithWorkingDirectory(libraryCsprojPath) .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) @@ -84,9 +87,9 @@ public async Task LoadSatelliteAssemblyFromReference() .EnsureSuccessful(); // Publish the app and assert - PublishProject("Release"); + PublishProject(info, config); - var result = await RunSdkStyleAppForPublish(new(Configuration: "Release", TestScenario: "SatelliteAssembliesTest")); + var result = await RunForPublishWithWebServer(new BrowserRunOptions(Configuration: Configuration.Release, TestScenario: "SatelliteAssembliesTest")); Assert.Collection( result.TestOutput, m => Assert.Equal("default: hello", m), diff --git a/src/mono/wasm/Wasm.Build.Tests/SignalRTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/SignalRTestsBase.cs index 183d984b39b481..5987db9992f1bc 100644 --- a/src/mono/wasm/Wasm.Build.Tests/SignalRTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/SignalRTestsBase.cs @@ -5,30 +5,35 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Collections.Generic; -using Wasm.Build.Tests.TestAppScenarios; +using System.Collections.Specialized; +using Wasm.Build.Tests; using Xunit.Abstractions; using Xunit; #nullable enable namespace Wasm.Build.Tests; -public class SignalRTestsBase : AppTestBase +public class SignalRTestsBase : WasmTemplateTestsBase { public SignalRTestsBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - protected async Task SignalRPassMessage(string staticWebAssetBasePath, string config, string transport) + protected async Task SignalRPassMessage(string staticWebAssetBasePath, Configuration config, string transport) { - CopyTestAsset("WasmOnAspNetCore", "SignalRClientTests", "AspNetCoreServer"); - PublishProject(config, runtimeType: RuntimeVariant.MultiThreaded, assertAppBundle: false); + TestAsset asset = new() { Name = "WasmBasicTestApp", RunnableProjectSubPath = "AspNetCoreServer" }; + ProjectInfo info = CopyTestAsset(config, false, asset, "SignalRClientTests"); + PublishProject(info, config, new PublishOptions(RuntimeType: RuntimeVariant.MultiThreaded, AssertAppBundle: false)); - var result = await RunSdkStyleAppForBuild(new( + var result = await RunForPublishWithWebServer(new BrowserRunOptions( Configuration: config, ServerEnvironment: new Dictionary { ["ASPNETCORE_ENVIRONMENT"] = "Development" }, BrowserPath: staticWebAssetBasePath, - BrowserQueryString: new Dictionary { ["transport"] = transport, ["message"] = "ping" } )); + BrowserQueryString: new NameValueCollection { + { "transport", transport}, + { "message", "ping" } + })); string testOutput = string.Join("\n", result.TestOutput) ?? ""; Assert.NotEmpty(testOutput); diff --git a/src/mono/wasm/Wasm.Build.Tests/Templates/NativeBuildTests.cs b/src/mono/wasm/Wasm.Build.Tests/Templates/NativeBuildTests.cs index e76e1112e6b638..b1f892901d13d4 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Templates/NativeBuildTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Templates/NativeBuildTests.cs @@ -23,8 +23,7 @@ public NativeBuildTests(ITestOutputHelper output, SharedBuildPerTestClassFixture [InlineData(false)] public void BuildWithUndefinedNativeSymbol(bool allowUndefined) { - string id = $"UndefinedNativeSymbol_{(allowUndefined ? "allowed" : "disabled")}_{GetRandomId()}"; - + Configuration config = Configuration.Release; string code = @" using System; using System.Runtime.InteropServices; @@ -35,71 +34,50 @@ public void BuildWithUndefinedNativeSymbol(bool allowUndefined) [DllImport(""undefined_xyz"")] static extern void call(); "; - string projectPath = CreateWasmTemplateProject(id); - - AddItemsPropertiesToProject( - projectPath, - extraItems: @$"", - extraProperties: allowUndefined ? $"true" : null + string extraItems = @$""; + string extraProperties = allowUndefined ? $"true" : ""; + ProjectInfo info = CreateWasmTemplateProject( + Template.WasmBrowser, + config, + aot: false, + $"UndefinedNativeSymbol_{(allowUndefined ? "allowed" : "disabled")}", + extraItems: extraItems, + extraProperties: extraProperties ); + UpdateFile("Program.cs", code); + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "undefined-symbol.c"), Path.Combine(_projectDir, "undefined_xyz.c")); + var buildOptions = new BuildOptions(ExpectSuccess: allowUndefined, AssertAppBundle: false); + (string _, string buildOutput) = BuildProject(info, config, buildOptions, isNativeBuild: true); - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), code); - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "undefined-symbol.c"), Path.Combine(_projectDir!, "undefined_xyz.c")); - - using DotNetCommand cmd = new DotNetCommand(s_buildEnv, _testOutput); - CommandResult result = cmd.WithWorkingDirectory(_projectDir!) - .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) - .ExecuteWithCapturedOutput("build", "-c Release"); - - if (allowUndefined) - { - Assert.True(result.ExitCode == 0, "Expected build to succeed"); - } - else + if (!allowUndefined) { - Assert.False(result.ExitCode == 0, "Expected build to fail"); - Assert.Contains("undefined symbol: sgfg", result.Output); - Assert.Contains("Use '-p:WasmAllowUndefinedSymbols=true' to allow undefined symbols", result.Output); + Assert.Contains("undefined symbol: sgfg", buildOutput); + Assert.Contains("Use '-p:WasmAllowUndefinedSymbols=true' to allow undefined symbols", buildOutput); } } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public async Task ProjectWithDllImportsRequiringMarshalIlGen_ArrayTypeParameter(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public async Task ProjectWithDllImportsRequiringMarshalIlGen_ArrayTypeParameter(Configuration config) { - string id = $"dllimport_incompatible_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, template: "wasmbrowser"); - string projectName = Path.GetFileNameWithoutExtension(projectFile); - string nativeSourceFilename = "incompatible_type.c"; - string nativeCode = "void call_needing_marhsal_ilgen(void *x) {}"; - File.WriteAllText(path: Path.Combine(_projectDir!, nativeSourceFilename), nativeCode); - - AddItemsPropertiesToProject( - projectFile, - extraItems: "" + string extraItems = ""; + ProjectInfo info = CreateWasmTemplateProject( + Template.WasmBrowser, + config, + aot: false, + "dllimport_incompatible", + extraItems: extraItems ); - + string nativeCode = "void call_needing_marhsal_ilgen(void *x) {}"; + File.WriteAllText(path: Path.Combine(_projectDir, nativeSourceFilename), nativeCode); UpdateBrowserMainJs(); - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "marshal_ilgen_test.cs"), - Path.Combine(_projectDir!, "Program.cs"), - overwrite: true); - - var buildArgs = new BuildArgs(projectName, config, false, id, null); - buildArgs = ExpandBuildArgs(buildArgs); - BuildTemplateProject(buildArgs, id: id, new BuildProjectOptions( - AssertAppBundle: false, - CreateProject: false, - HasV8Script: false, - MainJS: "main.mjs", - Publish: false, - TargetFramework: DefaultTargetFramework, - IsBrowserProject: true) - ); - string runOutput = await RunBuiltBrowserApp(config, projectFile); + ReplaceFile("Program.cs", Path.Combine(BuildEnvironment.TestAssetsPath, "marshal_ilgen_test.cs")); - Assert.Contains("call_needing_marhsal_ilgen got called", runOutput); + (string _, string buildOutput) = BuildProject(info, config, new BuildOptions(AssertAppBundle: false), isNativeBuild: true); + var runOutput = await RunForBuildWithDotnetRun(new BrowserRunOptions(config, ExpectedExitCode: 42)); + Assert.Contains("call_needing_marhsal_ilgen got called", runOutput.TestOutput); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTests.cs b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTests.cs index 89107e11d1868e..3716b8d80006b5 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTests.cs @@ -22,66 +22,33 @@ public WasmTemplateTests(ITestOutputHelper output, SharedBuildPerTestClassFixtur { } - private BuildProjectOptions _basePublishProjectOptions = new BuildProjectOptions( - DotnetWasmFromRuntimePack: false, - CreateProject: false, - HasV8Script: false, - MainJS: "main.js", - Publish: true - ); - private BuildProjectOptions _baseBuildProjectOptions = new BuildProjectOptions( - DotnetWasmFromRuntimePack: true, - CreateProject: false, - HasV8Script: false, - MainJS: "main.js", - Publish: false - ); - [Theory, TestCategory("no-fingerprinting")] - [InlineData("Debug")] - [InlineData("Release")] - public void BrowserBuildThenPublish(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void BrowserBuildThenPublish(Configuration config) { - string id = $"browser_{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "wasmbrowser"); - string projectName = Path.GetFileNameWithoutExtension(projectFile); - - UpdateBrowserProgramFile(); - UpdateBrowserMainJs(); - - var buildArgs = new BuildArgs(projectName, config, false, id, null); - - AddItemsPropertiesToProject(projectFile, - atTheEnd: - """ + string atEnd = """ <_LinkedOutFile Include="$(IntermediateOutputPath)\linked\*.dll" /> - """ - ); + """; + ProjectInfo info = CreateWasmTemplateProject(Template.WasmBrowser, config, aot: false, "browser", insertAtEnd: atEnd); + UpdateBrowserProgramFile(); + UpdateBrowserMainJs(); - buildArgs = ExpandBuildArgs(buildArgs); - BuildTemplateProject(buildArgs, id: id, _baseBuildProjectOptions); + BuildProject(info, config); - if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product)) - throw new XunitException($"Test bug: could not get the build product in the cache"); + if (!_buildContext.TryGetBuildFor(info, out BuildResult? result)) + throw new XunitException($"Test bug: could not get the build result in the cache"); - File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog")); + File.Move(result!.LogFile, Path.ChangeExtension(result.LogFile!, ".first.binlog")); _testOutput.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}"); - bool expectRelinking = config == "Release"; - BuildTemplateProject(buildArgs, - id: id, - _basePublishProjectOptions with - { - UseCache = false, - DotnetWasmFromRuntimePack = !expectRelinking, - } - ); + PublishProject(info, config, new PublishOptions(UseCache: false)); } public static TheoryData TestDataForAppBundleDir() @@ -107,27 +74,22 @@ void AddTestData(bool runOutsideProjectDirectory) [MemberData(nameof(TestDataForAppBundleDir))] [ActiveIssue("https://github.com/dotnet/runtime/issues/108107")] public async Task RunWithDifferentAppBundleLocations(bool runOutsideProjectDirectory, string extraProperties) - => await BrowserRunTwiceWithAndThenWithoutBuildAsync("Release", extraProperties, runOutsideProjectDirectory); + => await BrowserRunTwiceWithAndThenWithoutBuildAsync(Configuration.Release, extraProperties, runOutsideProjectDirectory); - private async Task BrowserRunTwiceWithAndThenWithoutBuildAsync(string config, string extraProperties = "", bool runOutsideProjectDirectory = false) + private async Task BrowserRunTwiceWithAndThenWithoutBuildAsync(Configuration config, string extraProperties = "", bool runOutsideProjectDirectory = false) { - string id = $"browser_{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "wasmbrowser"); - + ProjectInfo info = CreateWasmTemplateProject(Template.WasmBrowser, config, aot: false, "browser", extraProperties: extraProperties); UpdateBrowserProgramFile(); UpdateBrowserMainJs(); - if (!string.IsNullOrEmpty(extraProperties)) - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); - - string workingDir = runOutsideProjectDirectory ? BuildEnvironment.TmpPath : _projectDir!; + string workingDir = runOutsideProjectDirectory ? BuildEnvironment.TmpPath : _projectDir; { using var runCommand = new RunCommand(s_buildEnv, _testOutput) .WithWorkingDirectory(workingDir); await using var runner = new BrowserRunner(_testOutput); - var page = await runner.RunAsync(runCommand, $"run --no-silent -c {config} --project \"{projectFile}\" --forward-console"); + var page = await runner.RunAsync(runCommand, $"run --no-silent -c {config} --project \"{info.ProjectName}.csproj\" --forward-console"); await runner.WaitForExitMessageAsync(TimeSpan.FromMinutes(2)); Assert.Contains("Hello, Browser!", string.Join(Environment.NewLine, runner.OutputLines)); } @@ -137,7 +99,7 @@ private async Task BrowserRunTwiceWithAndThenWithoutBuildAsync(string config, st .WithWorkingDirectory(workingDir); await using var runner = new BrowserRunner(_testOutput); - var page = await runner.RunAsync(runCommand, $"run --no-silent -c {config} --no-build --project \"{projectFile}\" --forward-console"); + var page = await runner.RunAsync(runCommand, $"run --no-silent -c {config} --no-build --project \"{info.ProjectName}.csproj\" --forward-console"); await runner.WaitForExitMessageAsync(TimeSpan.FromMinutes(2)); Assert.Contains("Hello, Browser!", string.Join(Environment.NewLine, runner.OutputLines)); } @@ -160,67 +122,52 @@ private async Task BrowserRunTwiceWithAndThenWithoutBuildAsync(string config, st [MemberData(nameof(BrowserBuildAndRunTestData))] public async Task BrowserBuildAndRun(string extraNewArgs, string targetFramework, string runtimeAssetsRelativePath) { - string config = "Debug"; - string id = $"browser_{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "wasmbrowser", extraNewArgs, addFrameworkArg: extraNewArgs.Length == 0); - string projectName = Path.GetFileNameWithoutExtension(projectFile); + Configuration config = Configuration.Debug; string extraProperties = runtimeAssetsRelativePath == DefaultRuntimeAssetsRelativePath ? "" : $"{runtimeAssetsRelativePath}"; - AddItemsPropertiesToProject(projectFile, extraProperties); + ProjectInfo info = CreateWasmTemplateProject( + Template.WasmBrowser, + config, + aot: false, + "browser", + extraProperties: extraProperties, + extraArgs: extraNewArgs, + addFrameworkArg: extraNewArgs.Length == 0 + ); if (targetFramework != "net8.0") UpdateBrowserProgramFile(); UpdateBrowserMainJs(targetFramework, runtimeAssetsRelativePath); - using ToolCommand cmd = new DotNetCommand(s_buildEnv, _testOutput) - .WithWorkingDirectory(_projectDir!); - cmd.Execute($"build -c {config} -bl:{Path.Combine(s_buildEnv.LogRootPath, $"{id}.binlog")} {(runtimeAssetsRelativePath != DefaultRuntimeAssetsRelativePath ? "-p:WasmRuntimeAssetsLocation=" + runtimeAssetsRelativePath : "")}") - .EnsureSuccessful(); - var buildArgs = new BuildArgs(projectName, config, false, id, null); - buildArgs = ExpandBuildArgs(buildArgs); - BuildTemplateProject(buildArgs, id: id, _baseBuildProjectOptions); + PublishProject(info, config, new PublishOptions(UseCache: false)); - string runOutput = await RunBuiltBrowserApp(config, projectFile); - Assert.Contains("Hello, Browser!", runOutput); + var runOutput = await RunForPublishWithWebServer(new BrowserRunOptions(config, ExpectedExitCode: 42)); + Assert.Contains("Hello, Browser!", runOutput.TestOutput); } [Theory] - [InlineData("Debug", /*appendRID*/ true, /*useArtifacts*/ false)] - [InlineData("Debug", /*appendRID*/ true, /*useArtifacts*/ true)] - [InlineData("Debug", /*appendRID*/ false, /*useArtifacts*/ true)] - [InlineData("Debug", /*appendRID*/ false, /*useArtifacts*/ false)] - public async Task BuildAndRunForDifferentOutputPaths(string config, bool appendRID, bool useArtifacts) + [InlineData(Configuration.Debug, /*appendRID*/ true, /*useArtifacts*/ false)] + [InlineData(Configuration.Debug, /*appendRID*/ true, /*useArtifacts*/ true)] + [InlineData(Configuration.Debug, /*appendRID*/ false, /*useArtifacts*/ true)] + [InlineData(Configuration.Debug, /*appendRID*/ false, /*useArtifacts*/ false)] + public async Task BuildAndRunForDifferentOutputPaths(Configuration config, bool appendRID, bool useArtifacts) { - string id = $"{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "wasmbrowser"); - string projectName = Path.GetFileNameWithoutExtension(projectFile); - string projectDirectory = Path.GetDirectoryName(projectFile)!; - + ProjectInfo info = CreateWasmTemplateProject(Template.WasmBrowser, config, aot: false); UpdateBrowserProgramFile(); UpdateBrowserMainJs(); - string extraPropertiesForDBP = string.Empty; - string frameworkDir = FindBinFrameworkDir(config, forPublish: false); - - var buildOptions = _baseBuildProjectOptions with - { - BinFrameworkDir = frameworkDir - }; + bool isPublish = false; + string projectDirectory = Path.GetDirectoryName(info.ProjectFilePath) ?? ""; + // browser app does not allow appending RID + string frameworkDir = useArtifacts ? + Path.Combine(projectDirectory, "bin", info.ProjectName, config.ToString().ToLower(), "wwwroot", "_framework") : + GetBinFrameworkDir(config, isPublish); + + string extraPropertiesForDBP = string.Empty; if (useArtifacts) { extraPropertiesForDBP += "true."; - buildOptions = buildOptions with - { - // browser app does not allow appending RID - BinFrameworkDir = Path.Combine( - projectDirectory, - "bin", - id, - config.ToLower(), - "wwwroot", - "_framework") - }; } if (appendRID) { @@ -230,11 +177,8 @@ public async Task BuildAndRunForDifferentOutputPaths(string config, bool appendR string propsPath = Path.Combine(projectDirectory, "Directory.Build.props"); AddItemsPropertiesToProject(propsPath, extraPropertiesForDBP); - var buildArgs = new BuildArgs(projectName, config, false, id, null); - buildArgs = ExpandBuildArgs(buildArgs); - BuildTemplateProject(buildArgs, id: id, buildOptions); - - await RunBuiltBrowserApp(config, projectFile, extraArgs: "x y z"); + BuildProject(info, config, new BuildOptions(NonDefaultFrameworkDir: frameworkDir)); + await RunForBuildWithDotnetRun(new BrowserRunOptions(config, ExpectedExitCode: 42, ExtraArgs: "x y z")); } [Theory] @@ -242,33 +186,23 @@ public async Task BuildAndRunForDifferentOutputPaths(string config, bool appendR [InlineData("false", false)] // the other case public async Task Test_WasmStripILAfterAOT(string stripILAfterAOT, bool expectILStripping) { - string config = "Release"; - string id = $"strip_{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "wasmbrowser"); - string projectName = Path.GetFileNameWithoutExtension(projectFile); - string projectDirectory = Path.GetDirectoryName(projectFile)!; + Configuration config = Configuration.Release; bool aot = true; + string extraProperties = "true"; + if (!string.IsNullOrEmpty(stripILAfterAOT)) + extraProperties += $"{stripILAfterAOT}"; + ProjectInfo info = CreateWasmTemplateProject(Template.WasmBrowser, config, aot, "strip", extraProperties: extraProperties); UpdateBrowserProgramFile(); UpdateBrowserMainJs(); - string extraProperties = "true"; - if (!string.IsNullOrEmpty(stripILAfterAOT)) - extraProperties += $"{stripILAfterAOT}"; - AddItemsPropertiesToProject(projectFile, extraProperties); - - var buildArgs = new BuildArgs(projectName, config, aot, id, null); - buildArgs = ExpandBuildArgs(buildArgs); - BuildTemplateProject(buildArgs, - id: id, - _basePublishProjectOptions with { - UseCache = false, - AssertAppBundle = false - }); - - await RunBuiltBrowserApp(config, projectFile); - string frameworkDir = FindBinFrameworkDir(config, forPublish: true); - string objBuildDir = Path.Combine(projectDirectory, "obj", config, BuildTestBase.DefaultTargetFramework, "wasm", "for-publish"); + PublishProject(info, config, new PublishOptions(UseCache: false, AssertAppBundle: false)); + await RunForBuildWithDotnetRun(new BrowserRunOptions(config, ExpectedExitCode: 42)); + + string projectDirectory = Path.GetDirectoryName(info.ProjectFilePath)!; + string objBuildDir = Path.Combine(projectDirectory, "obj", config.ToString(), BuildTestBase.DefaultTargetFramework, "wasm", "for-publish"); + + string frameworkDir = GetBinFrameworkDir(config, forPublish: true); TestWasmStripILAfterAOTOutput(objBuildDir, frameworkDir, expectILStripping, _testOutput); } @@ -330,25 +264,20 @@ internal static void TestWasmStripILAfterAOTOutput(string objBuildDir, string fr [InlineData(true)] public void PublishPdb(bool copyOutputSymbolsToPublishDirectory) { - string config = "Release"; + Configuration config = Configuration.Release; string shouldCopy = copyOutputSymbolsToPublishDirectory.ToString().ToLower(); - string id = $"publishpdb_{shouldCopy}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "wasmbrowser"); - string projectName = Path.GetFileNameWithoutExtension(projectFile); - var buildArgs = new BuildArgs(projectName, config, false, id, null); - buildArgs = ExpandBuildArgs(buildArgs); - AddItemsPropertiesToProject(projectFile, - extraProperties: $"{shouldCopy}"); - - BuildTemplateProject(buildArgs, buildArgs.Id, _basePublishProjectOptions); - string publishPath = FindBinFrameworkDir(config, forPublish: true); + string extraProperties = $"{shouldCopy}"; + ProjectInfo info = CreateWasmTemplateProject(Template.WasmBrowser, config, aot: false, "publishpdb", extraProperties: extraProperties); + + PublishProject(info, config); + string publishPath = GetBinFrameworkDir(config, forPublish: true); AssertFile(".pdb"); AssertFile(".pdb.gz"); AssertFile(".pdb.br"); void AssertFile(string suffix) { - var fileName = Directory.EnumerateFiles(publishPath, $"*{suffix}").FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).StartsWith(id)); + var fileName = Directory.EnumerateFiles(publishPath, $"*{suffix}").FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).StartsWith(info.ProjectName)); Assert.True(copyOutputSymbolsToPublishDirectory == (fileName != null && File.Exists(fileName)), $"The {fileName} file {(copyOutputSymbolsToPublishDirectory ? "should" : "shouldn't")} exist in publish folder"); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs index 6b668222cc2f75..f5d499533139de 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.Playwright; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; @@ -18,7 +20,10 @@ namespace Wasm.Build.Tests; public class WasmTemplateTestsBase : BuildTestBase { private readonly WasmSdkBasedProjectProvider _provider; + protected readonly PublishOptions _defaultPublishOptions = new PublishOptions(); + protected readonly BuildOptions _defaultBuildOptions = new BuildOptions(); protected const string DefaultRuntimeAssetsRelativePath = "./_framework/"; + public WasmTemplateTestsBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext, ProjectProviderBase? provider = null) : base(provider ?? new WasmSdkBasedProjectProvider(output, DefaultTargetFramework), output, buildContext) { @@ -27,78 +32,139 @@ public WasmTemplateTestsBase(ITestOutputHelper output, SharedBuildPerTestClassFi private Dictionary browserProgramReplacements = new Dictionary { - { "while(true)", $"int i = 0;{Environment.NewLine}while(i++ < 10)" }, - { "partial class StopwatchSample", $"return 42;{Environment.NewLine}partial class StopwatchSample" } + { "while(true)", $"int i = 0;{Environment.NewLine}while(i++ < 0)" }, // the test has to be fast, skip the loop + { "partial class StopwatchSample", $"return 42;{Environment.NewLine}partial class StopwatchSample" }, + { "Hello, Browser!", "TestOutput -> Hello, Browser!" } }; - public string CreateWasmTemplateProject(string id, string template = "wasmbrowser", string extraArgs = "", bool runAnalyzers = true, bool addFrameworkArg = false, string? extraProperties = null) + private string GetProjectName(string idPrefix, Configuration config, bool aot, bool appendUnicodeToPath, bool avoidAotLongPathIssue = false) => + avoidAotLongPathIssue ? // https://github.com/dotnet/runtime/issues/103625 + $"{GetRandomId()}" : + appendUnicodeToPath ? + $"{idPrefix}_{config}_{aot}_{GetRandomId()}_{s_unicodeChars}" : + $"{idPrefix}_{config}_{aot}_{GetRandomId()}"; + + private (string projectName, string logPath, string nugetDir) InitProjectLocation(string idPrefix, Configuration config, bool aot, bool appendUnicodeToPath, bool avoidAotLongPathIssue = false) { - InitPaths(id); + string projectName = GetProjectName(idPrefix, config, aot, appendUnicodeToPath, avoidAotLongPathIssue); + (string logPath, string nugetDir) = InitPaths(projectName); InitProjectDir(_projectDir, addNuGetSourceForLocalPackages: true); + return (projectName, logPath, nugetDir); + } - File.WriteAllText(Path.Combine(_projectDir, "Directory.Build.props"), ""); - File.WriteAllText(Path.Combine(_projectDir, "Directory.Build.targets"), - """ - - - - - - - - """); - if (UseWBTOverridePackTargets) - File.Copy(BuildEnvironment.WasmOverridePacksTargetsPath, Path.Combine(_projectDir, Path.GetFileName(BuildEnvironment.WasmOverridePacksTargetsPath)), overwrite: true); + public ProjectInfo CreateWasmTemplateProject( + Template template, + Configuration config, + bool aot, + string idPrefix = "wbt", + bool appendUnicodeToPath = true, + string extraArgs = "", + bool runAnalyzers = true, + bool addFrameworkArg = false, + string extraProperties = "", + string extraItems = "", + string insertAtEnd = "") + { + (string projectName, string logPath, string nugetDir) = + InitProjectLocation(idPrefix, config, aot, appendUnicodeToPath); if (addFrameworkArg) extraArgs += $" -f {DefaultTargetFramework}"; + using DotNetCommand cmd = new DotNetCommand(s_buildEnv, _testOutput, useDefaultArgs: false); - CommandResult result = cmd.WithWorkingDirectory(_projectDir!) - .ExecuteWithCapturedOutput($"new {template} {extraArgs}") + CommandResult result = cmd.WithWorkingDirectory(_projectDir) + .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) + .ExecuteWithCapturedOutput($"new {template.ToString().ToLower()} {extraArgs}") .EnsureSuccessful(); - string projectfile = Path.Combine(_projectDir!, $"{id}.csproj"); + string projectFilePath = Path.Combine(_projectDir, $"{projectName}.csproj"); + UpdateProjectFile(projectFilePath, runAnalyzers, extraProperties, extraItems, insertAtEnd); + return new ProjectInfo(projectName, projectFilePath, logPath, nugetDir); + } - if (extraProperties == null) - extraProperties = string.Empty; + protected ProjectInfo CopyTestAsset( + Configuration config, + bool aot, + TestAsset asset, + string idPrefix, + bool appendUnicodeToPath = true, + bool runAnalyzers = true, + string extraProperties = "", + string extraItems = "", + string insertAtEnd = "") + { + (string projectName, string logPath, string nugetDir) = + InitProjectLocation(idPrefix, config, aot, appendUnicodeToPath, avoidAotLongPathIssue: s_isWindows && aot); + Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, asset.Name), Path.Combine(_projectDir)); + if (!string.IsNullOrEmpty(asset.RunnableProjectSubPath)) + { + _projectDir = Path.Combine(_projectDir, asset.RunnableProjectSubPath); + } + string projectFilePath = Path.Combine(_projectDir, $"{asset.Name}.csproj"); + UpdateProjectFile(projectFilePath, runAnalyzers, extraProperties, extraItems, insertAtEnd); + return new ProjectInfo(asset.Name, projectFilePath, logPath, nugetDir); + } + private void UpdateProjectFile(string projectFilePath, bool runAnalyzers, string extraProperties, string extraItems, string insertAtEnd) + { extraProperties += "true"; if (runAnalyzers) extraProperties += "true"; - - AddItemsPropertiesToProject(projectfile, extraProperties); - - return projectfile; + AddItemsPropertiesToProject(projectFilePath, extraProperties, extraItems, insertAtEnd); } - public (string projectDir, string buildOutput) BuildTemplateProject( - BuildArgs buildArgs, - string id, - BuildProjectOptions buildProjectOptions, - params string[] extraArgs) + public virtual (string projectDir, string buildOutput) PublishProject( + ProjectInfo info, + Configuration configuration, + bool? isNativeBuild = null) => // null for WasmBuildNative unset + BuildProject(info, configuration, _defaultPublishOptions, isNativeBuild); + + public virtual (string projectDir, string buildOutput) PublishProject( + ProjectInfo info, + Configuration configuration, + PublishOptions publishOptions, + bool? isNativeBuild = null) => + BuildProject(info, configuration, publishOptions, isNativeBuild); + + public virtual (string projectDir, string buildOutput) BuildProject( + ProjectInfo info, + Configuration configuration, + bool? isNativeBuild = null) => // null for WasmBuildNative unset + BuildProject(info, configuration, _defaultBuildOptions, isNativeBuild); + + public virtual (string projectDir, string buildOutput) BuildProject( + ProjectInfo info, + Configuration configuration, + MSBuildOptions buildOptions, + bool? isNativeBuild = null) { - if (buildProjectOptions.ExtraBuildEnvironmentVariables is null) - buildProjectOptions = buildProjectOptions with { ExtraBuildEnvironmentVariables = new Dictionary() }; + if (buildOptions.AOT) + { + buildOptions = buildOptions with { ExtraMSBuildArgs = $"{buildOptions.ExtraMSBuildArgs} -p:RunAOTCompilation=true -p:EmccVerbose=true" }; + } + + if (buildOptions.ExtraBuildEnvironmentVariables is null) + buildOptions = buildOptions with { ExtraBuildEnvironmentVariables = new Dictionary() }; // TODO: reenable this when the SDK supports targetting net10.0 - //buildProjectOptions.ExtraBuildEnvironmentVariables["TreatPreviousAsCurrent"] = "false"; + //buildOptions.ExtraBuildEnvironmentVariables["TreatPreviousAsCurrent"] = "false"; + + (CommandResult res, string logFilePath) = BuildProjectWithoutAssert(configuration, info.ProjectName, buildOptions); - (CommandResult res, string logFilePath) = BuildProjectWithoutAssert(id, buildArgs.Config, buildProjectOptions, extraArgs); - if (buildProjectOptions.UseCache) - _buildContext.CacheBuild(buildArgs, new BuildProduct(_projectDir!, logFilePath, true, res.Output)); + if (buildOptions.UseCache) + _buildContext.CacheBuild(info, new BuildResult(_projectDir, logFilePath, true, res.Output)); - if (buildProjectOptions.AssertAppBundle) + if (!buildOptions.ExpectSuccess) { - if (buildProjectOptions.IsBrowserProject) - { - _provider.AssertWasmSdkBundle(buildArgs, buildProjectOptions, res.Output); - } - else - { - _provider.AssertTestMainJsBundle(buildArgs, buildProjectOptions, res.Output); - } + res.EnsureFailed(); + return (_projectDir, res.Output); } - return (_projectDir!, res.Output); + + if (buildOptions.AssertAppBundle) + { + _provider.AssertWasmSdkBundle(configuration, buildOptions, IsUsingWorkloads, isNativeBuild, res.Output); + } + return (_projectDir, res.Output); } private string StringReplaceWithAssert(string oldContent, string oldValue, string newValue) @@ -115,7 +181,7 @@ protected void UpdateBrowserProgramFile() => protected void UpdateFile(string pathRelativeToProjectDir, Dictionary replacements) { - var path = Path.Combine(_projectDir!, pathRelativeToProjectDir); + var path = Path.Combine(_projectDir, pathRelativeToProjectDir); string text = File.ReadAllText(path); foreach (var replacement in replacements) { @@ -124,24 +190,30 @@ protected void UpdateFile(string pathRelativeToProjectDir, Dictionary RunForBuildWithDotnetRun(RunOptions runOptions) + => await BrowserRun(runOptions with { Host = RunHost.DotnetRun }); + + public virtual async Task RunForPublishWithWebServer(RunOptions runOptions) + => await BrowserRun(runOptions with { Host = RunHost.WebServer }); + + private async Task BrowserRun(RunOptions runOptions) => runOptions.Host switch { - string mainJsPath = Path.Combine(_projectDir!, "main.mjs"); - string mainJsContent = File.ReadAllText(mainJsPath); + RunHost.DotnetRun => + await BrowserRunTest($"run -c {runOptions.Configuration} --no-build", _projectDir, runOptions), + + RunHost.WebServer => + await BrowserRunTest($"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files", + string.IsNullOrEmpty(runOptions.CustomBundleDir) ? + Path.GetFullPath(Path.Combine(GetBinFrameworkDir(runOptions.Configuration, forPublish: true), "..")) : + runOptions.CustomBundleDir, + runOptions), + + _ => throw new NotImplementedException(runOptions.Host.ToString()) + }; + + private async Task BrowserRunTest(string runArgs, + string workingDirectory, + RunOptions runOptions) + { + if (!string.IsNullOrEmpty(runOptions.ExtraArgs)) + runArgs += $" {runOptions.ExtraArgs}"; - StringBuilder js = new(); - foreach (var variable in variables) + runOptions.ServerEnvironment?.ToList().ForEach( + kv => s_buildEnv.EnvVars[kv.Key] = kv.Value); + + using RunCommand runCommand = new RunCommand(s_buildEnv, _testOutput); + ToolCommand cmd = runCommand.WithWorkingDirectory(workingDirectory); + + var query = runOptions.BrowserQueryString ?? new NameValueCollection(); + if (runOptions.AOT) + { + query.Add("MONO_LOG_LEVEL", "debug"); + query.Add("MONO_LOG_MASK", "aot"); + } + if (runOptions is BrowserRunOptions browserOp && !string.IsNullOrEmpty(browserOp.TestScenario)) + query.Add("test", browserOp.TestScenario); + var queryString = query.Count > 0 && query.AllKeys != null + ? "?" + string.Join("&", query.AllKeys.SelectMany(key => query.GetValues(key)?.Select(value => $"{key}={value}") ?? Enumerable.Empty())) + : ""; + + List testOutput = new(); + List consoleOutput = new(); + List serverOutput = new(); + await using var runner = new BrowserRunner(_testOutput); + var page = await runner.RunAsync( + cmd, + runArgs, + locale: runOptions.Locale, + onConsoleMessage: OnConsoleMessage, + onServerMessage: OnServerMessage, + onError: OnErrorMessage, + modifyBrowserUrl: browserUrl => new Uri(new Uri(browserUrl), runOptions.BrowserPath + queryString).ToString()); + + _testOutput.WriteLine("Waiting for page to load"); + await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded, new () { Timeout = 1 * 60 * 1000 }); + + if (runOptions.ExecuteAfterLoaded is not null) { - js.Append($".withEnvironmentVariable(\"{variable.key}\", \"{variable.value}\")"); + await runOptions.ExecuteAfterLoaded(runOptions, page); } - mainJsContent = StringReplaceWithAssert(mainJsContent, ".create()", js.ToString() + ".create()"); + if (runOptions is BlazorRunOptions blazorOp && blazorOp.Test is not null) + await blazorOp.Test(page); - File.WriteAllText(mainJsPath, mainJsContent); - } + _testOutput.WriteLine($"Waiting for additional 10secs to see if any errors are reported"); + int exitCode = await runner.WaitForExitMessageAsync(TimeSpan.FromSeconds(10)); + if (runOptions.ExpectedExitCode is not null && exitCode != runOptions.ExpectedExitCode) + throw new Exception($"Expected exit code {runOptions.ExpectedExitCode} but got {exitCode}.\nconsoleOutput={string.Join("\n", consoleOutput)}"); - // ToDo: consolidate with BlazorRunTest - protected async Task RunBuiltBrowserApp(string config, string projectFile, string language = "en-US", string extraArgs = "", string testScenario = "") - => await RunBrowser( - $"run --no-silent -c {config} --no-build --project \"{projectFile}\" --forward-console {extraArgs}", - _projectDir!, - language, - testScenario: testScenario); - - protected async Task RunPublishedBrowserApp(string config, string language = "en-US", string extraArgs = "", string testScenario = "") - => await RunBrowser( - command: $"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files", - workingDirectory: Path.Combine(FindBinFrameworkDir(config, forPublish: true), ".."), - language: language, - testScenario: testScenario); - - private async Task RunBrowser(string command, string workingDirectory, string language = "en-US", string testScenario = "") - { - using var runCommand = new RunCommand(s_buildEnv, _testOutput).WithWorkingDirectory(workingDirectory); - await using var runner = new BrowserRunner(_testOutput); - Func? modifyBrowserUrl = string.IsNullOrEmpty(testScenario) ? - null : - browserUrl => new Uri(new Uri(browserUrl), $"?test={testScenario}").ToString(); - var page = await runner.RunAsync(runCommand, command, language: language, modifyBrowserUrl: modifyBrowserUrl); - await runner.WaitForExitMessageAsync(TimeSpan.FromMinutes(2)); - Assert.Contains("WASM EXIT 42", string.Join(Environment.NewLine, runner.OutputLines)); - return string.Join("\n", runner.OutputLines); + return new(exitCode, testOutput, consoleOutput, serverOutput); + + void OnConsoleMessage(string type, string msg) + { + _testOutput.WriteLine($"[{type}] {msg}"); + consoleOutput.Add(msg); + OnTestOutput(msg); + + runOptions.OnConsoleMessage?.Invoke(type, msg); + + if (runOptions.DetectRuntimeFailures) + { + if (msg.Contains("[MONO] * Assertion") || msg.Contains("Error: [MONO] ")) + throw new XunitException($"Detected a runtime failure at line: {msg}"); + } + } + + void OnServerMessage(string msg) + { + serverOutput.Add(msg); + OnTestOutput(msg); + + if (runOptions.OnServerMessage != null) + runOptions.OnServerMessage(msg); + } + + void OnTestOutput(string msg) + { + const string testOutputPrefix = "TestOutput -> "; + if (msg.StartsWith(testOutputPrefix)) + testOutput.Add(msg.Substring(testOutputPrefix.Length)); + } + + void OnErrorMessage(string msg) + { + _testOutput.WriteLine($"[ERROR] {msg}"); + runOptions.OnErrorMessage?.Invoke(msg); + } } - public string FindBinFrameworkDir(string config, bool forPublish, string framework = DefaultTargetFramework, string? projectDir = null) => - _provider.FindBinFrameworkDir(config: config, forPublish: forPublish, framework: framework, projectDir: projectDir); + public string GetBinFrameworkDir(Configuration config, bool forPublish, string framework = DefaultTargetFramework, string? projectDir = null) => + _provider.GetBinFrameworkDir(config, forPublish, framework, projectDir); + + public BuildPaths GetBuildPaths(Configuration config, bool forPublish) => + _provider.GetBuildPaths(config, forPublish); + + public IDictionary GetFilesTable(string projectName, bool isAOT, BuildPaths paths, bool unchanged) => + _provider.GetFilesTable(projectName, isAOT, paths, unchanged); + + public IDictionary StatFiles(IDictionary fullpaths) => + _provider.StatFiles(fullpaths); + + // 2nd and next stats with fingerprinting require updated statistics + public IDictionary StatFilesAfterRebuild(IDictionary fullpaths) => + _provider.StatFilesAfterRebuild(fullpaths); + + public void CompareStat(IDictionary oldStat, IDictionary newStat, IDictionary expected) => + _provider.CompareStat(oldStat, newStat, expected); } diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs deleted file mode 100644 index 4ce41342917778..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Xunit; -using Xunit.Abstractions; - -#nullable enable - -namespace Wasm.Build.Tests.TestAppScenarios; - -public class AppSettingsTests : AppTestBase -{ - public AppSettingsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) - { - } - - [Theory] - [InlineData("Development")] - [InlineData("Production")] - public async Task LoadAppSettingsBasedOnApplicationEnvironment(string applicationEnvironment) - { - CopyTestAsset("WasmBasicTestApp", "AppSettingsTests", "App"); - PublishProject("Debug"); - - var result = await RunSdkStyleAppForPublish(new( - Configuration: "Debug", - TestScenario: "AppSettingsTest", - BrowserQueryString: new Dictionary { ["applicationEnvironment"] = applicationEnvironment } - )); - Assert.Collection( - result.TestOutput, - m => Assert.Equal(GetFileExistenceMessage("/appsettings.json", true), m), - m => Assert.Equal(GetFileExistenceMessage("/appsettings.Development.json", applicationEnvironment == "Development"), m), - m => Assert.Equal(GetFileExistenceMessage("/appsettings.Production.json", applicationEnvironment == "Production"), m) - ); - } - - // Synchronize with AppSettingsTest - private static string GetFileExistenceMessage(string path, bool expected) => $"'{path}' exists '{expected}'"; -} diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs deleted file mode 100644 index 71411e7c4dadf6..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs +++ /dev/null @@ -1,178 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Microsoft.Playwright; -using Xunit.Abstractions; -using Wasm.Build.Tests.Blazor; - -namespace Wasm.Build.Tests.TestAppScenarios; - -public abstract class AppTestBase : BlazorWasmTestBase -{ - protected AppTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) - { - } - - protected string Id { get; set; } - protected string LogPath { get; set; } - - protected void CopyTestAsset(string assetName, string generatedProjectNamePrefix = null, string? projectDirSuffix = null) - { - Id = $"{generatedProjectNamePrefix ?? assetName}_{GetRandomId()}"; - InitBlazorWasmProjectDir(Id); - - LogPath = Path.Combine(s_buildEnv.LogRootPath, Id); - Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, assetName), Path.Combine(_projectDir!)); - - if (!string.IsNullOrEmpty(projectDirSuffix)) - { - _projectDir = Path.Combine(_projectDir, projectDirSuffix); - } - } - - protected void BuildProject( - string configuration, - string? binFrameworkDir = null, - RuntimeVariant runtimeType = RuntimeVariant.SingleThreaded, - bool assertAppBundle = true, - bool expectSuccess = true, - string bootConfigFileName = "blazor.boot.json", - params string[] extraArgs) - { - (CommandResult result, _) = BlazorBuild(new BlazorBuildOptions( - Id: Id, - Config: configuration, - BinFrameworkDir: binFrameworkDir, - RuntimeType: runtimeType, - AssertAppBundle: assertAppBundle, - BootConfigFileName: bootConfigFileName, - ExpectSuccess: expectSuccess), extraArgs); - if (expectSuccess) - { - result.EnsureSuccessful(); - } - else - { - result.EnsureFailed(); - } - } - - protected void PublishProject( - string configuration, - RuntimeVariant runtimeType = RuntimeVariant.SingleThreaded, - bool assertAppBundle = true, - string bootConfigFileName = "blazor.boot.json", - params string[] extraArgs) - { - (CommandResult result, _) = BlazorPublish(new BlazorBuildOptions( - Id: Id, - Config: configuration, - RuntimeType: runtimeType, - BootConfigFileName: bootConfigFileName, - AssertAppBundle: assertAppBundle), extraArgs); - result.EnsureSuccessful(); - } - - protected Task RunSdkStyleAppForBuild(RunOptions options) - => RunSdkStyleApp(options, BlazorRunHost.DotnetRun); - - protected Task RunSdkStyleAppForPublish(RunOptions options) - => RunSdkStyleApp(options, BlazorRunHost.WebServer); - - private async Task RunSdkStyleApp(RunOptions options, BlazorRunHost host = BlazorRunHost.DotnetRun) - { - var query = options.BrowserQueryString ?? new Dictionary(); - if (!string.IsNullOrEmpty(options.TestScenario)) - query.Add("test", options.TestScenario); - - var queryString = query.Any() ? "?" + string.Join("&", query.Select(kvp => $"{kvp.Key}={kvp.Value}")) : ""; - var tcs = new TaskCompletionSource(); - List testOutput = new(); - List consoleOutput = new(); - List serverOutput = new(); - Regex exitRegex = new Regex("WASM EXIT (?[0-9]+)$"); - - BlazorRunOptions blazorRunOptions = new( - CheckCounter: false, - Config: options.Configuration, - ServerEnvironment: options.ServerEnvironment, - OnConsoleMessage: OnConsoleMessage, - OnServerMessage: OnServerMessage, - BrowserPath: options.BrowserPath, - QueryString: queryString, - Host: host, - ExtraArgs: options.ExtraArgs); - - await BlazorRunTest(blazorRunOptions); - - void OnConsoleMessage(IPage page, IConsoleMessage msg) - { - consoleOutput.Add(msg.Text); - - OnTestOutput(msg.Text); - - var exitMatch = exitRegex.Match(msg.Text); - if (exitMatch.Success) - tcs.TrySetResult(int.Parse(exitMatch.Groups["exitCode"].Value)); - - if (msg.Text.StartsWith("Error: Missing test scenario")) - throw new Exception(msg.Text); - - if (options.OnConsoleMessage != null) - options.OnConsoleMessage(page, msg); - } - - void OnServerMessage(string msg) - { - serverOutput.Add(msg); - OnTestOutput(msg); - - if (options.OnServerMessage != null) - options.OnServerMessage(msg); - } - - void OnTestOutput(string msg) - { - const string testOutputPrefix = "TestOutput -> "; - if (msg.StartsWith(testOutputPrefix)) - testOutput.Add(msg.Substring(testOutputPrefix.Length)); - } - - //TimeSpan timeout = TimeSpan.FromMinutes(2); - //await Task.WhenAny(tcs.Task, Task.Delay(timeout)); - //if (!tcs.Task.IsCompleted) - //throw new Exception($"Timed out after {timeout.TotalSeconds}s waiting for process to exit"); - - int wasmExitCode = tcs.Task.Result; - if (options.ExpectedExitCode != null && wasmExitCode != options.ExpectedExitCode) - throw new Exception($"Expected exit code {options.ExpectedExitCode} but got {wasmExitCode}"); - - return new(wasmExitCode, testOutput, consoleOutput, serverOutput); - } - - protected record RunOptions( - string Configuration, - string BrowserPath = "", - string? TestScenario = null, - Dictionary BrowserQueryString = null, - Dictionary ServerEnvironment = null, - Action OnConsoleMessage = null, - Action OnServerMessage = null, - int? ExpectedExitCode = 0, - string? ExtraArgs = null - ); - - protected record RunResult( - int ExitCode, - IReadOnlyCollection TestOutput, - IReadOnlyCollection ConsoleOutput, - IReadOnlyCollection ServerOutput - ); -} diff --git a/src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs b/src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs deleted file mode 100644 index b14d52ac4f1824..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs +++ /dev/null @@ -1,119 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System; -using System.Collections.Generic; -using System.IO; -using Xunit.Abstractions; - -namespace Wasm.Build.Tests; - -public class TestMainJsProjectProvider : ProjectProviderBase -{ - public TestMainJsProjectProvider(ITestOutputHelper _testOutput, string? _projectDir = null) - : base(_testOutput, _projectDir) - { } - protected override string BundleDirName { get { return "AppBundle"; } } - - // no fingerprinting - protected override IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(AssertBundleOptionsBase assertOptions) - => new SortedDictionary() - { - { "dotnet.js", false }, - { "dotnet.js.map", false }, - { "dotnet.native.js", false }, - { "dotnet.native.js.symbols", false }, - { "dotnet.globalization.js", false }, - { "dotnet.native.wasm", false }, - { "dotnet.native.worker.mjs", false }, - { "dotnet.runtime.js", false }, - { "dotnet.runtime.js.map", false } - }; - - protected override IReadOnlySet GetDotNetFilesExpectedSet(AssertBundleOptionsBase assertOptions) - { - SortedSet? res = new(); - if (assertOptions.RuntimeType is RuntimeVariant.SingleThreaded) - { - res.Add("dotnet.js"); - res.Add("dotnet.native.wasm"); - res.Add("dotnet.native.js"); - res.Add("dotnet.runtime.js"); - res.Add("dotnet.js.map"); - res.Add("dotnet.runtime.js.map"); - } - - if (assertOptions.RuntimeType is RuntimeVariant.MultiThreaded) - { - res.Add("dotnet.js"); - res.Add("dotnet.native.wasm"); - res.Add("dotnet.native.js"); - res.Add("dotnet.runtime.js"); - res.Add("dotnet.native.worker.mjs"); - - if (!assertOptions.IsPublish) - { - res.Add("dotnet.js.map"); - res.Add("dotnet.runtime.js.map"); - res.Add("dotnet.native.worker.mjs.map"); - } - } - - if (assertOptions.GlobalizationMode is GlobalizationMode.Hybrid) - res.Add("dotnet.globalization.js"); - - if (assertOptions.AssertSymbolsFile && assertOptions.ExpectSymbolsFile) - res.Add("dotnet.native.js.symbols"); - - return res ?? throw new ArgumentException($"Unknown runtime type: {assertOptions.RuntimeType}"); - } - - public void AssertBundle(AssertTestMainJsAppBundleOptions assertOptions) - { - AssertBasicBundle(assertOptions); - - TestUtils.AssertFilesExist(assertOptions.BundleDir, new[] { assertOptions.MainJS }); - if (assertOptions.IsBrowserProject) - TestUtils.AssertFilesExist(assertOptions.BundleDir, new[] { "index.html" }); - TestUtils.AssertFilesExist(assertOptions.BundleDir, new[] { "run-v8.sh" }, expectToExist: assertOptions.HasV8Script); - - string bundledMainAppAssembly = $"{assertOptions.ProjectName}{WasmAssemblyExtension}"; - TestUtils.AssertFilesExist(assertOptions.BinFrameworkDir, new[] { bundledMainAppAssembly }); - } - - public void AssertBundle(BuildArgs buildArgs, BuildProjectOptions buildProjectOptions) - { - string binFrameworkDir = buildProjectOptions.BinFrameworkDir - ?? FindBinFrameworkDir(buildArgs.Config, - buildProjectOptions.Publish, - buildProjectOptions.TargetFramework); - NativeFilesType expectedFileType = buildArgs.AOT - ? NativeFilesType.AOT - : buildProjectOptions.DotnetWasmFromRuntimePack == false - ? NativeFilesType.Relinked - : NativeFilesType.FromRuntimePack; - - var assertOptions = new AssertTestMainJsAppBundleOptions( - Config: buildArgs.Config, - IsPublish: buildProjectOptions.Publish, - TargetFramework: buildProjectOptions.TargetFramework!, - BinFrameworkDir: binFrameworkDir, - ProjectName: buildArgs.ProjectName, - MainJS: buildProjectOptions.MainJS ?? "test-main.js", - GlobalizationMode: buildProjectOptions.GlobalizationMode, - HasV8Script: buildProjectOptions.HasV8Script, - CustomIcuFile: buildProjectOptions.CustomIcuFile ?? string.Empty, - IsBrowserProject: buildProjectOptions.IsBrowserProject, - ExpectedFileType: expectedFileType, - ExpectSymbolsFile: !buildArgs.AOT); - AssertBundle(assertOptions); - } - - public override string FindBinFrameworkDir(string config, bool forPublish, string framework, string? projectDir = null) - { - EnsureProjectDirIsSet(); - return Path.Combine(projectDir ?? ProjectDir!, "bin", config, framework, "browser-wasm", BundleDirName, "_framework"); - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/TestMainJsTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/TestMainJsTestBase.cs deleted file mode 100644 index e5940778c3b3e5..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/TestMainJsTestBase.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System; -using System.Collections.Generic; -using System.IO; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Wasm.Build.Tests; - -public abstract class TestMainJsTestBase : BuildTestBase -{ - protected TestMainJsProjectProvider _provider; - protected TestMainJsTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(new TestMainJsProjectProvider(output), output, buildContext) - { - _provider = GetProvider(); - } - - public (string projectDir, string buildOutput) BuildProject(BuildArgs buildArgs, - string id, - BuildProjectOptions options) - { - string msgPrefix = options.Label != null ? $"[{options.Label}] " : string.Empty; - if (options.UseCache && _buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product)) - { - _testOutput.WriteLine($"Using existing build found at {product.ProjectDir}, with build log at {product.LogFile}"); - - if (!product.Result) - throw new XunitException($"Found existing build at {product.ProjectDir}, but it had failed. Check build log at {product.LogFile}"); - _projectDir = product.ProjectDir; - - // use this test's id for the run logs - _logPath = Path.Combine(s_buildEnv.LogRootPath, id); - return (_projectDir, product.BuildOutput); - } - - if (options.CreateProject) - { - InitPaths(id); - InitProjectDir(_projectDir); - options.InitProject?.Invoke(); - - File.WriteAllText(Path.Combine(_projectDir, $"{buildArgs.ProjectName}.csproj"), buildArgs.ProjectFileContents); - File.Copy( - Path.Combine( - AppContext.BaseDirectory, - options.TargetFramework == "net7.0" - ? "data/test-main-7.0.js" - : "test-main.js" - ), - Path.Combine(_projectDir, "test-main.js") - ); - - File.WriteAllText(Path.Combine(_projectDir!, "index.html"), @""); - } - else if (_projectDir is null) - { - throw new Exception("_projectDir should be set, to use options.createProject=false"); - } - - if (options.ExtraBuildEnvironmentVariables is null) - options = options with { ExtraBuildEnvironmentVariables = new Dictionary() }; - - // TODO: reenable this when the SDK supports targetting net10.0 - //options.ExtraBuildEnvironmentVariables["TreatPreviousAsCurrent"] = "false"; - - try - { - (CommandResult res, string logFilePath) = BuildProjectWithoutAssert(id, - buildArgs.Config, - options, - string.Join(" ", buildArgs.ExtraBuildArgs)); - - if (options.ExpectSuccess && options.AssertAppBundle) - { - ProjectProviderBase.AssertRuntimePackPath(res.Output, options.TargetFramework ?? DefaultTargetFramework); - _provider.AssertBundle(buildArgs, options); - } - - if (options.UseCache) - _buildContext.CacheBuild(buildArgs, new BuildProduct(_projectDir, logFilePath, true, res.Output)); - - return (_projectDir, res.Output); - } - catch (Exception ex) - { - if (options.UseCache) - _buildContext.CacheBuild(buildArgs, new BuildProduct(_projectDir, /*logFilePath*/"unset-log-path", false, $"The build attempt resulted in exception: {ex}.")); - throw; - } - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj index 034811490dcd19..c58bed440b6a1f 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj +++ b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj @@ -123,6 +123,9 @@ + + + diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmBuildAppBase.cs b/src/mono/wasm/Wasm.Build.Tests/WasmBuildAppBase.cs new file mode 100644 index 00000000000000..7836ff1a15b3be --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/WasmBuildAppBase.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xunit; +using Xunit.Abstractions; + +#nullable enable + +namespace Wasm.Build.Tests +{ + public class WasmBuildAppBase : WasmTemplateTestsBase + { + public static IEnumerable MainMethodTestData(bool aot) + => ConfigWithAOTData(aot) + .Where(item => !(item.ElementAt(0) is Configuration config && config == Configuration.Debug && item.ElementAt(1) is bool aotValue && aotValue)) + .UnwrapItemsAsArrays(); + + public WasmBuildAppBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) + { + } + + protected async void TestMain(string projectName, + string programText, + Configuration config, + bool aot, + bool? isNativeBuild = null, + int expectedExitCode = 42, + string expectedOutput = "Hello, World!", + string runtimeConfigContents = "", + string extraArgs = "") + { + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "DotnetRun"); + UpdateFile(Path.Combine("Common", "Program.cs"), programText); + if (!string.IsNullOrEmpty(runtimeConfigContents)) + { + UpdateFile("runtimeconfig.template.json", new Dictionary { { "}\n}", runtimeConfigContents } }); + } + PublishProject(info, config, new PublishOptions(AOT: aot, ExtraMSBuildArgs: extraArgs), isNativeBuild: isNativeBuild); + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: expectedExitCode) + ); + Assert.Contains(result.ConsoleOutput, m => m.Contains(expectedOutput)); + } + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmBuildAppTest.cs b/src/mono/wasm/Wasm.Build.Tests/WasmBuildAppTest.cs index cfff7b40ba0ba9..996d3a490651b1 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmBuildAppTest.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmBuildAppTest.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; +using System.Linq; using Xunit; using Xunit.Abstractions; @@ -12,21 +13,22 @@ namespace Wasm.Build.Tests { public class WasmBuildAppTest : WasmBuildAppBase { + // similar to MainWithArgsTests.cs, consider merging public WasmBuildAppTest(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) {} [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - public void TopLevelMain(BuildArgs buildArgs, RunHost host, string id) + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true })] + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false })] + public void TopLevelMain(Configuration config, bool aot) => TestMain("top_level", @"System.Console.WriteLine(""Hello, World!""); return await System.Threading.Tasks.Task.FromResult(42);", - buildArgs, host, id); + config, aot); [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - public void AsyncMain(BuildArgs buildArgs, RunHost host, string id) + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true })] + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false })] + public void AsyncMain(Configuration config, bool aot) => TestMain("async_main", @" using System; using System.Threading.Tasks; @@ -37,12 +39,12 @@ public static async Task Main() Console.WriteLine(""Hello, World!""); return await Task.FromResult(42); } - }", buildArgs, host, id); + }", config, aot); [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - public void NonAsyncMain(BuildArgs buildArgs, RunHost host, string id) + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true })] + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false })] + public void NonAsyncMain(Configuration config, bool aot) => TestMain("non_async_main", @" using System; using System.Threading.Tasks; @@ -53,11 +55,11 @@ public static int Main() Console.WriteLine(""Hello, World!""); return 42; } - }", buildArgs, host, id); + }", config, aot); [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - public void ExceptionFromMain(BuildArgs buildArgs, RunHost host, string id) + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false })] + public void ExceptionFromMain(Configuration config, bool aot) => TestMain("main_exception", """ using System; using System.Threading.Tasks; @@ -65,7 +67,7 @@ public void ExceptionFromMain(BuildArgs buildArgs, RunHost host, string id) public class TestClass { public static int Main() => throw new Exception("MessageFromMyException"); } - """, buildArgs, host, id, expectedExitCode: 71, expectedOutput: "Error: MessageFromMyException"); + """, config, aot, expectedExitCode: 1, expectedOutput: "Error: MessageFromMyException"); private static string s_bug49588_ProgramCS = @" using System; @@ -81,120 +83,57 @@ public static int Main() }"; [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - public void Bug49588_RegressionTest_AOT(BuildArgs buildArgs, RunHost host, string id) - => TestMain("bug49588_aot", s_bug49588_ProgramCS, buildArgs, host, id); + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true })] + public void Bug49588_RegressionTest_AOT(Configuration config, bool aot) + => TestMain("bug49588_aot", s_bug49588_ProgramCS, config, aot); [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - public void Bug49588_RegressionTest_NativeRelinking(BuildArgs buildArgs, RunHost host, string id) - => TestMain("bug49588_native_relinking", s_bug49588_ProgramCS, buildArgs, host, id, - extraProperties: "true", - dotnetWasmFromRuntimePack: false); + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false })] + public void Bug49588_RegressionTest_NativeRelinking(Configuration config, bool aot) + => TestMain("bug49588_native_relinking", s_bug49588_ProgramCS, config, aot, + extraArgs: "-p:WasmBuildNative=true", + isNativeBuild: true); [Theory] [BuildAndRun] - public void PropertiesFromRuntimeConfigJson(BuildArgs buildArgs, RunHost host, string id) - { - buildArgs = buildArgs with { ProjectName = $"runtime_config_{buildArgs.Config}_{buildArgs.AOT}" }; - buildArgs = ExpandBuildArgs(buildArgs); - - string programText = @" - using System; - using System.Runtime.CompilerServices; - - var config = AppContext.GetData(""test_runtimeconfig_json""); - Console.WriteLine ($""test_runtimeconfig_json: {(string)config}""); - return 42; - "; - - string runtimeConfigTemplateJson = @" - { - ""configProperties"": { - ""abc"": ""4"", - ""test_runtimeconfig_json"": ""25"" - } - }"; - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText); - File.WriteAllText(Path.Combine(_projectDir!, "runtimeconfig.template.json"), runtimeConfigTemplateJson); - }, - DotnetWasmFromRuntimePack: IsDotnetWasmFromRuntimePack(buildArgs))); - - RunAndTestWasmApp(buildArgs, expectedExitCode: 42, - test: output => Assert.Contains("test_runtimeconfig_json: 25", output), host: host, id: id); - } + [ActiveIssue("https://github.com/dotnet/runtime/issues/97449")] + public void PropertiesFromRuntimeConfigJson(Configuration config, bool aot) + => TestMain("runtime_config_json", + @" + using System; + using System.Runtime.CompilerServices; + + var config = AppContext.GetData(""test_runtimeconfig_json""); + Console.WriteLine ($""test_runtimeconfig_json: {(string)config}""); + return 42; + ", + config, + aot, + runtimeConfigContents: @" + }, + ""configProperties"": { + ""abc"": ""4"", + ""test_runtimeconfig_json"": ""25"" + } + }", + expectedOutput: "test_runtimeconfig_json: 25"); [Theory] [BuildAndRun] - public void PropertiesFromCsproj(BuildArgs buildArgs, RunHost host, string id) - { - buildArgs = buildArgs with { ProjectName = $"runtime_config_csproj_{buildArgs.Config}_{buildArgs.AOT}" }; - buildArgs = ExpandBuildArgs(buildArgs, extraProperties: "20"); - - string programText = @" - using System; - using System.Runtime.CompilerServices; - - var config = AppContext.GetData(""System.Threading.ThreadPool.MaxThreads""); - Console.WriteLine ($""System.Threading.ThreadPool.MaxThreads: {(string)config}""); - return 42; - "; - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText); - }, - DotnetWasmFromRuntimePack: IsDotnetWasmFromRuntimePack(buildArgs))); - - RunAndTestWasmApp(buildArgs, expectedExitCode: 42, - test: output => Assert.Contains("System.Threading.ThreadPool.MaxThreads: 20", output), host: host, id: id); - } - } - - public class WasmBuildAppBase : TestMainJsTestBase - { - public static IEnumerable MainMethodTestData(bool aot, RunHost host) - => ConfigWithAOTData(aot) - .WithRunHosts(host) - .UnwrapItemsAsArrays(); - - public WasmBuildAppBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) - { - } - - protected void TestMain(string projectName, - string programText, - BuildArgs buildArgs, - RunHost host, - string id, - string extraProperties = "", - bool? dotnetWasmFromRuntimePack = null, - int expectedExitCode = 42, - string expectedOutput = "Hello, World!") - { - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, extraProperties); - - if (dotnetWasmFromRuntimePack == null) - dotnetWasmFromRuntimePack = IsDotnetWasmFromRuntimePack(buildArgs); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack)); - - RunAndTestWasmApp(buildArgs, expectedExitCode: expectedExitCode, - test: output => Assert.Contains(expectedOutput, output), host: host, id: id); - } + [ActiveIssue("https://github.com/dotnet/runtime/issues/97449")] + public void PropertiesFromCsproj(Configuration config, bool aot) + => TestMain("csproj_properties", + @" + using System; + using System.Runtime.CompilerServices; + + var config = AppContext.GetData(""System.Threading.ThreadPool.MaxThreads""); + Console.WriteLine ($""System.Threading.ThreadPool.MaxThreads: {(string)config}""); + return 42; + ", + config, + aot, + extraArgs: "-p:ThreadPoolMaxThreads=20", + expectedOutput: "System.Threading.ThreadPool.MaxThreads: 20"); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmNativeDefaultsTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmNativeDefaultsTests.cs index 0fd78da40da588..d2c7a16d314e0c 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmNativeDefaultsTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmNativeDefaultsTests.cs @@ -11,7 +11,7 @@ namespace Wasm.Build.Tests { - public class WasmNativeDefaultsTests : TestMainJsTestBase + public class WasmNativeDefaultsTests : WasmTemplateTestsBase { private static Regex s_regex = new("\\*\\* WasmBuildNative:.*"); public WasmNativeDefaultsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) @@ -19,7 +19,7 @@ public WasmNativeDefaultsTests(ITestOutputHelper output, SharedBuildPerTestClass { } - public static TheoryData SettingDifferentFromValuesInRuntimePack(bool forPublish) + public static TheoryData SettingDifferentFromValuesInRuntimePack(bool forPublish) { List<(string propertyName, bool defaultValueInRuntimePack)> defaults = new() { @@ -30,15 +30,15 @@ public static TheoryData SettingDifferentFromV // ("WasmNativeStrip", true) -- tested separately because it has special handling in targets }; - TheoryData data = new(); + TheoryData data = new(); - string[] configs = new[] { "Debug", "Release" }; + var configs = new[] { Configuration.Debug, Configuration.Release }; foreach (var defaultPair in defaults) { - foreach (string config in configs) + foreach (Configuration config in configs) { - // Config=Release always causes relinking when publishing - bool publishValue = forPublish && config == "Release" ? true : false; + // Configuration=Release always causes relinking when publishing + bool publishValue = forPublish && config == Configuration.Release ? true : false; // Setting the default value from the runtime pack shouldn't trigger relinking data.Add(config, $"<{defaultPair.propertyName}>{defaultPair.defaultValueInRuntimePack.ToString().ToLower()}", /*aot*/ false, /*build*/ false, /*publish*/ publishValue); @@ -54,42 +54,42 @@ public static TheoryData SettingDifferentFromV return data; } - public static TheoryData DefaultsTestData(bool forPublish) + public static TheoryData DefaultsTestData(bool forPublish) { - TheoryData data = new() + TheoryData data = new() { /* relink by default for publish+Release */ - { "Release", "", /*aot*/ false, /*build*/ false, /*publish*/ true }, + { Configuration.Release, "", /*aot*/ false, /*build*/ false, /*publish*/ true }, /* NO relink by default for publish+Release, when not trimming */ - { "Release", "false", /*aot*/ false, /*build*/ false, /*publish*/ false }, + { Configuration.Release, "false", /*aot*/ false, /*build*/ false, /*publish*/ false }, /* When not trimming, and no-aot, we don't relink. But WasmNativeStrip=false should still trigger it*/ - // { "Release", "falsefalse", + // { Configuration.Release, "falsefalse", // /*aot*/ false, /*build*/ true, /*publish*/ true } }; if (!forPublish) { /* Debug config, when building does trigger relinking */ - data.Add("Debug", "", /*aot*/ false, /*build*/ false, /*publish*/ true); + data.Add(Configuration.Debug, "", /*aot*/ false, /*build*/ false, /*publish*/ true); } if (forPublish) { /* NO relink by default for publish+Debug */ - data.Add("Debug", "", /*aot*/ false, /*build*/ false, /*publish*/ false); + data.Add(Configuration.Debug, "", /*aot*/ false, /*build*/ false, /*publish*/ false); /* AOT */ - data.Add("Release", "", /*aot*/ true, /*build*/ false, /*publish*/ true); - data.Add("Debug", "", /*aot*/ true, /*build*/ false, /*publish*/ true); + data.Add(Configuration.Release, "", /*aot*/ true, /*build*/ false, /*publish*/ true); + data.Add(Configuration.Debug, "", /*aot*/ true, /*build*/ false, /*publish*/ true); // FIXME: separate test - // { "Release", "true", + // { Configuration.Release, "true", // /*aot*/ true, /*build*/ true, /*publish*/ true }, /* AOT not affected by trimming */ - data.Add("Release", "false", /*aot*/ true, /*build*/ false, /*publish*/ true); - data.Add("Debug", "false", /*aot*/ true, /*build*/ false, /*publish*/ true); + data.Add(Configuration.Release, "false", /*aot*/ true, /*build*/ false, /*publish*/ true); + data.Add(Configuration.Debug, "false", /*aot*/ true, /*build*/ false, /*publish*/ true); } return data; @@ -99,9 +99,9 @@ public static TheoryData DefaultsTestData(bool [Theory] [MemberData(nameof(DefaultsTestData), parameters: false)] [MemberData(nameof(SettingDifferentFromValuesInRuntimePack), parameters: false)] - public void DefaultsWithBuild(string config, string extraProperties, bool aot, bool expectWasmBuildNativeForBuild, bool expectWasmBuildNativeForPublish) + public void DefaultsWithBuild(Configuration config, string extraProperties, bool aot, bool expectWasmBuildNativeForBuild, bool expectWasmBuildNativeForPublish) { - (string output, string? line) = CheckWasmNativeDefaultValue("native_defaults_build", config, extraProperties, aot, dotnetWasmFromRuntimePack: !expectWasmBuildNativeForBuild, publish: false); + (string output, string? line) = CheckWasmNativeDefaultValue("native_defaults_build", config, extraProperties, aot, expectWasmBuildNativeForBuild, isPublish: false); InferAndCheckPropertyValues(line, isPublish: false, wasmBuildNative: expectWasmBuildNativeForBuild, config: config); } @@ -109,36 +109,36 @@ public void DefaultsWithBuild(string config, string extraProperties, bool aot, b [Theory] [MemberData(nameof(DefaultsTestData), parameters: true)] [MemberData(nameof(SettingDifferentFromValuesInRuntimePack), parameters: true)] - public void DefaultsWithPublish(string config, string extraProperties, bool aot, bool expectWasmBuildNativeForBuild, bool expectWasmBuildNativeForPublish) + public void DefaultsWithPublish(Configuration config, string extraProperties, bool aot, bool expectWasmBuildNativeForBuild, bool expectWasmBuildNativeForPublish) { - (string output, string? line) = CheckWasmNativeDefaultValue("native_defaults_publish", config, extraProperties, aot, dotnetWasmFromRuntimePack: !expectWasmBuildNativeForPublish, publish: true); + (string output, string? line) = CheckWasmNativeDefaultValue("native_defaults_publish", config, extraProperties, aot, expectWasmBuildNativeForPublish, isPublish: true); InferAndCheckPropertyValues(line, isPublish: true, wasmBuildNative: expectWasmBuildNativeForPublish, config: config); } #pragma warning restore xunit1026 - public static TheoryData SetWasmNativeStripExplicitlyTestData(bool publish) => new() + public static TheoryData SetWasmNativeStripExplicitlyTestData(bool publish) => new() { - {"Debug", "true", /*wasmBuildNative*/ false, /*wasmNativeStrip*/ true }, - {"Release", "true", /*wasmBuildNative*/ publish, /*wasmNativeStrip*/ true }, - {"Debug", "false", /*wasmBuildNative*/ true, /*wasmNativeStrip*/ false }, - {"Release", "false", /*wasmBuildNative*/ true, /*wasmNativeStrip*/ false } + {Configuration.Debug, "true", /*wasmBuildNative*/ false, /*wasmNativeStrip*/ true }, + {Configuration.Release, "true", /*wasmBuildNative*/ publish, /*wasmNativeStrip*/ true }, + {Configuration.Debug, "false", /*wasmBuildNative*/ true, /*wasmNativeStrip*/ false }, + {Configuration.Release, "false", /*wasmBuildNative*/ true, /*wasmNativeStrip*/ false } }; - public static TheoryData SetWasmNativeStripExplicitlyWithWasmBuildNativeTestData() => new() + public static TheoryData SetWasmNativeStripExplicitlyWithWasmBuildNativeTestData() => new() { - { "Debug", "falsetrue", true, false }, - { "Release", "falsetrue", true, false }, - { "Debug", "truetrue", true, true }, - { "Release", "truetrue", true, true } + { Configuration.Debug, "falsetrue", true, false }, + { Configuration.Release, "falsetrue", true, false }, + { Configuration.Debug, "truetrue", true, true }, + { Configuration.Release, "truetrue", true, true } }; [Theory] [MemberData(nameof(SetWasmNativeStripExplicitlyTestData), parameters: /*publish*/ false)] [MemberData(nameof(SetWasmNativeStripExplicitlyWithWasmBuildNativeTestData))] - public void WasmNativeStripDefaultWithBuild(string config, string extraProperties, bool expectedWasmBuildNativeValue, bool expectedWasmNativeStripValue) + public void WasmNativeStripDefaultWithBuild(Configuration config, string extraProperties, bool expectedWasmBuildNativeValue, bool expectedWasmNativeStripValue) { - (string output, string? line) = CheckWasmNativeDefaultValue("native_strip_defaults", config, extraProperties, aot: false, dotnetWasmFromRuntimePack: !expectedWasmBuildNativeValue, publish: false); + (string output, string? line) = CheckWasmNativeDefaultValue("native_strip_defaults", config, extraProperties, aot: false, expectedWasmBuildNativeValue, isPublish: false); CheckPropertyValues(line, wasmBuildNative: expectedWasmBuildNativeValue, @@ -150,9 +150,9 @@ public void WasmNativeStripDefaultWithBuild(string config, string extraPropertie [Theory] [MemberData(nameof(SetWasmNativeStripExplicitlyTestData), parameters: /*publish*/ true)] [MemberData(nameof(SetWasmNativeStripExplicitlyWithWasmBuildNativeTestData))] - public void WasmNativeStripDefaultWithPublish(string config, string extraProperties, bool expectedWasmBuildNativeValue, bool expectedWasmNativeStripValue) + public void WasmNativeStripDefaultWithPublish(Configuration config, string extraProperties, bool expectedWasmBuildNativeValue, bool expectedWasmNativeStripValue) { - (string output, string? line) = CheckWasmNativeDefaultValue("native_strip_defaults", config, extraProperties, aot: false, dotnetWasmFromRuntimePack: !expectedWasmBuildNativeValue, publish: true); + (string output, string? line) = CheckWasmNativeDefaultValue("native_strip_defaults", config, extraProperties, aot: false, expectedWasmBuildNativeValue, isPublish: true); CheckPropertyValues(line, wasmBuildNative: expectedWasmBuildNativeValue, @@ -163,12 +163,12 @@ public void WasmNativeStripDefaultWithPublish(string config, string extraPropert [Theory] /* always relink */ - [InlineData("Debug", "", /*publish*/ false)] - [InlineData("Debug", "", /*publish*/ true)] - [InlineData("Release", "", /*publish*/ false)] - [InlineData("Release", "", /*publish*/ true)] - [InlineData("Release", "false", /*publish*/ true)] - public void WithNativeReference(string config, string extraProperties, bool publish) + [InlineData(Configuration.Debug, "", /*publish*/ false)] + [InlineData(Configuration.Debug, "", /*publish*/ true)] + [InlineData(Configuration.Release, "", /*publish*/ false)] + [InlineData(Configuration.Release, "", /*publish*/ true)] + [InlineData(Configuration.Release, "false", /*publish*/ true)] + public void WithNativeReference(Configuration config, string extraProperties, bool publish) { string nativeLibPath = Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "native-lib.o"); string nativeRefItem = @$""; @@ -176,19 +176,19 @@ public void WithNativeReference(string config, string extraProperties, bool publ config, extraProperties, aot: false, - dotnetWasmFromRuntimePack: !publish, - publish: publish, + nativeBuild: true, + isPublish: publish, extraItems: nativeRefItem); InferAndCheckPropertyValues(line, isPublish: publish, wasmBuildNative: true, config: config); } - private (string, string?) CheckWasmNativeDefaultValue(string projectName, - string config, + private (string, string?) CheckWasmNativeDefaultValue(string projectPrefix, + Configuration config, string extraProperties, bool aot, - bool dotnetWasmFromRuntimePack, - bool publish, + bool nativeBuild, + bool isPublish, string extraItems = "") { // builds with -O0 @@ -197,27 +197,23 @@ public void WithNativeReference(string config, string extraProperties, bool publ string printValueTarget = @" - " + (publish + " + (isPublish ? @"" : @"") + ""; - - BuildArgs buildArgs = new(ProjectName: projectName, Config: config, AOT: aot, string.Empty, null); - buildArgs = ExpandBuildArgs(buildArgs, - extraProperties: extraProperties, - extraItems: extraItems, - insertAtEnd: printValueTarget); - - (_, string output) = BuildProject(buildArgs, - id: GetRandomId(), - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, - ExpectSuccess: false, - UseCache: false, - BuildOnlyAfterPublish: false, - Publish: publish)); - + ProjectInfo info = CopyTestAsset( + config, + aot, + TestAsset.WasmBasicTestApp, + projectPrefix, + extraProperties: extraProperties, + extraItems: extraItems, + insertAtEnd: printValueTarget); + UpdateFile(Path.Combine("Common", "Program.cs"), s_mainReturns42); + + (string _, string output) = isPublish ? + PublishProject(info, config, new PublishOptions(ExpectSuccess: false, AOT: aot), nativeBuild) : + BuildProject(info, config, new BuildOptions(ExpectSuccess: false, AOT: aot), nativeBuild); Assert.Contains("Stopping the build", output); Match m = s_regex.Match(output); @@ -225,10 +221,10 @@ public void WithNativeReference(string config, string extraProperties, bool publ return (output, m.Success ? m.Groups[0]?.ToString() : null); } - private void InferAndCheckPropertyValues(string? line, bool isPublish, bool wasmBuildNative, string config) + private void InferAndCheckPropertyValues(string? line, bool isPublish, bool wasmBuildNative, Configuration config) { bool expectedWasmNativeStripValue; - if (!isPublish && wasmBuildNative && config == "Debug") + if (!isPublish && wasmBuildNative && config == Configuration.Debug) expectedWasmNativeStripValue = false; else expectedWasmNativeStripValue = true; @@ -239,11 +235,11 @@ private void InferAndCheckPropertyValues(string? line, bool isPublish, bool wasm private void CheckPropertyValues(string? line, bool wasmBuildNative, bool wasmNativeStrip, bool wasmNativeDebugSymbols, bool? wasmBuildingForNestedPublish) { Assert.NotNull(line); - Assert.Contains($"** WasmBuildNative: '{wasmBuildNative.ToString().ToLower()}', " + + string expected = $"** WasmBuildNative: '{wasmBuildNative.ToString().ToLower()}', " + $"WasmNativeStrip: '{wasmNativeStrip.ToString().ToLower()}', " + $"WasmNativeDebugSymbols: '{wasmNativeDebugSymbols.ToString().ToLower()}', " + - $"WasmBuildingForNestedPublish: '{(wasmBuildingForNestedPublish.HasValue && wasmBuildingForNestedPublish == true ? "true" : "")}'", - line); + $"WasmBuildingForNestedPublish: '{(wasmBuildingForNestedPublish.HasValue && wasmBuildingForNestedPublish == true ? "true" : "")}'"; + Assert.Contains(expected, line); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmRunOutOfAppBundleTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmRunOutOfAppBundleTests.cs index 775d9cb46582d2..d5161990bce189 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmRunOutOfAppBundleTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmRunOutOfAppBundleTests.cs @@ -9,48 +9,40 @@ namespace Wasm.Build.Tests; -public class WasmRunOutOfAppBundleTests : TestMainJsTestBase +public class WasmRunOutOfAppBundleTests : WasmTemplateTestsBase { public WasmRunOutOfAppBundleTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) {} [Theory] [BuildAndRun] - public void RunOutOfAppBundle(BuildArgs buildArgs, RunHost host, string id) + public async void RunOutOfAppBundle(Configuration config, bool aot) { - buildArgs = buildArgs with { ProjectName = $"outofappbundle_{buildArgs.Config}_{buildArgs.AOT}" }; - buildArgs = ExpandBuildArgs(buildArgs); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: !(buildArgs.AOT || buildArgs.Config == "Release"))); - - string binDir = GetBinDir(baseDir: _projectDir!, config: buildArgs.Config); - string appBundleDir = Path.Combine(binDir, "AppBundle"); - string outerDir = Path.GetFullPath(Path.Combine(appBundleDir, "..")); - - if (host is RunHost.Chrome) + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "outofappbundle"); + UpdateFile(Path.Combine("Common", "Program.cs"), s_mainReturns42); + (string _, string output) = PublishProject(info, config, new PublishOptions(AOT: aot)); + + string binFrameworkDir = GetBinFrameworkDir(config, forPublish: true); + string appBundleDir = Path.Combine(binFrameworkDir, ".."); + string outerDir = Path.GetFullPath(Path.Combine(appBundleDir, "..")); + string indexHtmlPath = Path.Combine(appBundleDir, "index.html"); + // Delete the original one, so we don't use that by accident + if (File.Exists(indexHtmlPath)) + File.Delete(indexHtmlPath); + + indexHtmlPath = Path.Combine(outerDir, "index.html"); + string relativeMainJsPath = "./wwwroot/main.js"; + if (!File.Exists(indexHtmlPath)) { - string indexHtmlPath = Path.Combine(appBundleDir, "index.html"); - // Delete the original one, so we don't use that by accident - if (File.Exists(indexHtmlPath)) - File.Delete(indexHtmlPath); - - indexHtmlPath = Path.Combine(outerDir, "index.html"); - if (!File.Exists(indexHtmlPath)) - { - var html = @""; - File.WriteAllText(indexHtmlPath, html); - } + var html = $@""; + File.WriteAllText(indexHtmlPath, html); } - RunAndTestWasmApp(buildArgs, - expectedExitCode: 42, - host: host, - id: id, - bundleDir: outerDir, - jsRelativePath: "./AppBundle/test-main.js"); + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + CustomBundleDir: outerDir, + ExpectedExitCode: 42) + ); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmSIMDTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmSIMDTests.cs index 08ac8512665b75..d4008d6af8ecca 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmSIMDTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmSIMDTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; +using System.Linq; using Xunit; using Xunit.Abstractions; @@ -10,90 +11,57 @@ namespace Wasm.Build.Tests { - public class WasmSIMDTests : WasmBuildAppBase + public class WasmSIMDTests : WasmTemplateTestsBase { public WasmSIMDTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - public static IEnumerable MainMethodSimdTestData(bool aot, RunHost host, bool simd) - => ConfigWithAOTData(aot, extraArgs: $"-p:WasmEnableSIMD={simd}") - .WithRunHosts(host) + public static IEnumerable MainMethodSimdTestData(bool aot, bool simd) + => ConfigWithAOTData(aot) + .Multiply(new object[] { simd }) + .Where(item => !(item.ElementAt(0) is Configuration config && config == Configuration.Debug && item.ElementAt(1) is bool aotValue && aotValue)) .UnwrapItemsAsArrays(); [Theory] - [MemberData(nameof(MainMethodSimdTestData), parameters: new object[] { /*aot*/ false, RunHost.All, true /* simd */ })] - public void Build_NoAOT_ShouldNotRelink(BuildArgs buildArgs, RunHost host, string id) + [MemberData(nameof(MainMethodSimdTestData), parameters: new object[] { /*aot*/ false, /* simd */ true })] + public async void Build_NoAOT_ShouldNotRelink(Configuration config, bool aot, bool simd) { - string projectName = $"build_with_workload_no_aot"; - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs); - - (_, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_simdProgramText), - Publish: false, - DotnetWasmFromRuntimePack: true)); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "build_with_workload_no_aot"); + UpdateFile(Path.Combine("Common", "Program.cs"), s_simdProgramText); + (string _, string output) = BuildProject(info, config, new BuildOptions(ExtraMSBuildArgs: $"-p:WasmEnableSIMD={simd}")); // Confirm that we didn't relink Assert.DoesNotContain("Compiling native assets with emcc", output); - RunAndTestWasmApp(buildArgs, - expectedExitCode: 42, - test: output => - { - Assert.Contains("<-2094756296, -2094756296, -2094756296, -2094756296>", output); - Assert.Contains("Hello, World!", output); - }, host: host, id: id); - } - - [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - public void PublishWithSIMD_AOT(BuildArgs buildArgs, RunHost host, string id) - { - string projectName = $"simd_with_workload_aot"; - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, "true"); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_simdProgramText), - DotnetWasmFromRuntimePack: false)); + RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42) + ); - RunAndTestWasmApp(buildArgs, - expectedExitCode: 42, - test: output => - { - Assert.Contains("<-2094756296, -2094756296, -2094756296, -2094756296>", output); - Assert.Contains("Hello, World!", output); - }, host: host, id: id); + Assert.Contains(result.TestOutput, m => m.Contains("<-2094756296, -2094756296, -2094756296, -2094756296>")); + Assert.Contains(result.TestOutput, m => m.Contains("Hello, World!")); } [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - public void PublishWithoutSIMD_AOT(BuildArgs buildArgs, RunHost host, string id) + [MemberData(nameof(MainMethodSimdTestData), parameters: new object[] { /*aot*/ true, /* simd */ true })] + [MemberData(nameof(MainMethodSimdTestData), parameters: new object[] { /*aot*/ false, /* simd */ true })] + [MemberData(nameof(MainMethodSimdTestData), parameters: new object[] { /*aot*/ true, /* simd */ false })] + public async void PublishSIMD_AOT(Configuration config, bool aot, bool simd) { - string projectName = $"nosimd_with_workload_aot"; - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, "false"); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_simdProgramText), - DotnetWasmFromRuntimePack: false)); - - RunAndTestWasmApp(buildArgs, - expectedExitCode: 42, - test: output => - { - Assert.Contains("<-2094756296, -2094756296, -2094756296, -2094756296>", output); - Assert.Contains("Hello, World!", output); - }, host: host, id: id); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "simd_publish"); + UpdateFile(Path.Combine("Common", "Program.cs"), s_simdProgramText); + (string _, string output) = PublishProject(info, config, new PublishOptions(ExtraMSBuildArgs: $"-p:WasmEnableSIMD={simd}", AOT: aot)); + + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42) + ); + Assert.Contains(result.TestOutput, m => m.Contains("<-2094756296, -2094756296, -2094756296, -2094756296>")); + Assert.Contains(result.TestOutput, m => m.Contains("Hello, World!")); } private static string s_simdProgramText = @" @@ -106,8 +74,8 @@ public static int Main() var v1 = Vector128.Create(0x12345678); var v2 = Vector128.Create(0x23456789); var v3 = v1*v2; - Console.WriteLine(v3); - Console.WriteLine(""Hello, World!""); + Console.WriteLine($""TestOutput -> {v3}""); + Console.WriteLine(""TestOutput -> Hello, World!""); return 42; } diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs b/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs index 06d47877717507..c04d0bf50c2f65 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs @@ -20,11 +20,11 @@ public WasmSdkBasedProjectProvider(ITestOutputHelper _testOutput, string default : base(_testOutput, _projectDir) { _defaultTargetFramework = defaultTargetFramework; - IsFingerprintingSupported = true; } + protected override string BundleDirName { get { return "wwwroot"; } } - protected override IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(AssertBundleOptionsBase assertOptions) + protected override IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(AssertBundleOptions assertOptions) => new SortedDictionary() { { "dotnet.js", false }, @@ -38,7 +38,7 @@ protected override IReadOnlyDictionary GetAllKnownDotnetFilesToFin { "dotnet.runtime.js.map", false }, }; - protected override IReadOnlySet GetDotNetFilesExpectedSet(AssertBundleOptionsBase assertOptions) + protected override IReadOnlySet GetDotNetFilesExpectedSet(AssertBundleOptions assertOptions) { SortedSet res = new() { @@ -47,16 +47,16 @@ protected override IReadOnlySet GetDotNetFilesExpectedSet(AssertBundleOp "dotnet.native.js", "dotnet.runtime.js", }; - if (assertOptions.RuntimeType is RuntimeVariant.MultiThreaded) + if (assertOptions.BuildOptions.RuntimeType is RuntimeVariant.MultiThreaded) { res.Add("dotnet.native.worker.mjs"); } - if (assertOptions.GlobalizationMode is GlobalizationMode.Hybrid) + if (assertOptions.BuildOptions.GlobalizationMode is GlobalizationMode.Hybrid) { res.Add("dotnet.globalization.js"); } - if (!assertOptions.IsPublish) + if (!assertOptions.BuildOptions.IsPublish) { res.Add("dotnet.js.map"); res.Add("dotnet.runtime.js.map"); @@ -68,28 +68,36 @@ protected override IReadOnlySet GetDotNetFilesExpectedSet(AssertBundleOp return res; } + public NativeFilesType GetExpectedFileType(Configuration config, bool isAOT, bool isPublish, bool isUsingWorkloads, bool? isNativeBuild=null) => + isNativeBuild == true ? NativeFilesType.Relinked : // precedence over build/publish check: build with -p:WasmBuildNative=true should use relinked + !isPublish ? NativeFilesType.FromRuntimePack : // precedence over AOT check: build with AOT should use runtime pack + isAOT ? NativeFilesType.AOT : // precedence over -p:WasmBuildNative=false check: publish with AOT relinks regardless of WasmBuildNative value + isNativeBuild == false ? NativeFilesType.FromRuntimePack : + (config == Configuration.Release) ? NativeFilesType.Relinked : + NativeFilesType.FromRuntimePack; - protected void AssertBundle(BuildArgs buildArgs, BuildProjectOptions buildProjectOptions) + public void AssertBundle(Configuration config, MSBuildOptions buildOptions, bool isUsingWorkloads, bool? isNativeBuild = null) { - string frameworkDir = buildProjectOptions.BinFrameworkDir ?? - FindBinFrameworkDir(buildArgs.Config, buildProjectOptions.Publish, buildProjectOptions.TargetFramework); - AssertBundle(new( - Config: buildArgs.Config, - IsPublish: buildProjectOptions.Publish, - TargetFramework: buildProjectOptions.TargetFramework, + string frameworkDir = string.IsNullOrEmpty(buildOptions.NonDefaultFrameworkDir) ? + GetBinFrameworkDir(config, buildOptions.IsPublish, _defaultTargetFramework) : + buildOptions.NonDefaultFrameworkDir; + + AssertBundle(new AssertBundleOptions( + config, + BuildOptions: buildOptions, + ExpectedFileType: GetExpectedFileType(config, buildOptions.AOT, buildOptions.IsPublish, isUsingWorkloads, isNativeBuild), BinFrameworkDir: frameworkDir, - CustomIcuFile: buildProjectOptions.CustomIcuFile, - GlobalizationMode: buildProjectOptions.GlobalizationMode, - AssertSymbolsFile: false, - ExpectedFileType: buildProjectOptions.Publish && buildArgs.Config == "Release" ? NativeFilesType.Relinked : NativeFilesType.FromRuntimePack + ExpectSymbolsFile: true, + AssertIcuAssets: true, + AssertSymbolsFile: false )); } - protected void AssertBundle(AssertWasmSdkBundleOptions assertOptions) + private void AssertBundle(AssertBundleOptions assertOptions) { IReadOnlyDictionary actualDotnetFiles = AssertBasicBundle(assertOptions); - if (assertOptions.IsPublish) + if (assertOptions.BuildOptions.IsPublish) { string publishPath = Path.GetFullPath(Path.Combine(assertOptions.BinFrameworkDir, "..", "..")); Assert.Equal("publish", Path.GetFileName(publishPath)); @@ -105,24 +113,25 @@ protected void AssertBundle(AssertWasmSdkBundleOptions assertOptions) return; // Compare files with the runtime pack - string objBuildDir = Path.Combine(ProjectDir!, "obj", assertOptions.Config, assertOptions.TargetFramework, "wasm", assertOptions.IsPublish ? "for-publish" : "for-build"); + string objBuildDir = Path.Combine(ProjectDir!, "obj", assertOptions.Configuration.ToString(), assertOptions.BuildOptions.TargetFramework, "wasm", assertOptions.BuildOptions.IsPublish ? "for-publish" : "for-build"); - string runtimeNativeDir = BuildTestBase.s_buildEnv.GetRuntimeNativeDir(assertOptions.TargetFramework, assertOptions.RuntimeType); + string runtimeNativeDir = BuildTestBase.s_buildEnv.GetRuntimeNativeDir(assertOptions.BuildOptions.TargetFramework, assertOptions.BuildOptions.RuntimeType); string srcDirForNativeFileToCompareAgainst = assertOptions.ExpectedFileType switch { NativeFilesType.FromRuntimePack => runtimeNativeDir, NativeFilesType.Relinked => objBuildDir, NativeFilesType.AOT => objBuildDir, - _ => throw new ArgumentOutOfRangeException(nameof(assertOptions.ExpectedFileType)) + _ => throw new ArgumentOutOfRangeException(nameof(assertOptions.BuildOptions.ExpectedFileType)) }; - string buildType = assertOptions.IsPublish ? "publish" : "build"; + + string buildType = assertOptions.BuildOptions.IsPublish ? "publish" : "build"; var nativeFilesToCheck = new List() { "dotnet.native.wasm", "dotnet.native.js" }; - if (assertOptions.RuntimeType == RuntimeVariant.MultiThreaded) + if (assertOptions.BuildOptions.RuntimeType == RuntimeVariant.MultiThreaded) { nativeFilesToCheck.Add("dotnet.native.worker.mjs"); } - if (assertOptions.GlobalizationMode == GlobalizationMode.Hybrid) + if (assertOptions.BuildOptions.GlobalizationMode == GlobalizationMode.Hybrid) { nativeFilesToCheck.Add("dotnet.globalization.js"); } @@ -138,7 +147,7 @@ protected void AssertBundle(AssertWasmSdkBundleOptions assertOptions) actualDotnetFiles[nativeFilename].ActualPath, buildType); - if (assertOptions.ExpectedFileType != NativeFilesType.FromRuntimePack) + if (assertOptions.BuildOptions.ExpectedFileType != NativeFilesType.FromRuntimePack) { if (nativeFilename == "dotnet.native.worker.mjs") { @@ -152,41 +161,36 @@ protected void AssertBundle(AssertWasmSdkBundleOptions assertOptions) } } } - - public void AssertTestMainJsBundle(BuildArgs buildArgs, - BuildProjectOptions buildProjectOptions, - string? buildOutput = null, - AssertTestMainJsAppBundleOptions? assertAppBundleOptions = null) - { - if (buildOutput is not null) - ProjectProviderBase.AssertRuntimePackPath(buildOutput, buildProjectOptions.TargetFramework ?? _defaultTargetFramework); - if (assertAppBundleOptions is not null) - AssertBundle(assertAppBundleOptions); - else - AssertBundle(buildArgs, buildProjectOptions); + public void AssertWasmSdkBundle(Configuration config, MSBuildOptions buildOptions, bool isUsingWorkloads, bool? isNativeBuild = null, string? buildOutput = null) + { + if (isUsingWorkloads && buildOutput is not null) + { + // In no-workload case, the path would be from a restored nuget + ProjectProviderBase.AssertRuntimePackPath(buildOutput, buildOptions.TargetFramework ?? _defaultTargetFramework, buildOptions.RuntimeType); + } + AssertBundle(config, buildOptions, isUsingWorkloads, isNativeBuild); } - - public void AssertWasmSdkBundle(BuildArgs buildArgs, - BuildProjectOptions buildProjectOptions, - string? buildOutput = null, - AssertWasmSdkBundleOptions? assertAppBundleOptions = null) + + public BuildPaths GetBuildPaths(Configuration configuration, bool forPublish) { - if (buildOutput is not null) - ProjectProviderBase.AssertRuntimePackPath(buildOutput, buildProjectOptions.TargetFramework ?? _defaultTargetFramework); - - if (assertAppBundleOptions is not null) - AssertBundle(assertAppBundleOptions); - else - AssertBundle(buildArgs, buildProjectOptions); + Assert.NotNull(ProjectDir); + string configStr = configuration.ToString(); + string objDir = Path.Combine(ProjectDir, "obj", configStr, _defaultTargetFramework); + string binDir = Path.Combine(ProjectDir, "bin", configStr, _defaultTargetFramework); + string binFrameworkDir = GetBinFrameworkDir(configuration, forPublish, _defaultTargetFramework); + + string objWasmDir = Path.Combine(objDir, "wasm", forPublish ? "for-publish" : "for-build"); + // for build: we should take from runtime pack? + return new BuildPaths(objWasmDir, objDir, binDir, binFrameworkDir); } - public override string FindBinFrameworkDir(string config, bool forPublish, string framework, string? projectDir = null) + public override string GetBinFrameworkDir(Configuration config, bool forPublish, string framework, string? projectDir = null) { EnsureProjectDirIsSet(); - string basePath = Path.Combine(projectDir ?? ProjectDir!, "bin", config, framework); + string basePath = Path.Combine(projectDir ?? ProjectDir!, "bin", config.ToString(), framework); if (forPublish) - basePath = FindSubDirIgnoringCase(basePath, "publish"); + basePath = Path.Combine(basePath, "publish"); return Path.Combine(basePath, BundleDirName, "_framework"); } diff --git a/src/mono/wasm/Wasm.Build.Tests/WorkloadTests.cs b/src/mono/wasm/Wasm.Build.Tests/WorkloadTests.cs index 009da0c7bb1aee..0889d53091c02a 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WorkloadTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WorkloadTests.cs @@ -16,7 +16,7 @@ namespace Wasm.Build.Tests { - public class WorkloadTests : TestMainJsTestBase + public class WorkloadTests : WasmTemplateTestsBase { public WorkloadTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) diff --git a/src/mono/wasm/testassets/AppUsingNativeLib/Program.cs b/src/mono/wasm/testassets/AppUsingNativeLib/Program.cs index 5134392c9d8ca3..b56e43a2e84040 100644 --- a/src/mono/wasm/testassets/AppUsingNativeLib/Program.cs +++ b/src/mono/wasm/testassets/AppUsingNativeLib/Program.cs @@ -11,7 +11,7 @@ public class Test { public static int Main(string[] args) { - Console.WriteLine ($"from pinvoke: {SimpleConsole.Test.print_line(100)}"); + Console.WriteLine ($"TestOutput -> from pinvoke: {SimpleConsole.Test.print_line(100)}"); return 0; } diff --git a/src/mono/wasm/testassets/AppUsingNativeLib/native-lib.cpp b/src/mono/wasm/testassets/AppUsingNativeLib/native-lib.cpp index 329a593279fe2d..9babda6dde8bc6 100644 --- a/src/mono/wasm/testassets/AppUsingNativeLib/native-lib.cpp +++ b/src/mono/wasm/testassets/AppUsingNativeLib/native-lib.cpp @@ -6,6 +6,6 @@ int print_line(int x) { - printf("print_line: %d\n", x); + printf("TestOutput -> print_line: %d\n", x); return 42 + x; } diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/App.razor b/src/mono/wasm/testassets/BlazorBasicTestApp/App/App.razor new file mode 100644 index 00000000000000..6fd3ed1b5a3b04 --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/App.razor @@ -0,0 +1,12 @@ + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/BlazorBasicTestApp.csproj b/src/mono/wasm/testassets/BlazorBasicTestApp/App/BlazorBasicTestApp.csproj new file mode 100644 index 00000000000000..5030d68c356ee4 --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/BlazorBasicTestApp.csproj @@ -0,0 +1,14 @@ + + + + net9.0 + enable + enable + + + + + + + + diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/Layout/MainLayout.razor b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Layout/MainLayout.razor new file mode 100644 index 00000000000000..76eb72528390cf --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Layout/MainLayout.razor @@ -0,0 +1,16 @@ +@inherits LayoutComponentBase +
+ + +
+
+ About +
+ +
+ @Body +
+
+
diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/Layout/NavMenu.razor b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Layout/NavMenu.razor new file mode 100644 index 00000000000000..345ee43c02f805 --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Layout/NavMenu.razor @@ -0,0 +1,39 @@ + + + + +@code { + private bool collapseNavMenu = true; + + private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; + + private void ToggleNavMenu() + { + collapseNavMenu = !collapseNavMenu; + } +} diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/Pages/Counter.razor b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Pages/Counter.razor new file mode 100644 index 00000000000000..ef23cb31607f88 --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Pages/Counter.razor @@ -0,0 +1,18 @@ +@page "/counter" + +Counter + +

Counter

+ +

Current count: @currentCount

+ + + +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/Pages/Home.razor b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Pages/Home.razor new file mode 100644 index 00000000000000..6c432724c86b8b --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Pages/Home.razor @@ -0,0 +1,17 @@ +@page "/" + +Home + +

Hello, world!

+ +Welcome to your new app. + +@code { + protected override void OnAfterRender(bool firstRender) + { + if (firstRender) + { + Console.WriteLine("WASM EXIT 0"); + } + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/Program.cs b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Program.cs new file mode 100644 index 00000000000000..b81a54015b025e --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Program.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using BlazorBasicTestApp; + +var builder = WebAssemblyHostBuilder.CreateDefault(args); +builder.RootComponents.Add("#app"); +builder.RootComponents.Add("head::after"); + +builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + +await builder.Build().RunAsync(); diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/_Imports.razor b/src/mono/wasm/testassets/BlazorBasicTestApp/App/_Imports.razor new file mode 100644 index 00000000000000..99e8be8c170d9b --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/_Imports.razor @@ -0,0 +1,10 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.AspNetCore.Components.WebAssembly.Http +@using Microsoft.JSInterop +@using BlazorBasicTestApp +@using BlazorBasicTestApp.Layout diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/wwwroot/favicon.png b/src/mono/wasm/testassets/BlazorBasicTestApp/App/wwwroot/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8422b59695935d180d11d5dbe99653e711097819 GIT binary patch literal 1148 zcmV-?1cUpDP)9h26h2-Cs%i*@Moc3?#6qJID|D#|3|2Hn7gTIYEkr|%Xjp);YgvFmB&0#2E2b=| zkVr)lMv9=KqwN&%obTp-$<51T%rx*NCwceh-E+=&e(oLO`@Z~7gybJ#U|^tB2Pai} zRN@5%1qsZ1e@R(XC8n~)nU1S0QdzEYlWPdUpH{wJ2Pd4V8kI3BM=)sG^IkUXF2-j{ zrPTYA6sxpQ`Q1c6mtar~gG~#;lt=s^6_OccmRd>o{*=>)KS=lM zZ!)iG|8G0-9s3VLm`bsa6e ze*TlRxAjXtm^F8V`M1%s5d@tYS>&+_ga#xKGb|!oUBx3uc@mj1%=MaH4GR0tPBG_& z9OZE;->dO@`Q)nr<%dHAsEZRKl zedN6+3+uGHejJp;Q==pskSAcRcyh@6mjm2z-uG;s%dM-u0*u##7OxI7wwyCGpS?4U zBFAr(%GBv5j$jS@@t@iI8?ZqE36I^4t+P^J9D^ELbS5KMtZ z{Qn#JnSd$15nJ$ggkF%I4yUQC+BjDF^}AtB7w348EL>7#sAsLWs}ndp8^DsAcOIL9 zTOO!!0!k2`9BLk25)NeZp7ev>I1Mn={cWI3Yhx2Q#DnAo4IphoV~R^c0x&nw*MoIV zPthX?{6{u}sMS(MxD*dmd5rU(YazQE59b|TsB5Tm)I4a!VaN@HYOR)DwH1U5y(E)z zQqQU*B%MwtRQ$%x&;1p%ANmc|PkoFJZ%<-uq%PX&C!c-7ypis=eP+FCeuv+B@h#{4 zGx1m0PjS~FJt}3mdt4c!lel`1;4W|03kcZRG+DzkTy|7-F~eDsV2Tx!73dM0H0CTh zl)F-YUkE1zEzEW(;JXc|KR5{ox%YTh{$%F$a36JP6Nb<0%#NbSh$dMYF-{ z1_x(Vx)}fs?5_|!5xBTWiiIQHG<%)*e=45Fhjw_tlnmlixq;mUdC$R8v#j( zhQ$9YR-o%i5Uc`S?6EC51!bTRK=Xkyb<18FkCKnS2;o*qlij1YA@-nRpq#OMTX&RbL<^2q@0qja!uIvI;j$6>~k@IMwD42=8$$!+R^@5o6HX(*n~v0A9xRwxP|bki~~&uFk>U z#P+PQh zyZ;-jwXKqnKbb6)@RaxQz@vm={%t~VbaZrdbaZrdbaeEeXj>~BG?&`J0XrqR#sSlO zg~N5iUk*15JibvlR1f^^1czzNKWvoJtc!Sj*G37QXbZ8LeD{Fzxgdv#Q{x}ytfZ5q z+^k#NaEp>zX_8~aSaZ`O%B9C&YLHb(mNtgGD&Kezd5S@&C=n~Uy1NWHM`t07VQP^MopUXki{2^#ryd94>UJMYW|(#4qV`kb7eD)Q=~NN zaVIRi@|TJ!Rni8J=5DOutQ#bEyMVr8*;HU|)MEKmVC+IOiDi9y)vz=rdtAUHW$yjt zrj3B7v(>exU=IrzC<+?AE=2vI;%fafM}#ShGDZx=0Nus5QHKdyb9pw&4>4XCpa-o?P(Gnco1CGX|U> z$f+_tA3+V~<{MU^A%eP!8R*-sD9y<>Jc7A(;aC5hVbs;kX9&Sa$JMG!W_BLFQa*hM zri__C@0i0U1X#?)Y=)>JpvTnY6^s;fu#I}K9u>OldV}m!Ch`d1Vs@v9 zb}w(!TvOmSzmMBa9gYvD4xocL2r0ds6%Hs>Z& z#7#o9PGHDmfG%JQq`O5~dt|MAQN@2wyJw_@``7Giyy(yyk(m8U*kk5$X1^;3$a3}N^Lp6hE5!#8l z#~NYHmKAs6IAe&A;bvM8OochRmXN>`D`{N$%#dZCRxp4-dJ?*3P}}T`tYa3?zz5BA zTu7uE#GsDpZ$~j9q=Zq!LYjLbZPXFILZK4?S)C-zE1(dC2d<7nO4-nSCbV#9E|E1MM|V<9>i4h?WX*r*ul1 z5#k6;po8z=fdMiVVz*h+iaTlz#WOYmU^SX5#97H~B32s-#4wk<1NTN#g?LrYieCu> zF7pbOLR;q2D#Q`^t%QcY06*X-jM+ei7%ZuanUTH#9Y%FBi*Z#22({_}3^=BboIsbg zR0#jJ>9QR8SnmtSS6x($?$}6$x+q)697#m${Z@G6Ujf=6iO^S}7P`q8DkH!IHd4lB zDzwxt3BHsPAcXFFY^Fj}(073>NL_$A%v2sUW(CRutd%{G`5ow?L`XYSO*Qu?x+Gzv zBtR}Y6`XF4xX7)Z04D+fH;TMapdQFFameUuHL34NN)r@aF4RO%x&NApeWGtr#mG~M z6sEIZS;Uj1HB1*0hh=O@0q1=Ia@L>-tETu-3n(op+97E z#&~2xggrl(LA|giII;RwBlX2^Q`B{_t}gxNL;iB11gEPC>v` zb4SJ;;BFOB!{chn>?cCeGDKuqI0+!skyWTn*k!WiPNBf=8rn;@y%( znhq%8fj2eAe?`A5mP;TE&iLEmQ^xV%-kmC-8mWao&EUK_^=GW-Y3z ksi~={si~={skwfB0gq6itke#r1ONa407*qoM6N<$g11Kq@c;k- literal 0 HcmV?d00001 diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/wwwroot/index.html b/src/mono/wasm/testassets/BlazorBasicTestApp/App/wwwroot/index.html new file mode 100644 index 00000000000000..d9b7c48fe9435a --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/wwwroot/index.html @@ -0,0 +1,32 @@ + + + + + + + BlazorBasicTestApp + + + + + + + + +
+ + + + +
+
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/Component1.razor b/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/Component1.razor new file mode 100644 index 00000000000000..748079eb2e2284 --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/Component1.razor @@ -0,0 +1,3 @@ +
+ This component is defined in the RazorClassLibrary library. +
diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/RazorClassLibrary.csproj b/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/RazorClassLibrary.csproj new file mode 100644 index 00000000000000..a4991806ea06ca --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/RazorClassLibrary.csproj @@ -0,0 +1,18 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/_Imports.razor b/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/_Imports.razor new file mode 100644 index 00000000000000..77285129dabe4b --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/_Imports.razor @@ -0,0 +1 @@ +@using Microsoft.AspNetCore.Components.Web diff --git a/src/mono/wasm/testassets/EntryPoints/AsyncMainWithArgs.cs b/src/mono/wasm/testassets/EntryPoints/AsyncMainWithArgs.cs new file mode 100644 index 00000000000000..594a03a50d9067 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/AsyncMainWithArgs.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading.Tasks; + +public class TestClass { + static void WriteTestOutput(string output) => Console.WriteLine($"TestOutput -> {output}"); + + public static async Task Main(string[] args) + { + int count = args == null ? 0 : args.Length; + WriteTestOutput($"args#: {args?.Length}"); + foreach (var arg in args ?? Array.Empty()) + WriteTestOutput($"arg: {arg}"); + return await Task.FromResult(42 + count); + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/CultureResource.cs b/src/mono/wasm/testassets/EntryPoints/CultureResource.cs new file mode 100644 index 00000000000000..ca928e32200a45 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/CultureResource.cs @@ -0,0 +1,40 @@ +using System; +using System.Runtime.CompilerServices; +using System.Globalization; +using System.Resources; +using System.Threading; + +namespace ResourcesTest +{ + public class TestClass + { + public static int Main(string[] args) + { + string expected; + if (args.Length == 1) + { + string cultureToTest = args[0]; + var newCulture = new CultureInfo(cultureToTest); + Thread.CurrentThread.CurrentCulture = newCulture; + Thread.CurrentThread.CurrentUICulture = newCulture; + + if (cultureToTest == "es-ES") + expected = "hola"; + else if (cultureToTest == "ja-JP") + expected = "\u3053\u3093\u306B\u3061\u306F"; + else + throw new Exception($"Cannot determine the expected output for {cultureToTest}"); + + } else { + expected = "hello"; + } + + var currentCultureName = Thread.CurrentThread.CurrentCulture.Name; + + var rm = new ResourceManager("##RESOURCE_NAME##", typeof(##TYPE_NAME##).Assembly); + Console.WriteLine($"For '{currentCultureName}' got: {rm.GetString("hello")}"); + + return rm.GetString("hello") == expected ? 42 : -1; + } + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/HybridGlobalization.cs b/src/mono/wasm/testassets/EntryPoints/HybridGlobalization.cs similarity index 50% rename from src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/HybridGlobalization.cs rename to src/mono/wasm/testassets/EntryPoints/HybridGlobalization.cs index 094a10d7871790..d318530fb26ffa 100644 --- a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/HybridGlobalization.cs +++ b/src/mono/wasm/testassets/EntryPoints/HybridGlobalization.cs @@ -1,6 +1,8 @@ using System; using System.Globalization; +void WriteTestOutput(string output) => Console.WriteLine($"TestOutput -> {output}"); + try { CompareInfo compareInfo = new CultureInfo("es-ES").CompareInfo; @@ -10,16 +12,16 @@ return 1; } int shouldThrow = compareInfo.Compare("A\u0300", "\u00C0", CompareOptions.IgnoreNonSpace); - Console.WriteLine($"Did not throw as expected but returned {shouldThrow} as a result. Using CompareOptions.IgnoreNonSpace option alone should be unavailable in HybridGlobalization mode."); + WriteTestOutput($"Did not throw as expected but returned {shouldThrow} as a result. Using CompareOptions.IgnoreNonSpace option alone should be unavailable in HybridGlobalization mode."); } catch (PlatformNotSupportedException pnse) { - Console.WriteLine($"HybridGlobalization works, thrown exception as expected: {pnse}."); + WriteTestOutput($"HybridGlobalization works, thrown exception as expected: {pnse}."); return 42; } catch (Exception ex) { - Console.WriteLine($"HybridGlobalization failed, unexpected exception was thrown: {ex}."); + WriteTestOutput($"HybridGlobalization failed, unexpected exception was thrown: {ex}."); return 2; } return 3; diff --git a/src/mono/wasm/testassets/EntryPoints/InvariantGlobalization.cs b/src/mono/wasm/testassets/EntryPoints/InvariantGlobalization.cs new file mode 100644 index 00000000000000..535209b197c5dc --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/InvariantGlobalization.cs @@ -0,0 +1,25 @@ +using System; +using System.Globalization; +using System.Linq; + +// https://github.com/dotnet/runtime/blob/main/docs/design/features/globalization-invariant-mode.md#cultures-and-culture-data + + +void WriteTestOutput(string output) => Console.WriteLine($"TestOutput -> {output}"); +try +{ + CultureInfo culture = new ("es-ES", false); + WriteTestOutput($"es-ES: Is Invariant LCID: {culture.LCID == CultureInfo.InvariantCulture.LCID}"); + + string expectedNativeName = "espa\u00F1ol (Espa\u00F1a)"; + string nativeName = culture.NativeName; + if (nativeName != expectedNativeName) + throw new ArgumentException($"Expected es-ES NativeName: {expectedNativeName}, but got: {nativeName}"); +} +catch (CultureNotFoundException cnfe) +{ + WriteTestOutput($"Could not create es-ES culture: {cnfe.Message}"); +} + +WriteTestOutput($"CurrentCulture.NativeName: {CultureInfo.CurrentCulture.NativeName}"); +return 42; diff --git a/src/mono/wasm/testassets/EntryPoints/InvariantTimezone.cs b/src/mono/wasm/testassets/EntryPoints/InvariantTimezone.cs new file mode 100644 index 00000000000000..1ef6c684b3a195 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/InvariantTimezone.cs @@ -0,0 +1,23 @@ +using System; + +// https://github.com/dotnet/runtime/blob/main/docs/design/features/timezone-invariant-mode.md + +void WriteTestOutput(string output) => Console.WriteLine($"TestOutput -> {output}"); + +var timezonesCount = TimeZoneInfo.GetSystemTimeZones().Count; +WriteTestOutput($"Found {timezonesCount} timezones in the TZ database"); + +TimeZoneInfo utc = TimeZoneInfo.FindSystemTimeZoneById("UTC"); +WriteTestOutput($"{utc.DisplayName} BaseUtcOffset is {utc.BaseUtcOffset}"); + +try +{ + TimeZoneInfo tst = TimeZoneInfo.FindSystemTimeZoneById("Asia/Tokyo"); + WriteTestOutput($"{tst.DisplayName} BaseUtcOffset is {tst.BaseUtcOffset}"); +} +catch (TimeZoneNotFoundException tznfe) +{ + WriteTestOutput($"Could not find Asia/Tokyo: {tznfe.Message}"); +} + +return 42; diff --git a/src/mono/wasm/testassets/EntryPoints/MyDllImport.cs b/src/mono/wasm/testassets/EntryPoints/MyDllImport.cs new file mode 100644 index 00000000000000..a98e9d0334ab2c --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/MyDllImport.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; +namespace ##NAMESPACE##; + +public static class MyDllImports +{ + [DllImport("mylib")] + public static extern int cpp_add(int a, int b); +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/NativeCrypto.cs b/src/mono/wasm/testassets/EntryPoints/NativeCrypto.cs new file mode 100644 index 00000000000000..5dadfd89d41529 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/NativeCrypto.cs @@ -0,0 +1,17 @@ +using System; +using System.Security.Cryptography; + +public class Test +{ + public static int Main() + { + using (SHA256 mySHA256 = SHA256.Create()) + { + byte[] data = { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o' }; + byte[] hashed = mySHA256.ComputeHash(data); + string asStr = string.Join(' ', hashed); + Console.WriteLine("TestOutput -> Hashed: " + asStr); + return 0; + } + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/NativeRebuildNewAssembly.cs b/src/mono/wasm/testassets/EntryPoints/NativeRebuildNewAssembly.cs new file mode 100644 index 00000000000000..fe96960f5385d9 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/NativeRebuildNewAssembly.cs @@ -0,0 +1,17 @@ +using System; +using System.Security.Cryptography; +using System.Text; +public class Test +{ + public static int Main() + { + string input = "Hello, world!"; + using (SHA256 sha256 = SHA256.Create()) + { + byte[] inputBytes = Encoding.UTF8.GetBytes(input); + byte[] hashBytes = sha256.ComputeHash(inputBytes); + Console.WriteLine($"Hash of {input}: {Convert.ToBase64String(hashBytes)}"); + } + return 42; + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/AbiRules.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/AbiRules.cs new file mode 100644 index 00000000000000..3c925215272420 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/AbiRules.cs @@ -0,0 +1,107 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +public struct SingleFloatStruct { + public float Value; +} +public struct SingleDoubleStruct { + public struct Nested1 { + // This field is private on purpose to ensure we treat visibility correctly + double Value; + } + public Nested1 Value; +} +public struct SingleI64Struct { + public Int64 Value; +} +public struct PairStruct { + public int A, B; +} +public unsafe struct MyFixedArray { + public fixed int elements[2]; +} +[System.Runtime.CompilerServices.InlineArray(2)] +public struct MyInlineArray { + public int element0; +} + +public class Test +{ + public static unsafe int Main(string[] argv) + { + var i64_a = 0xFF00FF00FF00FF0L; + var i64_b = ~i64_a; + var resI = direct64(i64_a); + Console.WriteLine("TestOutput -> l (l)=" + resI); + + var sis = new SingleI64Struct { Value = i64_a }; + var resSI = indirect64(sis); + Console.WriteLine("TestOutput -> s (s)=" + resSI.Value); + + var resF = direct(3.14); + Console.WriteLine("TestOutput -> f (d)=" + resF); + + SingleDoubleStruct sds = default; + Unsafe.As(ref sds) = 3.14; + + resF = indirect_arg(sds); + Console.WriteLine("TestOutput -> f (s)=" + resF); + + var res = indirect(sds); + Console.WriteLine("TestOutput -> s (s)=" + res.Value); + + var pair = new PairStruct { A = 1, B = 2 }; + var paires = accept_and_return_pair(pair); + Console.WriteLine("TestOutput -> paires.B=" + paires.B); + + // This test is split into methods to simplify debugging issues with it + var ia = InlineArrayTest1(); + var iares = InlineArrayTest2(ia); + Console.WriteLine($"TestOutput -> iares[0]={iares[0]} iares[1]={iares[1]}"); + + MyFixedArray fa = new (); + for (int i = 0; i < 2; i++) + fa.elements[i] = i; + var fares = accept_and_return_fixedarray(fa); + Console.WriteLine("TestOutput -> fares.elements[1]=" + fares.elements[1]); + + int exitCode = (int)res.Value; + return exitCode; + } + + public static unsafe MyInlineArray InlineArrayTest1 () { + MyInlineArray ia = new (); + for (int i = 0; i < 2; i++) + ia[i] = i; + return ia; + } + + public static unsafe MyInlineArray InlineArrayTest2 (MyInlineArray ia) { + return accept_and_return_inlinearray(ia); + } + + [DllImport("wasm-abi", EntryPoint="accept_double_struct_and_return_float_struct")] + public static extern SingleFloatStruct indirect(SingleDoubleStruct arg); + + [DllImport("wasm-abi", EntryPoint="accept_double_struct_and_return_float_struct")] + public static extern float indirect_arg(SingleDoubleStruct arg); + + [DllImport("wasm-abi", EntryPoint="accept_double_struct_and_return_float_struct")] + public static extern float direct(double arg); + + [DllImport("wasm-abi", EntryPoint="accept_and_return_i64_struct")] + public static extern SingleI64Struct indirect64(SingleI64Struct arg); + + [DllImport("wasm-abi", EntryPoint="accept_and_return_i64_struct")] + public static extern Int64 direct64(Int64 arg); + + [DllImport("wasm-abi", EntryPoint="accept_and_return_pair")] + public static extern PairStruct accept_and_return_pair(PairStruct arg); + + [DllImport("wasm-abi", EntryPoint="accept_and_return_fixedarray")] + public static extern MyFixedArray accept_and_return_fixedarray(MyFixedArray arg); + + [DllImport("wasm-abi", EntryPoint="accept_and_return_inlinearray")] + public static extern MyInlineArray accept_and_return_inlinearray(MyInlineArray arg); +} diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableDifferentAssembly.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableDifferentAssembly.cs new file mode 100644 index 00000000000000..562f2384f40c20 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableDifferentAssembly.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: DisableRuntimeMarshalling] +public class Test +{ + public static int Main() + { + var x = new S { Value = 5 }; + + Console.WriteLine("TestOutput -> Main running " + x.Value); + return 42; + } + + [UnmanagedCallersOnly] + public static void M(S myStruct) { } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableDifferentAssembly_Lib.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableDifferentAssembly_Lib.cs new file mode 100644 index 00000000000000..597b51823e5182 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableDifferentAssembly_Lib.cs @@ -0,0 +1,6 @@ +[assembly: System.Runtime.CompilerServices.DisableRuntimeMarshalling] +public struct __NonBlittableTypeForAutomatedTests__ { } +public struct S { + public int Value; + public __NonBlittableTypeForAutomatedTests__ NonBlittable; +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableSameAssembly.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableSameAssembly.cs new file mode 100644 index 00000000000000..f5103f279e2a19 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableSameAssembly.cs @@ -0,0 +1,21 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: DisableRuntimeMarshalling] +public class Test +{ + public static int Main() + { + var x = new S { Value = 5 }; + + Console.WriteLine("TestOutput -> Main running " + x.Value); + return 42; + } + + [StructLayout(LayoutKind.Auto)] + public struct S { public int Value; public float Value2; } + + [UnmanagedCallersOnly] + public static void M(S myStruct) { } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/BuildNative.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/BuildNative.cs new file mode 100644 index 00000000000000..088651f205c79f --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/BuildNative.cs @@ -0,0 +1,8 @@ +using System; +using System.Runtime.InteropServices; + +Console.WriteLine($"TestOutput -> square: {square(5)}"); +return 42; + +[DllImport("simple")] +static extern int square(int x); \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/ComInterop.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/ComInterop.cs new file mode 100644 index 00000000000000..b347a9162c0659 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/ComInterop.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; + +public class Test +{ + public static int Main(string[] args) + { + var s = new STGMEDIUM(); + ReleaseStgMedium(ref s); + return 42; + } + + [DllImport("ole32.dll")] + internal static extern void ReleaseStgMedium(ref STGMEDIUM medium); +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/DllImportNoWarning.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/DllImportNoWarning.cs new file mode 100644 index 00000000000000..a77515ae54c1ac --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/DllImportNoWarning.cs @@ -0,0 +1,16 @@ +using System; +using System.Runtime.InteropServices; +public class Test +{ + public static int Main() + { + Console.WriteLine("TestOutput -> Main running"); + return 42; + } + + [DllImport("variadic", EntryPoint="sum")] + public unsafe static extern int using_sum_one(delegate* unmanaged callback); + + [DllImport("variadic", EntryPoint="sum")] + public static extern int sum_one(int a, int b); +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/DllImportWarning.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/DllImportWarning.cs new file mode 100644 index 00000000000000..8e6faeef850d15 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/DllImportWarning.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.InteropServices; +public class Test +{ + public static int Main() + { + Console.WriteLine("TestOutput -> Main running"); + return 42; + } + + [DllImport("variadic", EntryPoint="sum")] + public unsafe static extern int using_sum_one(delegate* unmanaged callback); +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/FunctionPointers.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/FunctionPointers.cs new file mode 100644 index 00000000000000..6c3d56a09e3a5a --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/FunctionPointers.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.InteropServices; +public class Test +{ + public static int Main() + { + Console.WriteLine("TestOutput -> Main running"); + return 42; + } + + [DllImport("someting")] + public unsafe static extern void SomeFunction1(delegate* unmanaged callback); +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/ICall_Lib.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/ICall_Lib.cs new file mode 100644 index 00000000000000..351578387cd8fc --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/ICall_Lib.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.CompilerServices; + +public static class Interop +{ + public enum Numbers { A, B, C, D } + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void Square(Numbers x); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void Square(Numbers x, Numbers y); + + public static void Main() + { + // Noop + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/UnmanagedCallback.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallback.cs similarity index 83% rename from src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/UnmanagedCallback.cs rename to src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallback.cs index 623cd2c5cb707d..0482b445a42f9e 100644 --- a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/UnmanagedCallback.cs +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallback.cs @@ -18,7 +18,7 @@ file class Interop { [UnmanagedCallersOnly(EntryPoint = "ConflictManagedFunc")] public static int Managed8\u4F60Func(int number) { - Console.WriteLine($"Conflict.A.Managed8\u4F60Func({number}) -> {number}"); + Console.WriteLine($"TestOutput -> Conflict.A.Managed8\u4F60Func({number}) -> {number}"); return number; } } @@ -30,7 +30,7 @@ file partial class Interop public static int Managed8\u4F60Func(int number) { // called from UnmanagedFunc - Console.WriteLine($"Managed8\u4F60Func({number}) -> 42"); + Console.WriteLine($"TestOutput -> Managed8\u4F60Func({number}) -> 42"); return 42; } diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallbackInFile.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallbackInFile.cs new file mode 100644 index 00000000000000..a56183c29247df --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallbackInFile.cs @@ -0,0 +1,16 @@ +using System; +using System.Runtime.InteropServices; +public class Test +{ + public static int Main() + { + Console.WriteLine("TestOutput -> Main running"); + return 42; + } +} + +file class Foo +{ + [UnmanagedCallersOnly] + public unsafe static extern void SomeFunction1(int i); +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallbackNamespaced.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallbackNamespaced.cs new file mode 100644 index 00000000000000..334e49ef432a26 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallbackNamespaced.cs @@ -0,0 +1,42 @@ +using System; +using System.Runtime.InteropServices; + +public class Test +{ + public unsafe static int Main() + { + ((delegate* unmanaged)&A.Conflict.C)(); + ((delegate* unmanaged)&B.Conflict.C)(); + ((delegate* unmanaged)&A.Conflict.C\u733f)(); + ((delegate* unmanaged)&B.Conflict.C\u733f)(); + return 42; + } +} + +namespace A { + public class Conflict { + [UnmanagedCallersOnly(EntryPoint = "A_Conflict_C")] + public static void C() { + Console.WriteLine("TestOutput -> A.Conflict.C"); + } + + [UnmanagedCallersOnly(EntryPoint = "A_Conflict_C\u733f")] + public static void C\u733f() { + Console.WriteLine("TestOutput -> A.Conflict.C_\U0001F412"); + } + } +} + +namespace B { + public class Conflict { + [UnmanagedCallersOnly(EntryPoint = "B_Conflict_C")] + public static void C() { + Console.WriteLine("TestOutput -> B.Conflict.C"); + } + + [UnmanagedCallersOnly(EntryPoint = "B_Conflict_C\u733f")] + public static void C\u733f() { + Console.WriteLine("TestOutput -> B.Conflict.C_\U0001F412"); + } + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/VariadicFunctions.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/VariadicFunctions.cs new file mode 100644 index 00000000000000..5357065a9a5997 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/VariadicFunctions.cs @@ -0,0 +1,21 @@ +using System; +using System.Runtime.InteropServices; +public class Test +{ + public static int Main(string[] args) + { + Console.WriteLine("TestOutput -> Main running"); + if (args.Length > 2) + { + // We don't want to run this, because we can't call variadic functions + Console.WriteLine($"sum_three: {sum_three(7, 14, 21)}"); + Console.WriteLine($"sum_two: {sum_two(3, 6)}"); + Console.WriteLine($"sum_one: {sum_one(5)}"); + } + return 42; + } + + [DllImport("variadic", EntryPoint="sum")] public static extern int sum_one(int a); + [DllImport("variadic", EntryPoint="sum")] public static extern int sum_two(int a, int b); + [DllImport("variadic", EntryPoint="sum")] public static extern int sum_three(int a, int b, int c); +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/SimpleSourceChange.cs b/src/mono/wasm/testassets/EntryPoints/SimpleSourceChange.cs new file mode 100644 index 00000000000000..e86104e6df8f55 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/SimpleSourceChange.cs @@ -0,0 +1,7 @@ +public class TestClass +{ + public static int Main() + { + return 55; + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/SkiaSharp.cs b/src/mono/wasm/testassets/EntryPoints/SkiaSharp.cs new file mode 100644 index 00000000000000..97236c63e5a665 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/SkiaSharp.cs @@ -0,0 +1,14 @@ +using System; +using SkiaSharp; + +public class Test +{ + public static int Main() + { + using SKFileStream skfs = new SKFileStream("mono.png"); + using SKImage img = SKImage.FromEncodedData(skfs); + + Console.WriteLine ($"Size: {skfs.Length} Height: {img.Height}, Width: {img.Width}"); + return 0; + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/SyncMainWithArgs.cs b/src/mono/wasm/testassets/EntryPoints/SyncMainWithArgs.cs new file mode 100644 index 00000000000000..08b859d5173e9e --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/SyncMainWithArgs.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading.Tasks; + +public class TestClass { + static void WriteTestOutput(string output) => Console.WriteLine($"TestOutput -> {output}"); + + public static int Main(string[] args) + { + int count = args == null ? 0 : args.Length; + WriteTestOutput($"args#: {args?.Length}"); + foreach (var arg in args ?? Array.Empty()) + WriteTestOutput($"arg: {arg}"); + return 42 + count; + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/SatelliteAssemblyFromProjectRef/LibraryWithResources/LibraryWithResources.csproj b/src/mono/wasm/testassets/SatelliteAssemblyFromProjectRef/LibraryWithResources/LibraryWithResources.csproj index c6c44c64130006..3043227ce00b5f 100644 --- a/src/mono/wasm/testassets/SatelliteAssemblyFromProjectRef/LibraryWithResources/LibraryWithResources.csproj +++ b/src/mono/wasm/testassets/SatelliteAssemblyFromProjectRef/LibraryWithResources/LibraryWithResources.csproj @@ -1,5 +1,5 @@ - net7.0 + net9.0 diff --git a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs b/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs deleted file mode 100644 index c7b1219b6aabde..00000000000000 --- a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; - -// https://github.com/dotnet/runtime/blob/main/docs/design/features/globalization-invariant-mode.md#cultures-and-culture-data -try -{ - CultureInfo culture = new ("es-ES", false); - Console.WriteLine($"es-ES: Is Invariant LCID: {culture.LCID == CultureInfo.InvariantCulture.LCID}"); - - var nativeNameArg = args.FirstOrDefault(arg => arg.StartsWith("nativename=")); - if (nativeNameArg == null) - throw new ArgumentException($"When not in invariant mode, InvariantGlobalization.cs expects nativename argument with expected es-ES NativeName."); - string expectedNativeName = nativeNameArg.Substring(11).Trim('"'); // skip nativename= - string nativeName = culture.NativeName; - if (nativeName != expectedNativeName) - throw new ArgumentException($"Expected es-ES NativeName: {expectedNativeName}, but got: {nativeName}"); -} -catch (CultureNotFoundException cnfe) -{ - Console.WriteLine($"Could not create es-ES culture: {cnfe.Message}"); -} - -Console.WriteLine($"CurrentCulture.NativeName: {CultureInfo.CurrentCulture.NativeName}"); -return 42; diff --git a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantTimezone.cs b/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantTimezone.cs deleted file mode 100644 index deba28c7be27ea..00000000000000 --- a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantTimezone.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -// https://github.com/dotnet/runtime/blob/main/docs/design/features/timezone-invariant-mode.md - -var timezonesCount = TimeZoneInfo.GetSystemTimeZones().Count; -Console.WriteLine($"Found {timezonesCount} timezones in the TZ database"); - -TimeZoneInfo utc = TimeZoneInfo.FindSystemTimeZoneById("UTC"); -Console.WriteLine($"{utc.DisplayName} BaseUtcOffset is {utc.BaseUtcOffset}"); - -try -{ - TimeZoneInfo tst = TimeZoneInfo.FindSystemTimeZoneById("Asia/Tokyo"); - Console.WriteLine($"{tst.DisplayName} BaseUtcOffset is {tst.BaseUtcOffset}"); -} -catch (TimeZoneNotFoundException tznfe) -{ - Console.WriteLine($"Could not find Asia/Tokyo: {tznfe.Message}"); -} - -return 42; diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/WasmBasicTestApp.csproj b/src/mono/wasm/testassets/WasmBasicTestApp/App/WasmBasicTestApp.csproj index 548b310286bedf..d65c69a4ba4aba 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/WasmBasicTestApp.csproj +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/WasmBasicTestApp.csproj @@ -4,6 +4,8 @@ browser-wasm Exe true + + $(NoWarn);CS0169 @@ -15,11 +17,11 @@ - + - + diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/index.html b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/index.html index 5179693a495f65..f0429391614fb3 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/index.html +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/index.html @@ -8,7 +8,7 @@ - + diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js index b2354f0672c3f1..8a773e2756aaa1 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js @@ -26,6 +26,16 @@ dotnet .withExitCodeLogging() .withExitOnUnhandledError(); +const logLevel = params.get("MONO_LOG_LEVEL"); +const logMask = params.get("MONO_LOG_MASK"); +if (logLevel !== null && logMask !== null) { + dotnet.withDiagnosticTracing(true); // enable JavaScript tracing + dotnet.withConfig({environmentVariables: { + "MONO_LOG_LEVEL": logLevel, + "MONO_LOG_MASK": logMask, + }}); +} + // Modify runtime start based on test case switch (testCase) { case "SatelliteAssembliesTest": @@ -151,6 +161,9 @@ switch (testCase) { case "OverrideBootConfigName": dotnet.withConfigSrc("boot.json"); break; + case "MainWithArgs": + dotnet.withApplicationArgumentsFromQuery(); + break; } const { setModuleImports, Module, getAssemblyExports, getConfig, INTERNAL } = await dotnet.create(); @@ -199,6 +212,8 @@ try { exit(0); break; case "OutErrOverrideWorks": + case "DotnetRun": + case "MainWithArgs": dotnet.run(); break; case "DebugLevelTest": diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/Library/Json.cs b/src/mono/wasm/testassets/WasmBasicTestApp/Json/Json.cs similarity index 100% rename from src/mono/wasm/testassets/WasmBasicTestApp/Library/Json.cs rename to src/mono/wasm/testassets/WasmBasicTestApp/Json/Json.cs diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/Library/Json.csproj b/src/mono/wasm/testassets/WasmBasicTestApp/Json/Json.csproj similarity index 100% rename from src/mono/wasm/testassets/WasmBasicTestApp/Library/Json.csproj rename to src/mono/wasm/testassets/WasmBasicTestApp/Json/Json.csproj diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/Library/Library.cs b/src/mono/wasm/testassets/WasmBasicTestApp/Library/Library.cs new file mode 100644 index 00000000000000..a1ed50130d7136 --- /dev/null +++ b/src/mono/wasm/testassets/WasmBasicTestApp/Library/Library.cs @@ -0,0 +1,5 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Empty external library that is not referenced by default by the app +// this file can be updated using ReplaceFile or UpdateFile if needed \ No newline at end of file diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/Library/Library.csproj b/src/mono/wasm/testassets/WasmBasicTestApp/Library/Library.csproj new file mode 100644 index 00000000000000..3d5e0e2093c1a9 --- /dev/null +++ b/src/mono/wasm/testassets/WasmBasicTestApp/Library/Library.csproj @@ -0,0 +1,7 @@ + + + net9.0 + Library + true + + diff --git a/src/mono/wasm/testassets/marshal_ilgen_test.cs b/src/mono/wasm/testassets/marshal_ilgen_test.cs index 1c52a8b5ee3497..685ae119ccc4a8 100644 --- a/src/mono/wasm/testassets/marshal_ilgen_test.cs +++ b/src/mono/wasm/testassets/marshal_ilgen_test.cs @@ -4,7 +4,7 @@ int[] x = new int[0]; MyClass.call_needing_marhsal_ilgen(x); -Console.WriteLine("call_needing_marhsal_ilgen got called"); +Console.WriteLine("TestOutput -> call_needing_marhsal_ilgen got called"); return 42; diff --git a/src/mono/wasm/testassets/native-libs/local.c b/src/mono/wasm/testassets/native-libs/local.c index df7987bb8f38df..125ee7c4b704b1 100644 --- a/src/mono/wasm/testassets/native-libs/local.c +++ b/src/mono/wasm/testassets/native-libs/local.c @@ -6,5 +6,5 @@ void UnmanagedFunc() int ret = 0; printf("UnmanagedFunc calling ManagedFunc\n"); ret = ManagedFunc(123); - printf("ManagedFunc returned %d\n", ret); + printf("TestOutput -> ManagedFunc returned %d\n", ret); } diff --git a/src/mono/wasm/testassets/native-libs/mylib.cpp b/src/mono/wasm/testassets/native-libs/mylib.cpp new file mode 100644 index 00000000000000..b781006fec5961 --- /dev/null +++ b/src/mono/wasm/testassets/native-libs/mylib.cpp @@ -0,0 +1,7 @@ +#include + +extern "C" { + int cpp_add(int a, int b) { + return a + b; + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/native-libs/native-lib.o b/src/mono/wasm/testassets/native-libs/native-lib.o index 10ccf42c5ff2397c2201e74ed6ffa7237ed4f24f..5b5218ec686a8d75c3e2ea5f5926702c1bf4775e 100644 GIT binary patch delta 275 zcmXYry-tNN7(mMnG3Gj4R}*tX;-H2UM1+8YKbwPzi#sXpSHzTp?FarW@(7wcxHIA7WMg>^7O#5jdi3at_xiKDO`LgWYK?`eHOw}N zGg}KT1B-@`Q`TP?kYHe1r1;o@_1A4(X$lYc3!a2q AUjP6A delta 200 zcmaFKGLL1#WA14U4Gj#842}%k0u%qJ%djwV6%=LWmBi;{=A|+TFfcN)>G|jtmlQED z2u!wTv}as7xt>wEzJt-wSkFMuKtV%4zo10FSif99vq(QVF*zeuKRvTVKRKf)KR2^9 zS1&z3KRqY4IKQ+gIaMz?KUX)kq9nB_FEK|Ks4Sy22`rYAQCUgsm^0QKtOB75k rOpQz}j7*J Date: Thu, 12 Dec 2024 11:52:42 +0100 Subject: [PATCH 45/70] [Mono]: Update Mono diagnostic docs. (#110621) * [Mono]: Update Mono diagnostic docs. * Review feedback. --- docs/design/mono/diagnostics-tracing.md | 385 +++++++++++++++++------- 1 file changed, 268 insertions(+), 117 deletions(-) diff --git a/docs/design/mono/diagnostics-tracing.md b/docs/design/mono/diagnostics-tracing.md index 898ea1a3a96ff4..6485a25d241c62 100644 --- a/docs/design/mono/diagnostics-tracing.md +++ b/docs/design/mono/diagnostics-tracing.md @@ -6,7 +6,7 @@ MonoVM includes support for EventPipe and DiagnosticServer components used to ge ## Scenarios -.Net supports several different EventPipe scenarios using tools mainly from diagnostics repository. MonoVM include support for several of these scenarios, running tools like `dotnet-counters` and `dotnet-trace` to collect and analyze runtime performance data. Other things like requesting a core/gc dump or attaching profiler over EventPipe is currently not supported on MonoVM. +.NET supports several different EventPipe scenarios using tools mainly from diagnostics repository. MonoVM include support for several of these scenarios, running tools like `dotnet-counters` and `dotnet-trace` to collect and analyze runtime performance data. Other things like requesting a core dump or attaching profiler over EventPipe is currently not supported on MonoVM. Due to differences between runtimes many of the NativeRuntimeEvents won't apply to MonoVM. Only a selected amount of NativeRuntimeEvents will initially be added to MonoVM. Current supported NativeRuntimeEvents can be viewed in MonoVM include file, https://github.com/dotnet/runtime/blob/main/src/mono/mono/eventpipe/gen-eventing-event-inc.lst. Since primary focus is EventPipe and mobile platforms (iOS/Android), ETW and LTTng providers have currently not been integrated/enabled for NativeRuntimeEvents on MonoVM. @@ -95,17 +95,13 @@ On the other hand, if it is desired to include all components, there are two opt true ``` -## Install diagnostic client tooling -``` -dotnet tool install -g dotnet-trace --add-source=https://aka.ms/dotnet-tools/index.json -dotnet tool install -g dotnet-counters --add-source=https://aka.ms/dotnet-tools/index.json -``` - -`dotnet-dsrouter` is still installed from preview source: +## Install diagnostic client tooling -``` -dotnet tool install -g dotnet-dsrouter --add-source=https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json +```sh +$ dotnet tool install -g dotnet-trace --add-source=https://aka.ms/dotnet-tools/index.json +$ dotnet tool install -g dotnet-counters --add-source=https://aka.ms/dotnet-tools/index.json +$ dotnet tool install -g dotnet-dsrouter --add-source=https://aka.ms/dotnet-tools/index.json ``` If tools have already been installed, they can all be updated to latest version using `update` keyword instead of `install` keyword. @@ -122,7 +118,54 @@ Prerequisites when running below diagnostic scenarios: * If anything EventSource related is used, make sure EventSourceSupport is enabled when building application (or ILLinker can remove needed EventSource classes). If just running tracing, collecting native EventPipe events emitted by SampleProfiler, DotNetRuntime or MonoProfiler providers, EventSourceSupport can be enabled or disabled. * Install needed diagnostic tooling on development machine. -### Application running diagnostics and connecting over loopback interface +### Application running diagnostics using default .NET 8 configuration + +Starting in .NET 8, enhancements to `dotnet-dsrouter` supporting most of below described scenarios, using a smaller subset of available configurations using loopback interface on emulators/simulators and physical devices attached over usb. `dotnet-dsrouter` outputs detailed information on how to launch runtime as well as connect to the instance using diagnostic tooling. + +Starting `dotnet-dsrouter` to trace an application running on iOS simulator could be done using the `ios-sim` profile: + +```sh +$ dotnet-dsrouter ios-sim -i +WARNING: dotnet-dsrouter is a development tool not intended for production environments. + +How to connect current dotnet-dsrouter pid=26600 with iOS simulator and diagnostics tooling. +Start an application on iOS simulator with ONE of the following environment variables set: +[Default Tracing] +DOTNET_DiagnosticPorts=127.0.0.1:9000,nosuspend,listen +[Startup Tracing] +DOTNET_DiagnosticPorts=127.0.0.1:9000,suspend,listen +Run diagnostic tool connecting application on iOS simulator through dotnet-dsrouter pid=26600: +dotnet-trace collect -p 26600 +See https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dsrouter for additional details and examples. + +info: dotnet-dsrouter-26600[0] + Starting dotnet-dsrouter using pid=26600 +info: dotnet-dsrouter-26600[0] + Starting IPC server (dotnet-diagnostic-dsrouter-26600) <--> TCP client (127.0.0.1:9000) router. +``` + +Using `-i` outputs enough details on how to configure the `DOTNET_DiagnosticPorts` when launching the application depending on default or startup tracing needs as well as how to run diagnostic tooling against the specific `dotnet-dsrouter` instance. + +For example, running default tracing together with above `dotnet-dsrouter` instance, set `DOTNET_DiagnosticPorts=127.0.0.1:9000,nosuspend,listen` environment variable for the launched application running on iOS simulator and then run diagnostic tooling using pid of running `dotnet-dsrouter`: + +```sh +$ dotnet-trace collect -p 26600 +``` + +Changing the environment variable for launched application to `DOTNET_DiagnosticPorts=127.0.0.1:9000,suspend,listen` will enable startup tracing using above `dotnet-dsrouter` instance. + +.NET 8 version of `dotnet-dsrouter` supports the following profiles that could be used when running diagnostic tool against iOS/Android simulator/emulator/devices: + + * `ios-sim` + * `ios` + * `android-emu` + * `android` + + Running profiles with `-i` gives detailed info on how to launch application and diagnostic tooling together with `dotnet-dsrouter`. + +In case the default profiles described above are too limited, the following sections describes all low level configuration details and options needed to trace applications on iOS/Android using `dotnet-dsrouter`. + +#### Application running diagnostics using custom configuration on simulator/emulator Starting up application using `DOTNET_DiagnosticPorts=127.0.0.1:9000,nosuspend` on iOS, or `DOTNET_DiagnosticPorts=10.0.2.2:9000,nosuspend` on Android, will connect to `dotnet-dsrouter` listening on loopback port 9000 (can be any available port) on local machine. Once runtime is connected, it is possible to connect diagnostic tools like `dotnet-counters`, `dotnet-trace`, towards `dotnet-dsrouter` local IPC interface. To include startup events in EventPipe sessions, change `nosuspend` to `suspend` and runtime startup and wait for diagnostic tooling to connect before resuming. @@ -140,8 +183,6 @@ https://developer.android.com/studio/command-line/adb#forwardports On IOS, `--forward-port` works in the scenario where DiagnosticServer runs in listening mode on device (connected over usb) using loopback interface. -Latest enhancements to `dotnet-dsrouter` have opened opportunity to run most of below described scenarios, using a smaller subset of available configurations using loopback interface on emulators/simulators and physical devices attached over usb. - Runtime configuration: `DIAGNOSTIC_PORTS=127.0.0.1:9000,suspend|nosuspend,listen` @@ -152,178 +193,266 @@ Use `nosuspend` keyword if runtime do regular startup, not waiting for any diagn When running towards Android emulator or device (connected over usb): -``` -dotnet-dsrouter server-client -ipcs ~/myport -tcpc 127.0.0.1:9000 --forward-port Android +```sh +$ dotnet-dsrouter server-client -ipcs ~/myport -tcpc 127.0.0.1:9000 --forward-port Android ``` When running towards iOS simulator: -``` -dotnet-dsrouter server-client -ipcs ~/myport -tcpc 127.0.0.1:9000 +```sh +$ dotnet-dsrouter server-client -ipcs ~/myport -tcpc 127.0.0.1:9000 ``` When running towards iOS device (connected over usb): -``` -dotnet-dsrouter server-client -ipcs ~/myport -tcpc 127.0.0.1:9000 --forward-port iOS +```sh +$ dotnet-dsrouter server-client -ipcs ~/myport -tcpc 127.0.0.1:9000 --forward-port iOS ``` Run diagnostic tooling like this, regardless of `suspend|nosuspend`, `Android|iOS` or `simulator|emulator|device` scenarios are being used: -``` -dotnet-trace collect --diagnostic-port ~/myport,connect +```sh +$ dotnet-trace collect --diagnostic-port ~/myport,connect ``` or +```sh +$ dotnet-counters monitor --diagnostic-port ~/myport,connect ``` -dotnet-counters monitor --diagnostic-port ~/myport,connect -``` + +Android and iOS SDK's have documented similar steps on how to setup profiling/tracing: + +https://github.com/xamarin/xamarin-android/blob/main/Documentation/guides/tracing.md + +https://github.com/xamarin/xamarin-macios/wiki/Profiling #### Example using dotnet-counters using sample app on iOS simulator +`dotnet-dsrouter` needs to run using a compatible configuration depending on scenario. Either launch a new instance for every run or have a background instance running over several sessions using same configuration. + +##### Default .NET 8 configuration: + Make sure the following is enabled in https://github.com/dotnet/runtime/blob/main/src/mono/sample/iOS/Makefile, -``` +```Makefile RUNTIME_COMPONENTS=diagnostics_tracing -DIAGNOSTIC_PORTS=127.0.0.1:9000,nosuspend +DIAGNOSTIC_PORTS=127.0.0.1:9000,nosuspend,listen ``` -`dotnet-dsrouter` needs to run using a compatible configuration depending on scenario. Either launch a new instance for every run or have a background instance running over several sessions using same configuration. +```sh +$ dotnet-dsrouter ios-sim & +``` +```sh +$ dotnet-counters monitor -p ``` -dotnet-dsrouter server-server -ipcs ~/myport -tcps 127.0.0.1:9000 & + +##### Custom configuration: + +Make sure the following is enabled in https://github.com/dotnet/runtime/blob/main/src/mono/sample/iOS/Makefile, + +```Makefile +RUNTIME_COMPONENTS=diagnostics_tracing +DIAGNOSTIC_PORTS=127.0.0.1:9000,nosuspend ``` +```sh +$ dotnet-dsrouter server-server -ipcs ~/myport -tcps 127.0.0.1:9000 & ``` + +```sh cd src/mono/sample/iOS/ -make run-sim +$ make run-sim ``` -``` -dotnet-counters monitor --diagnostic-port ~/myport,connect +```sh +$ dotnet-counters monitor --diagnostic-port ~/myport,connect ``` #### Example using dotnet-counters using sample app on Android emulator +`dotnet-dsrouter` needs to run using a compatible configuration depending on scenario. Either launch a new instance for every run or have a background instance running over several sessions using same configuration. + +##### Default .NET 8 configuration: + Make sure the following is enabled in https://github.com/dotnet/runtime/blob/main/src/mono/sample/Android/Makefile, -``` +```Makefile RUNTIME_COMPONENTS=diagnostics_tracing -DIAGNOSTIC_PORTS=10.0.2.2:9000,nosuspend +DIAGNOSTIC_PORTS=10.0.2.2:9000,nosuspend,connect ``` -`dotnet-dsrouter` needs to run using a compatible configuration depending on scenario. Either launch a new instance for every run or have a background instance running over several sessions using same configuration. +```sh +$ dotnet-dsrouter android-emu & +``` + +```sh +cd src/mono/sample/Android/ +$ make run +``` +```sh +$ dotnet-counters monitor -p ``` -dotnet-dsrouter server-server -ipcs ~/myport -tcps 10.0.2.2:9000 & + +##### Custom configuration: + +Make sure the following is enabled in https://github.com/dotnet/runtime/blob/main/src/mono/sample/Android/Makefile, + +```Makefile +RUNTIME_COMPONENTS=diagnostics_tracing +DIAGNOSTIC_PORTS=10.0.2.2:9000,nosuspend ``` +```sh +$ dotnet-dsrouter server-server -ipcs ~/myport -tcps 10.0.2.2:9000 & ``` + +```sh cd src/mono/sample/Android/ -make run +$ make run ``` -``` -dotnet-counters monitor --diagnostic-port ~/myport,connect +```sh +$ dotnet-counters monitor --diagnostic-port ~/myport,connect ``` Using `adb` port forwarding it is possible to use `127.0.0.1:9000` in above scenario and adding `--forward-port Android` to `dotnet-dsrouter` launch arguments. That will automatically run needed `adb` commands. NOTE, `dotnet-dsrouter` needs to find `adb` tool for this to work, see above for more details. #### Example using dotnet-trace startup tracing using sample app on iOS simulator +`dotnet-dsrouter` needs to run using a compatible configuration depending on scenario. Either launch a new instance for every run or have a background instance running over several sessions using same configuration. + +##### Default .NET 8 configuration: + Make sure the following is enabled in https://github.com/dotnet/runtime/blob/main/src/mono/sample/iOS/Makefile, -``` +```Makefile RUNTIME_COMPONENTS=diagnostics_tracing -DIAGNOSTIC_PORTS=127.0.0.1:9000,suspend +DIAGNOSTIC_PORTS=127.0.0.1:9000,suspend,listen ``` -`dotnet-dsrouter` needs to run using a compatible configuration depending on scenario. Either launch a new instance for every run or have a background instance running over several sessions using same configuration. - +```sh +$ dotnet-dsrouter ios-sim & ``` -dotnet-dsrouter client-server -tcpc ~/myport -tcps 127.0.0.1:9000 & + +```sh +$ dotnet-counters monitor -p ``` +##### Custom configuration: + +Make sure the following is enabled in https://github.com/dotnet/runtime/blob/main/src/mono/sample/iOS/Makefile, + +```Makefile +RUNTIME_COMPONENTS=diagnostics_tracing +DIAGNOSTIC_PORTS=127.0.0.1:9000,suspend ``` -dotnet-trace collect --diagnostic-port ~/myport + +```sh +$ dotnet-dsrouter client-server -tcpc ~/myport -tcps 127.0.0.1:9000 & ``` +```sh +$ dotnet-trace collect --diagnostic-port ~/myport ``` + +```sh cd src/mono/sample/iOS/ -make run-sim +$ make run-sim ``` Since `dotnet-dsrouter` is capable to run several different modes, it is also possible to do startup tracing using server-server mode. Make sure the following is enabled in https://github.com/dotnet/runtime/blob/main/src/mono/sample/iOS/Makefile, -``` +```Makefile RUNTIME_COMPONENTS=diagnostics_tracing DIAGNOSTIC_PORTS=127.0.0.1:9000,suspend ``` -`dotnet-dsrouter` needs to run using a compatible configuration depending on scenario. Either launch a new instance for every run or have a background instance running over several sessions using same configuration. - -``` -dotnet-dsrouter server-server -ipcs ~/myport -tcps 127.0.0.1:9000 & +```sh +$ dotnet-dsrouter server-server -ipcs ~/myport -tcps 127.0.0.1:9000 & ``` -``` +```sh cd src/mono/sample/iOS/ -make run-sim +$ make run-sim ``` -``` -dotnet-trace collect --diagnostic-port ~/myport,connect +```sh +$ dotnet-trace collect --diagnostic-port ~/myport,connect ``` #### Example using dotnet-trace startup tracing using sample app on Android emulator +`dotnet-dsrouter` needs to run using a compatible configuration depending on scenario. Either launch a new instance for every run or have a background instance running over several sessions using same configuration. + +##### Default .NET 8 configuration: + Make sure the following is enabled in https://github.com/dotnet/runtime/blob/main/src/mono/sample/Android/Makefile, -``` +```Makefile RUNTIME_COMPONENTS=diagnostics_tracing -DIAGNOSTIC_PORTS=10.0.2.2:9000,suspend +DIAGNOSTIC_PORTS=10.0.2.2:9000,suspend,connect ``` -`dotnet-dsrouter` needs to run using a compatible configuration depending on scenario. Either launch a new instance for every run or have a background instance running over several sessions using same configuration. +```sh +$ dotnet-dsrouter android-emu & +``` +```sh +cd src/mono/sample/Android/ +$ make run ``` -dotnet-dsrouter client-server -tcpc ~/myport -tcps 127.0.0.1:9000 & + +```sh +$ dotnet-counters monitor -p ``` +##### Custom configuration: + +Make sure the following is enabled in https://github.com/dotnet/runtime/blob/main/src/mono/sample/Android/Makefile, + +```Makefile +RUNTIME_COMPONENTS=diagnostics_tracing +DIAGNOSTIC_PORTS=10.0.2.2:9000,suspend ``` -dotnet-trace collect --diagnostic-port ~/myport + +```sh +$ dotnet-dsrouter client-server -tcpc ~/myport -tcps 127.0.0.1:9000 & ``` +```sh +$ dotnet-trace collect --diagnostic-port ~/myport ``` + +```sh cd src/mono/sample/Android/ -make run +$ make run ``` Since `dotnet-dsrouter` is capable to run several different modes, it is also possible to do startup tracing using server-server mode. Make sure the following is enabled in https://github.com/dotnet/runtime/blob/main/src/mono/sample/Android/Makefile, -``` +```Makefile RUNTIME_COMPONENTS=diagnostics_tracing DIAGNOSTIC_PORTS=10.0.2.2:9000,suspend ``` -`dotnet-dsrouter` needs to run using a compatible configuration depending on scenario. Either launch a new instance for every run or have a background instance running over several sessions using same configuration. - -``` -dotnet-dsrouter server-server -ipcs ~/myport -tcps 10.0.2.2:9000 & +```sh +$ dotnet-dsrouter server-server -ipcs ~/myport -tcps 10.0.2.2:9000 & ``` -``` +```sh cd src/mono/sample/Android/ -make run +$ make run ``` -``` -dotnet-trace collect --diagnostic-port ~/myport,connect +```sh +$ dotnet-trace collect --diagnostic-port ~/myport,connect ``` Using `adb` port forwarding it is possible to use `127.0.0.1:9000` in above scenario and adding `--forward-port Android` to `dotnet-dsrouter` launch arguments. That will automatically run needed `adb` commands. NOTE, `dotnet-dsrouter` needs to find `adb` tool for this to work, see above for more details. @@ -338,81 +467,111 @@ Below scenarios uses loopback interface together with device connected to develo #### Android -Launch application using the following environment variable set, `DIAGNOSTIC_PORTS=127.0.0.1:9000,suspend` - `dotnet-dsrouter` needs to run using a compatible configuration depending on scenario. Either launch a new instance for every run or have a background instance running over several sessions using same configuration. -``` -dotnet-dsrouter server-server -ipcs ~/myport -tcps 127.0.0.1:9000 --forward-port Android & +##### Default .NET 8 configuration: + +Launch application using the following environment variable set, `DIAGNOSTIC_PORTS=127.0.0.1:9000,suspend,connect` + +```sh +$ dotnet-dsrouter android & ``` Run application on device connected over usb. +```sh +$ dotnet-trace collect -p +``` + +##### Custom configuration: + +Launch application using the following environment variable set, `DIAGNOSTIC_PORTS=127.0.0.1:9000,suspend` + +```sh +$ dotnet-dsrouter server-server -ipcs ~/myport -tcps 127.0.0.1:9000 --forward-port Android & ``` -dotnet-trace collect --diagnostic-port ~/myport,connect + +Run application on device connected over usb. + +```sh +$ dotnet-trace collect --diagnostic-port ~/myport,connect ``` The same scenario could be run pushing the TCP/IP listener over to device. Following changes needs to be done to above configuration: `DIAGNOSTIC_PORTS=127.0.0.1:9000,suspend,listen` -``` -dotnet-dsrouter server-client -ipcs ~/myport -tcpc 127.0.0.1:9000 --forward-port Android & +```sh +$ dotnet-dsrouter server-client -ipcs ~/myport -tcpc 127.0.0.1:9000 --forward-port Android & ``` #### iOS -Launch application using the following environment variable set, `DIAGNOSTIC_PORTS=*:9000,suspend,listen` - `dotnet-dsrouter` needs to run using a compatible configuration depending on scenario. Either launch a new instance for every run or have a background instance running over several sessions using same configuration. -``` -dotnet-dsrouter server-client -ipcs ~/myport -tcpc 127.0.0.1:9000 --forward-port iOS & +##### Default .NET 8 configuration: + +Launch application using the following environment variable set, `DIAGNOSTIC_PORTS=127.0.0.1:9000,suspend,listen` + +```sh +$ dotnet-dsrouter ios & ``` Run application on device connected over usb. +```sh +$ dotnet-trace collect -p +``` + +##### Custom configuration: + +Launch application using the following environment variable set, `DIAGNOSTIC_PORTS=*:9000,suspend,listen` + +```sh +$ dotnet-dsrouter server-client -ipcs ~/myport -tcpc 127.0.0.1:9000 --forward-port iOS & ``` -dotnet-trace collect --diagnostic-port ~/myport,connect + +Run application on device connected over usb. + +```sh +$ dotnet-trace collect --diagnostic-port ~/myport,connect ``` NOTE, iOS only support use of loopback interface when running DiagnosticServer in listening mode on device and using `dotnet-dsrouter` in server-client (IPC Server, TCP/IP client) with port forwarding. ### Application running single file based EventPipe session -If application supports controlled runtime shutdown, `mono_jit_cleanup` gets called before terminating process, it is possible to run a single file based EventPipe session using environment variables as described in https://learn.microsoft.com/dotnet/core/diagnostics/eventpipe#trace-using-environment-variables. In .net6 an additional variable has been added, `DOTNET_EventPipeOutputStreaming`, making sure data is periodically flushed into the output file. +If application supports controlled runtime shutdown, `mono_jit_cleanup` gets called before terminating process, it is possible to run a single file based EventPipe session using environment variables as described in https://learn.microsoft.com/dotnet/core/diagnostics/eventpipe#trace-using-environment-variables. In .NET 6 an additional variable has been added, `DOTNET_EventPipeOutputStreaming`, making sure data is periodically flushed into the output file. If application doesn't support controlled runtime shutdown, this mode won't work, since it requires rundown events, only emitted when closing session and flushing memory manager. If application doesn't call `mono_jit_cleanup` before terminating, generated nettrace file will lack rundown events needed to produce callstacks including symbols. Running using single file based EventPipe session will produce a file in working directory. Use platform specific tooling to extract file once application has terminated. Since file based EventPipe session doesn't use diagnostic server, there is no need to use `DOTNET_DiagnosticPorts` or running `dotnet-dsrouter`. -### Analyze JIT/Loader events during startup. +### Analyze JIT/Loader events during startup Increasing the default log level in `dotnet-trace` for `Microsoft-Windows-DotNETRuntime` provider will include additional events in nettrace file giving more details around JIT and loader activities, like all loaded assemblies, loaded types, loaded/JIT:ed methods as well as timing and size metrics, all valuable information when analyzing things like startup performance, size of loaded/JIT:ed methods, time it takes to JIT all, subset or individual methods etc. To instruct `dotnet-trace` to only collect `Microsoft-Windows-DotNETRuntime` events during startup, use one of that startup tracing scenarios as described above, but add the following parameters to `dotnet-trace`, -``` ---clreventlevel verbose --providers Microsoft-Windows-DotNETRuntime -``` +`--clreventlevel verbose --providers Microsoft-Windows-DotNETRuntime` `Prefview` have built in analyzers for JIT/Loader stats, so either load resulting nettrace file in `Perfview` or analyze file using custom `TraceEvent` parsers. -### Trace MonoVM Profiler events during startup. +### Trace MonoVM profiler events during startup + +Starting with .NET 8 the experimental profiler provider, `Microsoft-DotNETRuntimeMonoProfiler`, has been disabled by default. In order to enable it, the following needs to be added to MonoVM specific environment variable: + +`MONO_DIAGNOSTICS=--diagnostic-mono-profiler=enable` -MonoVM comes with a EventPipe provider mapping most of low-level Mono profiler events into native EventPipe events thought `Microsoft-DotNETRuntimeMonoProfiler`. Mainly this provider exists to simplify transition from old MonoVM log profiler over to nettrace, but it also adds a couple of features available in MonoVM profiler as well as ability to take heap shots over EventPipe when running on MonoVM (current dotnet-gcdump is tied to events emitted by CoreCLR GC and currently not supported on MonoVM). +MonoVM comes with a EventPipe provider mapping most of low-level Mono profiler events into native EventPipe events thought `Microsoft-DotNETRuntimeMonoProfiler`. Mainly this provider exists to simplify transition from old MonoVM log profiler over to nettrace, but it also adds a couple of features available in MonoVM profiler. Mono profiler includes the concept of method tracing and have support for method enter/leave events (method execution timing). This can be used when running in JIT/Interpreter mode (for AOT it needs to be passed to MonoVM AOT compiler). Enable enter/leave can produce a large set of events so there might be a risk hitting buffer manager size limits, temporary dropping events. This can be mitigated by either increasing buffer manager memory limit or reduce the number of instrumented methods. Since enter/leave needs instrumentation it must be enabled when a method is JIT:ed. Method tracing can be controlled using a keyword in the MonoProfiler provider, but if an EventPipe session is not running when a method gets JIT:ed, it won't be instrumented and will not fire any enter/leave events. 0x20000000 will start to capture enter/leave events and 0x40000000000 can be used to enable instrumentation including all methods JIT:ed during lifetime of EventPipe session. To make sure all needed methods gets instrumented, runtime should be configured using startup profiling (as described above) and `dotnet-trace` should be run using the following provider configuration, -``` ---providers Microsoft-DotNETRuntimeMonoProfiler:0x40020000000;4 -``` +`--providers Microsoft-DotNETRuntimeMonoProfiler:0x40020000000;4` To fully control the instrumentation (not depend on running EventPipe session), there is a MonoVM specific environment variable that can be set: -``` -MONO_DIAGNOSTICS=--diagnostic-mono-profiler-callspec= -``` +`MONO_DIAGNOSTICS=--diagnostic-mono-profiler-callspec=` That will enable enter/leave profiling for all JIT:ed methods matching callspec. Callspec uses MonoVM callspec format: @@ -432,35 +591,33 @@ It is possible to combine include and exclude expressions using `,` as separator Trace all methods, except methods belonging to `System.Int32` type. -``` -MONO_DIAGNOSTICS=--diagnostic-mono-profiler-callspec=all,-T:System.Int32 -``` +`MONO_DIAGNOSTICS=--diagnostic-mono-profiler-callspec=all,-T:System.Int32` Trace all methods in `Program` type. -``` -MONO_DIAGNOSTICS=--diagnostic-mono-profiler-callspec=T:Program -``` +`MONO_DIAGNOSTICS=--diagnostic-mono-profiler-callspec=T:Program` If no EventPipe session is running using MonoProfiler provider with method tracing keyword, no events will be emitted, even if running methods have been instrumented using `MONO_DIAGNOSTICS`. When instrumenting methods using `MONO_DIAGNOSTICS`, it is possible to run `dotnet-trace` using the following provider configuration, -``` ---providers Microsoft-DotNETRuntimeMonoProfiler:0x20000000:4 -``` +`--providers Microsoft-DotNETRuntimeMonoProfiler:0x20000000:4` Since all methods matching callspec will be instrumented it is possible to capture enter/leave events only when needed at any time during application lifetime. A way to effectively use this precise profiling is to first run with SampleProfiler provider, identifying hot paths worth additional investigation and then enable tracing using a matching callspec and trace execution of methods using MonoProfiler provider tracing keyword. It is of course possible to do enter/leave profiling during startup as well (using callspec or provider enabled instrumentation), just keep in mind that it can produce many events, especially in case when no callspec is in use. -### Collect GC dumps on MonoVM. +NOTE, EventPipe technology comes with an overhead emitting events that will have impact on enter/leave measurements. If more precise instrumentation is needed it is recommended to implement a Mono profiler provider handling the enter/leave callbacks directly. + +### Collect GC dumps on MonoVM .NET 8 + +Starting with .NET 8 MonoVM support GC dumps functionality using tools like `dotnet-gcdump`. No need to use `Microsoft-DotNETRuntimeMonoProfiler` or custom tooling to analyze GC dumps. For more information capturing GC dumps on MonoVM, see https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-gcdump. + +### Collect GC dumps on MonoVM pre .NET 8 MonoVM EventPipe provider `Microsoft-DotNETRuntimeMonoProfiler`, includes several GC related events that can be used to track allocations, roots and other GC events as well as generating GC heap dumps. Heap dumps can be requested on demand using `dotnet-trace` using the following provider configuration, -``` ---providers Microsoft-DotNETRuntimeMonoProfiler:0x8900001:4 -``` +`--providers Microsoft-DotNETRuntimeMonoProfiler:0x8900001:4` This will trigger a heap dump including object references, object type info and GC events that can be used to detect when dump starts and completes (to know when to end EventPipe session). If `dotnet-trace` is used, there is no clear indication when full dump has been completed but looking at the stream size counter will indicate when no more data gets written into stream and session can be closed. @@ -470,22 +627,16 @@ It is also possible to get details about roots registration/deregistration, hand To track GC allocations with callstack, it needs to be enabled when starting up application using an environment variable: -``` -MONO_DIAGNOSTICS=--diagnostic-mono-profiler=alloc -``` +`MONO_DIAGNOSTICS=--diagnostic-mono-profiler=alloc` NOTE, this affects runtime performance since it will change the underlying allocator. It however won't emit any events until an EventPipe session has been created with keywords enabling allocation tracking, -``` ---providers Microsoft-DotNETRuntimeMonoProfiler:0x200000:4 -``` +`--providers Microsoft-DotNETRuntimeMonoProfiler:0x200000:4` It is also possible to setup one EventPipe session running over longer periods of time, getting all requested GC events, including multiple heap dumps. A different EventPipe session can be used to trigger heap dumps on demand, but that session won't get any additional events, just trigger a heap dump. -``` ---providers Microsoft-DotNETRuntimeMonoProfiler:0x800000:4 -``` +`--providers Microsoft-DotNETRuntimeMonoProfiler:0x800000:4` Combining different sessions including different GC information opens up ability to track all GC allocations during a specific time period (to reduce size of captured data), while taking heap dumps into separate sessions, make it possible to do full analysis of GC memory increase/decrease tied allocation callstacks for individual object instances. @@ -507,7 +658,7 @@ Using the diagnostic client library gives full flexibility to use data in nettra TraceEvent library, https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent/ can be used to implement custom nettrace parsers. -https://github.com/lateralusX/diagnostics-nettrace-samples includes a couple of custom tools (startup tracing, instrumented method execution, GC heap dump analysis) analyzing nettrace files using TraceEvent library. +https://github.com/lateralusX/diagnostics-nettrace-samples includes a couple of custom tools (startup tracing, instrumented method execution, pre .NET 8 MonoVM GC heap dump analysis) analyzing nettrace files using TraceEvent library. ## Developing EventPipe/DiagnosticServer on MonoVM @@ -515,4 +666,4 @@ https://github.com/lateralusX/diagnostics-nettrace-samples includes a couple of ** TODO **: How to add a new NativeRuntimeEvent. -** TODO **: How to add a new component API. +** TODO **: How to add a new component API. \ No newline at end of file From 124986ba293622e38f5c128c055ab1894ef6186c Mon Sep 17 00:00:00 2001 From: Adeel Mujahid <3840695+am11@users.noreply.github.com> Date: Thu, 12 Dec 2024 13:25:30 +0200 Subject: [PATCH 46/70] Update dependencies from dotnet/roslyn (#110105) * Update dependencies from https://github.com/dotnet/roslyn build 20241122.2 Microsoft.SourceBuild.Intermediate.roslyn , Microsoft.CodeAnalysis , Microsoft.CodeAnalysis.CSharp , Microsoft.Net.Compilers.Toolset From Version 4.13.0-2.24570.4 -> To Version 4.13.0-2.24572.2 * Update dependencies from https://github.com/dotnet/roslyn build 20241122.15 Microsoft.SourceBuild.Intermediate.roslyn , Microsoft.CodeAnalysis , Microsoft.CodeAnalysis.CSharp , Microsoft.Net.Compilers.Toolset From Version 4.13.0-2.24570.4 -> To Version 4.13.0-2.24572.15 * Update dependencies from https://github.com/dotnet/roslyn build 20241124.5 Microsoft.SourceBuild.Intermediate.roslyn , Microsoft.CodeAnalysis , Microsoft.CodeAnalysis.CSharp , Microsoft.Net.Compilers.Toolset From Version 4.13.0-2.24570.4 -> To Version 4.13.0-2.24574.5 * Update dependencies from https://github.com/dotnet/roslyn build 20241125.18 Microsoft.SourceBuild.Intermediate.roslyn , Microsoft.CodeAnalysis , Microsoft.CodeAnalysis.CSharp , Microsoft.Net.Compilers.Toolset From Version 4.13.0-2.24570.4 -> To Version 4.13.0-3.24575.18 * Update dependencies from https://github.com/dotnet/roslyn build 20241127.2 Microsoft.SourceBuild.Intermediate.roslyn , Microsoft.CodeAnalysis , Microsoft.CodeAnalysis.CSharp , Microsoft.Net.Compilers.Toolset From Version 4.13.0-2.24570.4 -> To Version 4.13.0-3.24577.2 * Update dependencies from https://github.com/dotnet/roslyn build 20241128.2 Microsoft.SourceBuild.Intermediate.roslyn , Microsoft.CodeAnalysis , Microsoft.CodeAnalysis.CSharp , Microsoft.Net.Compilers.Toolset From Version 4.13.0-2.24570.4 -> To Version 4.13.0-3.24578.2 * Update dependencies from https://github.com/dotnet/roslyn build 20241128.3 Microsoft.SourceBuild.Intermediate.roslyn , Microsoft.CodeAnalysis , Microsoft.CodeAnalysis.CSharp , Microsoft.Net.Compilers.Toolset From Version 4.13.0-2.24570.4 -> To Version 4.13.0-3.24578.3 * Update dependencies from https://github.com/dotnet/roslyn build 20241130.1 Microsoft.SourceBuild.Intermediate.roslyn , Microsoft.CodeAnalysis , Microsoft.CodeAnalysis.CSharp , Microsoft.Net.Compilers.Toolset From Version 4.13.0-2.24570.4 -> To Version 4.13.0-3.24580.1 * Update dependencies from https://github.com/dotnet/roslyn build 20241130.2 Microsoft.SourceBuild.Intermediate.roslyn , Microsoft.CodeAnalysis , Microsoft.CodeAnalysis.CSharp , Microsoft.Net.Compilers.Toolset From Version 4.13.0-2.24570.4 -> To Version 4.13.0-3.24580.2 * Update dependencies from https://github.com/dotnet/roslyn build 20241201.2 Microsoft.SourceBuild.Intermediate.roslyn , Microsoft.CodeAnalysis , Microsoft.CodeAnalysis.CSharp , Microsoft.Net.Compilers.Toolset From Version 4.13.0-2.24570.4 -> To Version 4.13.0-3.24601.2 * Update dependencies from https://github.com/dotnet/roslyn build 20241202.26 Microsoft.SourceBuild.Intermediate.roslyn , Microsoft.CodeAnalysis , Microsoft.CodeAnalysis.CSharp , Microsoft.Net.Compilers.Toolset From Version 4.13.0-2.24570.4 -> To Version 4.13.0-3.24602.26 * Use explicit AsSpan * Fix LibraryImportGenerator.UnitTests * Fix regex tests * Adjust trimming test for new codegen With this roslyn update, the generated iterator state machine for this test includes a Dispose method which assigns null to the array, inhibiting dataflow analysis. --------- Co-authored-by: dotnet-maestro[bot] Co-authored-by: Sven Boemer --- eng/Version.Details.xml | 16 ++++++++-------- eng/Versions.props | 6 +++--- .../ReachabilityInstrumentationFilter.cs | 2 +- .../System/IO/StreamConformanceTests.cs | 4 ++-- .../Formats/Nrbf/ArraySinglePrimitiveRecord.cs | 8 ++++---- src/libraries/System.Linq/tests/RangeTests.cs | 2 +- src/libraries/System.Linq/tests/RepeatTests.cs | 2 +- .../Base64Url/Base64UrlUnicodeAPIsUnitTests.cs | 2 +- .../System.Memory/tests/Span/SearchValues.cs | 2 +- .../tests/Span/StringSearchValues.cs | 2 +- .../SocketAsyncEventArgsTest.cs | 2 +- ..._StatefulLinearCollectionShapeValidation.cs | 3 +-- ...eFixerTests_StatefulValueShapeValidation.cs | 1 - ...StatelessLinearCollectionShapeValidation.cs | 1 - ...FixerTests_StatelessValueShapeValidation.cs | 1 - .../InteropServices/CollectionsMarshalTests.cs | 10 +++++----- .../src/System/Numerics/BigInteger.cs | 2 +- .../FileStream/ReadAsync.cs | 2 +- .../System/Convert.FromHexString.cs | 2 +- .../System/Random.cs | 18 +++++++++--------- .../Ascii/CaseConversionTests.cs | 16 ++++++++-------- .../tests/CoseHeaderValueTests.cs | 6 +++--- .../tests/SpanProtectedDataTests.cs | 2 +- .../tests/HashAlgorithmTestDriver.cs | 8 ++++---- .../tests/HmacTests.cs | 8 ++++---- .../tests/KmacTestDriver.cs | 2 +- .../AuthorityKeyIdentifierTests.cs | 2 +- .../Json/Document/JsonDocument.MetadataDb.cs | 2 +- .../CollectionTests/CollectionTests.Memory.cs | 8 ++++---- .../UpgradeToGeneratedRegexAnalyzerTests.cs | 8 ++------ ...mpilerGeneratedCodeAccessedViaReflection.cs | 1 + 31 files changed, 72 insertions(+), 79 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 70c0a792543b35..83e1ab38472d04 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -376,17 +376,17 @@ https://github.com/dotnet/runtime-assets 4daef9f8f673c1d0bbec273f2bcda1ab9074ef75 - + https://github.com/dotnet/roslyn - 94fff7ad4f11977f903f435b0d97dcf0f2183710 + 089e61c59dcb3d46fe766abc85176ef8e42e4d58 - + https://github.com/dotnet/roslyn - 94fff7ad4f11977f903f435b0d97dcf0f2183710 + 089e61c59dcb3d46fe766abc85176ef8e42e4d58 - + https://github.com/dotnet/roslyn - 94fff7ad4f11977f903f435b0d97dcf0f2183710 + 089e61c59dcb3d46fe766abc85176ef8e42e4d58 https://github.com/dotnet/roslyn-analyzers @@ -397,9 +397,9 @@ 5435ba7b1037f21237adc1b3845f97e9fdbc075d - + https://github.com/dotnet/roslyn - 94fff7ad4f11977f903f435b0d97dcf0f2183710 + 089e61c59dcb3d46fe766abc85176ef8e42e4d58 diff --git a/eng/Versions.props b/eng/Versions.props index b762a76e6910cd..1f89aa43476191 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -43,9 +43,9 @@ Any tools that contribute to the design-time experience should use the MicrosoftCodeAnalysisVersion_LatestVS property above to ensure they do not break the local dev experience. --> - 4.13.0-2.24570.4 - 4.13.0-2.24570.4 - 4.13.0-2.24570.4 + 4.13.0-3.24602.26 + 4.13.0-3.24602.26 + 4.13.0-3.24602.26 - + https://github.com/dotnet/roslyn - 089e61c59dcb3d46fe766abc85176ef8e42e4d58 + 86d60f7a00b0274a806a40afde8801a89d27e6bc diff --git a/eng/Versions.props b/eng/Versions.props index 1f89aa43476191..788fd0df01aa64 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -43,9 +43,9 @@ Any tools that contribute to the design-time experience should use the MicrosoftCodeAnalysisVersion_LatestVS property above to ensure they do not break the local dev experience. --> - 4.13.0-3.24602.26 - 4.13.0-3.24602.26 - 4.13.0-3.24602.26 + 4.13.0-3.24611.10 + 4.13.0-3.24611.10 + 4.13.0-3.24611.10 - - - - - - - - - - - diff --git a/src/native/libs/System.Globalization.Native/CMakeLists.txt b/src/native/libs/System.Globalization.Native/CMakeLists.txt index 07080ea9056bb5..45dab320037cda 100644 --- a/src/native/libs/System.Globalization.Native/CMakeLists.txt +++ b/src/native/libs/System.Globalization.Native/CMakeLists.txt @@ -212,16 +212,7 @@ if(CLR_CMAKE_TARGET_WIN32) STATIC ${NATIVEGLOBALIZATION_SOURCES} ) - set_target_properties(System.Globalization.Native.Aot PROPERTIES CLR_CONTROL_FLOW_GUARD OFF) set_target_properties(System.Globalization.Native.Aot PROPERTIES INTERPROCEDURAL_OPTIMIZATION OFF) - - add_library(System.Globalization.Native.Aot.GuardCF - STATIC - ${NATIVEGLOBALIZATION_SOURCES} - ) - set_target_properties(System.Globalization.Native.Aot.GuardCF PROPERTIES INTERPROCEDURAL_OPTIMIZATION OFF) - install_static_library(System.Globalization.Native.Aot aotsdk nativeaot) - install_static_library(System.Globalization.Native.Aot.GuardCF aotsdk nativeaot) endif() endif() diff --git a/src/native/libs/System.IO.Compression.Native/CMakeLists.txt b/src/native/libs/System.IO.Compression.Native/CMakeLists.txt index 81b9f69b8d88b8..0734e8a3112b6d 100644 --- a/src/native/libs/System.IO.Compression.Native/CMakeLists.txt +++ b/src/native/libs/System.IO.Compression.Native/CMakeLists.txt @@ -163,23 +163,7 @@ else () target_include_directories(System.IO.Compression.Native.Aot PUBLIC ${BROTLI_INCLUDE_DIRS}) target_link_libraries(System.IO.Compression.Native.Aot PUBLIC ${BROTLI_LIBRARIES}) - - set_target_properties(System.IO.Compression.Native.Aot PROPERTIES CLR_CONTROL_FLOW_GUARD OFF) set_target_properties(System.IO.Compression.Native.Aot PROPERTIES INTERPROCEDURAL_OPTIMIZATION OFF) - - add_library(System.IO.Compression.Native.Aot.GuardCF - STATIC - ${NATIVECOMPRESSION_SOURCES} - ) - - if (NOT CLR_CMAKE_USE_SYSTEM_ZLIB) - target_link_libraries(System.IO.Compression.Native.Aot.GuardCF PRIVATE zlibstatic) - endif() - - target_include_directories(System.IO.Compression.Native.Aot.GuardCF PUBLIC ${BROTLI_INCLUDE_DIRS}) - target_link_libraries(System.IO.Compression.Native.Aot.GuardCF PUBLIC ${BROTLI_LIBRARIES}) - - set_target_properties(System.IO.Compression.Native.Aot.GuardCF PROPERTIES INTERPROCEDURAL_OPTIMIZATION OFF) endif() if (GEN_SHARED_LIB) @@ -196,7 +180,6 @@ else () if(STATIC_LIBS_ONLY) install_static_library(System.IO.Compression.Native.Aot aotsdk nativeaot) - install_static_library(System.IO.Compression.Native.Aot.GuardCF aotsdk nativeaot) foreach(BROTLI_LIB ${BROTLI_LIBRARIES}) # Brotli's build scripts can add some system dependencies like libm # to BROTLI_LIBRARIES. Only install the libraries that are actually diff --git a/src/tests/tracing/eventpipe/randomizedallocationsampling/allocationsampling.csproj b/src/tests/tracing/eventpipe/randomizedallocationsampling/allocationsampling.csproj index d89955bb638d4c..a2944dc986a117 100644 --- a/src/tests/tracing/eventpipe/randomizedallocationsampling/allocationsampling.csproj +++ b/src/tests/tracing/eventpipe/randomizedallocationsampling/allocationsampling.csproj @@ -12,11 +12,6 @@ true - - - guard - - diff --git a/src/tests/tracing/eventpipe/simpleruntimeeventvalidation/simpleruntimeeventvalidation.csproj b/src/tests/tracing/eventpipe/simpleruntimeeventvalidation/simpleruntimeeventvalidation.csproj index 558d2949c7abcf..b9f4eebaab3f9d 100644 --- a/src/tests/tracing/eventpipe/simpleruntimeeventvalidation/simpleruntimeeventvalidation.csproj +++ b/src/tests/tracing/eventpipe/simpleruntimeeventvalidation/simpleruntimeeventvalidation.csproj @@ -10,11 +10,6 @@ true - - - guard - - From 32acefa84eeafa3060cf73a6b85bf6fdb0e34d3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 13 Dec 2024 09:39:21 +0100 Subject: [PATCH 58/70] [browser] NativeAOT-LLVM support in browser-bench (#110611) --- src/mono/browser/build/WasmApp.InTree.props | 2 +- src/mono/browser/build/WasmApp.InTree.targets | 3 ++- src/mono/sample/wasm/Directory.Build.targets | 15 +++++++---- .../Wasm.Browser.Bench.Sample.csproj | 25 +++++++++++++++++++ .../wasm/browser-bench/appstart-frame.html | 8 +++--- .../sample/wasm/browser-bench/frame-main.js | 2 +- src/mono/sample/wasm/browser-bench/main.js | 2 +- 7 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/mono/browser/build/WasmApp.InTree.props b/src/mono/browser/build/WasmApp.InTree.props index 951aeca06948e3..35de73ac973082 100644 --- a/src/mono/browser/build/WasmApp.InTree.props +++ b/src/mono/browser/build/WasmApp.InTree.props @@ -5,7 +5,7 @@ $([MSBuild]::NormalizeDirectory($(MonoProjectRoot), 'wasm', 'build')) - + true AnyCPU diff --git a/src/mono/browser/build/WasmApp.InTree.targets b/src/mono/browser/build/WasmApp.InTree.targets index e4d1475c81f2cf..885a3fba37ea8b 100644 --- a/src/mono/browser/build/WasmApp.InTree.targets +++ b/src/mono/browser/build/WasmApp.InTree.targets @@ -6,7 +6,8 @@ - + + diff --git a/src/mono/sample/wasm/Directory.Build.targets b/src/mono/sample/wasm/Directory.Build.targets index 2bc82f4fce0d37..8982f866eefe27 100644 --- a/src/mono/sample/wasm/Directory.Build.targets +++ b/src/mono/sample/wasm/Directory.Build.targets @@ -38,14 +38,19 @@ <_ScriptExt Condition="'$(OS)' == 'Windows_NT'">.cmd <_ScriptExt Condition="'$(OS)' != 'Windows_NT'">.sh <_Dotnet>$(RepoRoot)dotnet$(_ScriptExt) - <_AOTFlag Condition="'$(RunAOTCompilation)' != ''">/p:RunAOTCompilation=$(RunAOTCompilation) - <_HybridGlobalizationFlag Condition="'$(HybridGlobalization)' != ''">/p:HybridGlobalization=$(HybridGlobalization) - <_WasmEnableThreadsFlag Condition="'$(WasmEnableThreads)' != ''">/p:WasmEnableThreads=$(WasmEnableThreads) <_SampleProject Condition="'$(_SampleProject)' == ''">$(MSBuildProjectFile) <_SampleAssembly Condition="'$(_SampleAssembly)' == ''">$(TargetFileName) - $(BuildAdditionalArgs) /p:MonoDiagnosticsMock=$(MonoDiagnosticsMock) - + + + + + + + + + + diff --git a/src/mono/sample/wasm/browser-bench/Wasm.Browser.Bench.Sample.csproj b/src/mono/sample/wasm/browser-bench/Wasm.Browser.Bench.Sample.csproj index 679ca812e0f420..35f13d2b03c099 100644 --- a/src/mono/sample/wasm/browser-bench/Wasm.Browser.Bench.Sample.csproj +++ b/src/mono/sample/wasm/browser-bench/Wasm.Browser.Bench.Sample.csproj @@ -8,8 +8,33 @@ $(EnableAOTAndTrimming) $(EnableAOTAndTrimming) $(EnableAOTAndTrimming) + ./ + + + true + $(MSBuildThisFileDirectory)/bin/$(Configuration)/AppBundle + none + $(RestoreAdditionalProjectSources);https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json + false + false + true + true + <_ExeExt Condition="$([MSBuild]::IsOSPlatform('WINDOWS'))">.exe + + + + + + + + + + + + + diff --git a/src/mono/sample/wasm/browser-bench/appstart-frame.html b/src/mono/sample/wasm/browser-bench/appstart-frame.html index 481ffa4e27301f..e7d6d03a08f6b7 100644 --- a/src/mono/sample/wasm/browser-bench/appstart-frame.html +++ b/src/mono/sample/wasm/browser-bench/appstart-frame.html @@ -8,10 +8,10 @@ - - - - + + + + diff --git a/src/mono/sample/wasm/browser-bench/frame-main.js b/src/mono/sample/wasm/browser-bench/frame-main.js index 23f76c03dda5ac..14b56fbcb02ece 100644 --- a/src/mono/sample/wasm/browser-bench/frame-main.js +++ b/src/mono/sample/wasm/browser-bench/frame-main.js @@ -3,7 +3,7 @@ "use strict"; -import { dotnet, exit } from './_framework/dotnet.js' +import { dotnet, exit } from './dotnet.js' class FrameApp { async init({ getAssemblyExports }) { diff --git a/src/mono/sample/wasm/browser-bench/main.js b/src/mono/sample/wasm/browser-bench/main.js index 71e4a677ddcc7e..595dc55e0471e7 100644 --- a/src/mono/sample/wasm/browser-bench/main.js +++ b/src/mono/sample/wasm/browser-bench/main.js @@ -3,7 +3,7 @@ "use strict"; -import { dotnet, exit } from './_framework/dotnet.js' +import { dotnet, exit } from './dotnet.js' let runBenchmark; let setTasks; From cb8d1412e18af2f4f48b27d97944eed7570add3b Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 13 Dec 2024 11:00:24 +0100 Subject: [PATCH 59/70] JIT: Remove always-true `fgCanRelocateEHRegions` (#110612) --- src/coreclr/jit/compiler.cpp | 2 - src/coreclr/jit/compiler.h | 1 - src/coreclr/jit/jiteh.cpp | 111 +++++++++++++++++------------------ 3 files changed, 54 insertions(+), 60 deletions(-) diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 348ba243d13f9c..a5899c9e29f47e 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -4059,8 +4059,6 @@ void Compiler::compSetOptimizationLevel() JitMetadata::report(this, JitMetadata::TieringName, tieringName, strlen(tieringName)); #endif } - - fgCanRelocateEHRegions = true; } #if defined(TARGET_ARMARCH) || defined(TARGET_RISCV64) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index b267c79bb1c65c..45261bc9571d1c 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -5332,7 +5332,6 @@ class Compiler // - Rationalization links all nodes into linear form which is kept until // the end of compilation. The first and last nodes are stored in the block. NodeThreading fgNodeThreading = NodeThreading::None; - bool fgCanRelocateEHRegions; // true if we are allowed to relocate the EH regions weight_t fgCalledCount = BB_ZERO_WEIGHT; // count of the number of times this method was called // This is derived from the profile data // or is BB_UNITY_WEIGHT when we don't have profile data diff --git a/src/coreclr/jit/jiteh.cpp b/src/coreclr/jit/jiteh.cpp index 8327f14f633239..703c615fcd6339 100644 --- a/src/coreclr/jit/jiteh.cpp +++ b/src/coreclr/jit/jiteh.cpp @@ -4397,75 +4397,37 @@ bool Compiler::fgRelocateEHRegions() printf("*************** In fgRelocateEHRegions()\n"); #endif - if (fgCanRelocateEHRegions) - { - unsigned XTnum; - EHblkDsc* HBtab; + unsigned XTnum; + EHblkDsc* HBtab; - for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++) + for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++) + { + // Nested EH regions cannot be moved. + // Also we don't want to relocate an EH region that has a filter + if ((HBtab->ebdHandlerNestingLevel == 0) && !HBtab->HasFilter()) { - // Nested EH regions cannot be moved. - // Also we don't want to relocate an EH region that has a filter - if ((HBtab->ebdHandlerNestingLevel == 0) && !HBtab->HasFilter()) - { - bool movedTry = false; + bool movedTry = false; #if DEBUG - bool movedHnd = false; + bool movedHnd = false; #endif // DEBUG - // Only try to move the outermost try region - if (HBtab->ebdEnclosingTryIndex == EHblkDsc::NO_ENCLOSING_INDEX) - { - // Move the entire try region if it can be moved - if (HBtab->ebdTryBeg->isRunRarely()) - { - BasicBlock* bTryLastBB = fgRelocateEHRange(XTnum, FG_RELOCATE_TRY); - if (bTryLastBB != NULL) - { - result = true; - movedTry = true; - } - } -#if DEBUG - if (verbose && movedTry) - { - printf("\nAfter relocating an EH try region"); - fgDispBasicBlocks(); - fgDispHandlerTab(); - - // Make sure that the predecessor lists are accurate - if (expensiveDebugCheckLevel >= 2) - { - fgDebugCheckBBlist(); - } - } -#endif // DEBUG - } - - // Currently it is not good to move the rarely run handler regions to the end of the method - // because fgDetermineFirstColdBlock() must put the start of any handler region in the hot - // section. - -#if 0 - // Now try to move the entire handler region if it can be moved. - // Don't try to move a finally handler unless we already moved the try region. - if (HBtab->ebdHndBeg->isRunRarely() && - !HBtab->ebdHndBeg->hasTryIndex() && - (movedTry || !HBtab->HasFinallyHandler())) + // Only try to move the outermost try region + if (HBtab->ebdEnclosingTryIndex == EHblkDsc::NO_ENCLOSING_INDEX) + { + // Move the entire try region if it can be moved + if (HBtab->ebdTryBeg->isRunRarely()) { - BasicBlock* bHndLastBB = fgRelocateEHRange(XTnum, FG_RELOCATE_HANDLER); - if (bHndLastBB != NULL) + BasicBlock* bTryLastBB = fgRelocateEHRange(XTnum, FG_RELOCATE_TRY); + if (bTryLastBB != NULL) { result = true; - movedHnd = true; + movedTry = true; } } -#endif // 0 - #if DEBUG - if (verbose && movedHnd) + if (verbose && movedTry) { - printf("\nAfter relocating an EH handler region"); + printf("\nAfter relocating an EH try region"); fgDispBasicBlocks(); fgDispHandlerTab(); @@ -4477,6 +4439,41 @@ bool Compiler::fgRelocateEHRegions() } #endif // DEBUG } + + // Currently it is not good to move the rarely run handler regions to the end of the method + // because fgDetermineFirstColdBlock() must put the start of any handler region in the hot + // section. + +#if 0 + // Now try to move the entire handler region if it can be moved. + // Don't try to move a finally handler unless we already moved the try region. + if (HBtab->ebdHndBeg->isRunRarely() && + !HBtab->ebdHndBeg->hasTryIndex() && + (movedTry || !HBtab->HasFinallyHandler())) + { + BasicBlock* bHndLastBB = fgRelocateEHRange(XTnum, FG_RELOCATE_HANDLER); + if (bHndLastBB != NULL) + { + result = true; + movedHnd = true; + } + } +#endif // 0 + +#if DEBUG + if (verbose && movedHnd) + { + printf("\nAfter relocating an EH handler region"); + fgDispBasicBlocks(); + fgDispHandlerTab(); + + // Make sure that the predecessor lists are accurate + if (expensiveDebugCheckLevel >= 2) + { + fgDebugCheckBBlist(); + } + } +#endif // DEBUG } } From a4ca48f3fde676db2e872c772685ce7dad2297b2 Mon Sep 17 00:00:00 2001 From: Radek Doulik Date: Fri, 13 Dec 2024 12:31:34 +0100 Subject: [PATCH 60/70] [wasm] Add bench output log, to the file and to the console (#110669) This improves diagnostic in our measurement infra --- src/mono/sample/wasm/browser-bench/main.js | 10 +++++--- src/mono/sample/wasm/simple-server/Program.cs | 25 +++++++++++++++++-- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/mono/sample/wasm/browser-bench/main.js b/src/mono/sample/wasm/browser-bench/main.js index 595dc55e0471e7..393349c9a0201b 100644 --- a/src/mono/sample/wasm/browser-bench/main.js +++ b/src/mono/sample/wasm/browser-bench/main.js @@ -85,7 +85,7 @@ class MainApp { setExclusions(exclusions.join(',')); } - const r = await fetch("/bootstrap.flag", { + const r = await fetch("/rewrite=bootstrap.flag", { method: 'POST', body: "ok" }); @@ -96,15 +96,19 @@ class MainApp { if (resultString.length == 0) break; document.getElementById("out").innerHTML += resultString; console.log(resultString); + await fetch("/log=bench-log.txt", { + method: 'POST', + body: resultString + }); } document.getElementById("out").innerHTML += "Finished"; - const r1 = await fetch("/results.json", { + const r1 = await fetch("/rewrite=results.json", { method: 'POST', body: getFullJsonResults() }); console.log("post request complete, response: ", r1); - const r2 = await fetch("/results.html", { + const r2 = await fetch("/rewrite=results.html", { method: 'POST', body: document.getElementById("out").innerHTML }); diff --git a/src/mono/sample/wasm/simple-server/Program.cs b/src/mono/sample/wasm/simple-server/Program.cs index f750a18f908491..e978ef4922efa3 100644 --- a/src/mono/sample/wasm/simple-server/Program.cs +++ b/src/mono/sample/wasm/simple-server/Program.cs @@ -191,11 +191,32 @@ private async void ReceivePostAsync(HttpListenerContext context) if (contentType != null && contentType.StartsWith("text/plain") && path.StartsWith("/")) { path = path.Substring(1); + var split = path.Split('='); + string cmd; + if (split.Length > 1) + { + cmd = split[0]; + path = split[1]; + } else + cmd = "rewrite"; + if (Verbose) - Console.WriteLine($" writting POST stream to '{path}' file"); + Console.WriteLine($" POST cmd: {cmd} path: '{path}'"); var content = await new StreamReader(context.Request.InputStream).ReadToEndAsync().ConfigureAwait(false); - await File.WriteAllTextAsync(path, content).ConfigureAwait(false); + + switch (cmd) { + case "rewrite": + await File.WriteAllTextAsync(path, content).ConfigureAwait(false); + break; + case "log": + await File.AppendAllTextAsync(path, content + "\n").ConfigureAwait(false); + Console.WriteLine($" log: {content}"); + break; + default: + Console.WriteLine($" unknown command: {cmd}"); + break; + } } else return; From 34cf5bc3e40031cb8f6d7cf7e831beadbaba4f85 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 13 Dec 2024 14:28:01 +0100 Subject: [PATCH 61/70] JIT: Add an "init BB" invariant (#110404) This adds an invariant that there always exists an "init BB" throughout the JIT's phases. The init BB has the following properties: - It is only executed once, so it does not have any predecessors - It is not inside a try region, hence it dominates all other blocks in the function There are no further requirements on the BB. The init BB does not have to be `BBJ_ALWAYS` (unlike the old "scratch BB" concept). This is mainly because it philosophically does not make sense to insert IR at the end of the init BB, since earlier phases can have inserted arbitrary IR in them. --- src/coreclr/jit/codegenlinear.cpp | 18 +-- src/coreclr/jit/compiler.cpp | 31 +--- src/coreclr/jit/compiler.h | 12 +- src/coreclr/jit/fgbasic.cpp | 235 ++++++++++-------------------- src/coreclr/jit/fgdiagnostic.cpp | 16 +- src/coreclr/jit/fgopt.cpp | 60 +++++--- src/coreclr/jit/fgprofile.cpp | 2 +- src/coreclr/jit/flowgraph.cpp | 86 +++++------ src/coreclr/jit/gschecks.cpp | 68 +++++++-- src/coreclr/jit/importercalls.cpp | 2 +- src/coreclr/jit/lower.cpp | 3 - src/coreclr/jit/lsrabuild.cpp | 2 - src/coreclr/jit/morph.cpp | 69 +++++---- src/coreclr/jit/optimizer.cpp | 27 ---- src/coreclr/jit/patchpoint.cpp | 10 +- src/coreclr/jit/phase.cpp | 5 + 16 files changed, 283 insertions(+), 363 deletions(-) diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index dcfbcdb9884630..1d6ac2301c45dd 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -156,10 +156,6 @@ void CodeGen::genCodeForBBlist() genMarkLabelsForCodegen(); - assert(!compiler->fgFirstBBScratch || - compiler->fgFirstBB == compiler->fgFirstBBScratch); // compiler->fgFirstBBScratch - // has to be first. - /* Initialize structures used in the block list iteration */ genInitialize(); @@ -367,9 +363,9 @@ void CodeGen::genCodeForBBlist() siBeginBlock(block); // BBF_INTERNAL blocks don't correspond to any single IL instruction. - if (compiler->opts.compDbgInfo && block->HasFlag(BBF_INTERNAL) && - !compiler->fgBBisScratch(block)) // If the block is the distinguished first scratch block, then no need to - // emit a NO_MAPPING entry, immediately after the prolog. + // Add a NoMapping entry unless this is right after the prolog where it + // is unnecessary. + if (compiler->opts.compDbgInfo && block->HasFlag(BBF_INTERNAL) && !block->IsFirst()) { genIPmappingAdd(IPmappingDscKind::NoMapping, DebugInfo(), true); } @@ -388,17 +384,17 @@ void CodeGen::genCodeForBBlist() #ifdef SWIFT_SUPPORT // Reassemble Swift struct parameters on the local stack frame in the - // scratch BB right after the prolog. There can be arbitrary amounts of + // init BB right after the prolog. There can be arbitrary amounts of // codegen related to doing this, so it cannot be done in the prolog. - if (compiler->fgBBisScratch(block) && compiler->lvaHasAnySwiftStackParamToReassemble()) + if (block->IsFirst() && compiler->lvaHasAnySwiftStackParamToReassemble()) { genHomeSwiftStructParameters(/* handleStack */ true); } #endif - // Emit poisoning into scratch BB that comes right after prolog. + // Emit poisoning into the init BB that comes right after prolog. // We cannot emit this code in the prolog as it might make the prolog too large. - if (compiler->compShouldPoisonFrame() && compiler->fgBBisScratch(block)) + if (compiler->compShouldPoisonFrame() && block->IsFirst()) { genPoisonFrame(newLiveRegSet); } diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index a5899c9e29f47e..e2bae6d02fd67f 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -3743,20 +3743,6 @@ void Compiler::compInitDebuggingInfo() compInitScopeLists(); } - if (opts.compDbgCode && (info.compVarScopesCount > 0)) - { - /* Create a new empty basic block. fgExtendDbgLifetimes() may add - initialization of variables which are in scope right from the - start of the (real) first BB (and therefore artificially marked - as alive) into this block. - */ - - fgEnsureFirstBBisScratch(); - - fgNewStmtAtEnd(fgFirstBB, gtNewNothingNode()); - - JITDUMP("Debuggable code - Add new %s to perform initialization of variables\n", fgFirstBB->dspToString()); - } /*------------------------------------------------------------------------- * * Read the stmt-offsets table and the line-number table @@ -4526,6 +4512,9 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl activePhaseChecks |= PhaseChecks::CHECK_PROFILE; DoPhase(this, PHASE_INCPROFILE, &Compiler::fgIncorporateProfileData); + activePhaseChecks |= PhaseChecks::CHECK_FG_INIT_BLOCK; + DoPhase(this, PHASE_CANONICALIZE_ENTRY, &Compiler::fgCanonicalizeFirstBB); + // If we are doing OSR, update flow to initially reach the appropriate IL offset. // if (opts.IsOSR()) @@ -4694,10 +4683,6 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl if (opts.OptimizationEnabled()) { - // Canonicalize entry to have unique entry BB to put IR in for the upcoming phases - // - DoPhase(this, PHASE_CANONICALIZE_ENTRY, &Compiler::fgCanonicalizeFirstBB); - // Build post-order and remove dead blocks // DoPhase(this, PHASE_DFS_BLOCKS2, &Compiler::fgDfsBlocksAndRemove); @@ -4792,10 +4777,6 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl return fgHeadTailMerge(false); }); - // Canonicalize entry to give a unique dominator tree root - // - DoPhase(this, PHASE_CANONICALIZE_ENTRY, &Compiler::fgCanonicalizeFirstBB); - // Compute DFS tree and remove all unreachable blocks. // DoPhase(this, PHASE_DFS_BLOCKS3, &Compiler::fgDfsBlocksAndRemove); @@ -5027,12 +5008,6 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl assert(opts.optRepeat); - // We may have optimized away the canonical entry BB that SSA - // depends on above, so if we are going for another iteration then - // make sure we still have a canonical entry. - // - DoPhase(this, PHASE_CANONICALIZE_ENTRY, &Compiler::fgCanonicalizeFirstBB); - ResetOptAnnotations(); RecomputeFlowGraphAnnotations(); diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 45261bc9571d1c..f18684f5296308 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -1542,6 +1542,7 @@ enum class PhaseChecks : unsigned int CHECK_LIKELIHOODS = 1 << 5, // profile data likelihood integrity CHECK_PROFILE = 1 << 6, // profile data full integrity CHECK_LINKED_LOCALS = 1 << 7, // check linked list of locals + CHECK_FG_INIT_BLOCK = 1 << 8, // flow graph has an init block }; inline constexpr PhaseChecks operator ~(PhaseChecks a) @@ -5197,8 +5198,6 @@ class Compiler BasicBlock* fgEntryBB = nullptr; // For OSR, the original method's entry point BasicBlock* fgOSREntryBB = nullptr; // For OSR, the logical entry point (~ patchpoint) BasicBlock* fgFirstFuncletBB = nullptr; // First block of outlined funclets (to allow block insertion before the funclets) - BasicBlock* fgFirstBBScratch = nullptr; // Block inserted for initialization stuff. Is nullptr if no such block has been - // created. BasicBlockList* fgReturnBlocks = nullptr; // list of BBJ_RETURN blocks unsigned fgEdgeCount = 0; // # of control flow edges between the BBs unsigned fgBBcount = 0; // # of BBs in the method (in the linked list that starts with fgFirstBB) @@ -5246,10 +5245,6 @@ class Compiler return getAllocator(cmk).allocate(fgBBNumMax + 1); } - bool fgEnsureFirstBBisScratch(); - bool fgFirstBBisScratch(); - bool fgBBisScratch(BasicBlock* block); - void fgExtendEHRegionBefore(BasicBlock* block); void fgExtendEHRegionAfter(BasicBlock* block); @@ -5439,6 +5434,7 @@ class Compiler }; PhaseStatus fgMorphBlocks(); + BasicBlock* fgGetFirstILBlock(); void fgMorphBlock(BasicBlock* block, MorphUnreachableInfo* unreachableInfo = nullptr); void fgMorphStmts(BasicBlock* block); @@ -6158,6 +6154,7 @@ class Compiler bool fgCheckRemoveStmt(BasicBlock* block, Statement* stmt); PhaseStatus fgCanonicalizeFirstBB(); + void fgCreateNewInitBB(); void fgSetEHRegionForNewPreheaderOrExit(BasicBlock* preheader); @@ -6179,6 +6176,8 @@ class Compiler bool fgCanCompactBlock(BasicBlock* block); + bool fgCanCompactInitBlock(); + void fgCompactBlock(BasicBlock* block); BasicBlock* fgConnectFallThrough(BasicBlock* bSrc, BasicBlock* bDst); @@ -6355,6 +6354,7 @@ class Compiler void fgDebugCheckBBNumIncreasing(); void fgDebugCheckBBlist(bool checkBBNum = false, bool checkBBRefs = true); void fgDebugCheckBlockLinks(); + void fgDebugCheckInitBB(); void fgDebugCheckLinks(bool morphTrees = false); void fgDebugCheckStmtsList(BasicBlock* block, bool morphTrees); void fgDebugCheckNodeLinks(BasicBlock* block, Statement* stmt); diff --git a/src/coreclr/jit/fgbasic.cpp b/src/coreclr/jit/fgbasic.cpp index 30a3a83581a3bb..320ad81906c310 100644 --- a/src/coreclr/jit/fgbasic.cpp +++ b/src/coreclr/jit/fgbasic.cpp @@ -8,167 +8,102 @@ // Flowgraph Construction and Maintenance -//------------------------------------------------------------------------ -// fgEnsureFirstBBisScratch: Ensure that fgFirstBB is a scratch BasicBlock +//------------------------------------------------------------------------------ +// fgCanonicalizeFirstBB: Canonicalize the method entry to be dominate all +// blocks in the BB and to be executed exactly once. // // Returns: -// True, if a new basic block was allocated. -// -// Notes: -// This should be called before adding on-entry initialization code to -// the method, to ensure that fgFirstBB is not part of a loop. -// -// Does nothing, if fgFirstBB is already a scratch BB. After calling this, -// fgFirstBB may already contain code. Callers have to be careful -// that they do not mess up the order of things added to this block and -// inadvertently change semantics. -// -// We maintain the invariant that a scratch BB ends with BBJ_ALWAYS, -// so that when adding independent bits of initialization, -// callers can generally append to the fgFirstBB block without worrying -// about what code is there already. +// Suitable phase status. // -// Can be called at any time, and can be called multiple times. -// -bool Compiler::fgEnsureFirstBBisScratch() +PhaseStatus Compiler::fgCanonicalizeFirstBB() { - // Have we already allocated a scratch block? - if (fgFirstBBisScratch()) + if (fgFirstBB->hasTryIndex()) { - return false; + JITDUMP("Canonicalizing entry because it currently is the beginning of a try region\n"); + } + else if (fgFirstBB->bbPreds != nullptr) + { + JITDUMP("Canonicalizing entry because it currently has predecessors\n"); + } + else if (opts.compDbgCode && !fgFirstBB->HasFlag(BBF_INTERNAL)) + { + // For debug ensure the first BB is internal so as to not conflate user + // code with JIT added code. + JITDUMP("Canonicalizing entry because it currently is a user BB and we are generating debug code\n"); + } + else + { + return PhaseStatus::MODIFIED_NOTHING; } - assert(fgFirstBBScratch == nullptr); - - BasicBlock* block; + fgCreateNewInitBB(); + return PhaseStatus::MODIFIED_EVERYTHING; +} - if (fgFirstBB != nullptr) - { - // The first block has an implicit ref count which we must - // remove. Note the ref count could be greater than one, if - // the first block is not scratch and is targeted by a - // branch. - assert(fgFirstBB->bbRefs >= 1); - fgFirstBB->bbRefs--; +//------------------------------------------------------------------------------ +// fgCreateNewInitBB: +// Create a new init BB at the beginning of the function. +// +void Compiler::fgCreateNewInitBB() +{ + // The first block has an implicit ref count which we must remove. Note the + // ref count could be greater than one, if the first block is targeted by a + // branch. + assert(fgFirstBB->bbRefs >= 1); + fgFirstBB->bbRefs--; - block = BasicBlock::New(this); + BasicBlock* block = BasicBlock::New(this); - // If we have profile data determine the weight of the scratch BB + // If we have profile data determine the weight of the initBB BB + // + if (fgFirstBB->hasProfileWeight()) + { + // If current entry has preds, sum up those weights // - if (fgFirstBB->hasProfileWeight()) + weight_t nonEntryWeight = 0; + for (FlowEdge* const edge : fgFirstBB->PredEdges()) { - // If current entry has preds, sum up those weights - // - weight_t nonEntryWeight = 0; - for (FlowEdge* const edge : fgFirstBB->PredEdges()) - { - nonEntryWeight += edge->getLikelyWeight(); - } + nonEntryWeight += edge->getLikelyWeight(); + } - // entry weight is weight not from any pred + // entry weight is weight not from any pred + // + weight_t const entryWeight = fgFirstBB->bbWeight - nonEntryWeight; + if (entryWeight <= 0) + { + // If the result is clearly nonsensical, just inherit // - weight_t const entryWeight = fgFirstBB->bbWeight - nonEntryWeight; - if (entryWeight <= 0) - { - // If the result is clearly nonsensical, just inherit - // - JITDUMP( - "\fgEnsureFirstBBisScratch: Profile data could not be locally repaired. Data %s inconsistent.\n", + JITDUMP("\fgCanonicalizeFirstBB: Profile data could not be locally repaired. Data %s inconsistent.\n", fgPgoConsistent ? "is now" : "was already"); - if (fgPgoConsistent) - { - Metrics.ProfileInconsistentScratchBB++; - fgPgoConsistent = false; - } - - block->inheritWeight(fgFirstBB); - } - else + if (fgPgoConsistent) { - block->setBBProfileWeight(entryWeight); + Metrics.ProfileInconsistentScratchBB++; + fgPgoConsistent = false; } - } - // The new scratch bb will fall through to the old first bb - FlowEdge* const edge = fgAddRefPred(fgFirstBB, block); - block->SetKindAndTargetEdge(BBJ_ALWAYS, edge); - fgInsertBBbefore(fgFirstBB, block); - } - else - { - noway_assert(fgLastBB == nullptr); - block = BasicBlock::New(this, BBJ_ALWAYS); - fgFirstBB = block; - fgLastBB = block; + block->inheritWeight(fgFirstBB); + } + else + { + block->setBBProfileWeight(entryWeight); + } } - noway_assert(fgLastBB != nullptr); + // The new scratch bb will fall through to the old first bb + FlowEdge* const edge = fgAddRefPred(fgFirstBB, block); + block->SetKindAndTargetEdge(BBJ_ALWAYS, edge); + fgInsertBBbefore(fgFirstBB, block); // Set the expected flags - block->SetFlags(BBF_INTERNAL | BBF_IMPORTED); + block->SetFlags(BBF_INTERNAL); // This new first BB has an implicit ref, and no others. // - // But if we call this early, before fgLinkBasicBlocks, - // defer and let it handle adding the implicit ref. - // - block->bbRefs = fgPredsComputed ? 1 : 0; - - fgFirstBBScratch = fgFirstBB; - -#ifdef DEBUG - if (verbose) - { - printf("New scratch " FMT_BB "\n", block->bbNum); - } -#endif - - return true; -} - -//------------------------------------------------------------------------ -// fgFirstBBisScratch: Check if fgFirstBB is a scratch block -// -// Returns: -// true if fgFirstBB is a scratch block. -// -bool Compiler::fgFirstBBisScratch() -{ - if (fgFirstBBScratch != nullptr) - { - assert(fgFirstBBScratch == fgFirstBB); - assert(fgFirstBBScratch->HasFlag(BBF_INTERNAL)); - if (fgPredsComputed) - { - assert(fgFirstBBScratch->countOfInEdges() == 1); - } - - // Normally, the first scratch block is a fall-through block. However, if the block after it was an empty - // BBJ_ALWAYS block, it might get removed, and the code that removes it will make the first scratch block - // a BBJ_ALWAYS block. - assert(fgFirstBBScratch->KindIs(BBJ_ALWAYS)); - - return true; - } - else - { - return false; - } -} + assert(fgPredsComputed); + block->bbRefs = 1; -//------------------------------------------------------------------------ -// fgBBisScratch: Check if a given block is a scratch block. -// -// Arguments: -// block - block in question -// -// Returns: -// true if this block is the first block and is a scratch block. -// -bool Compiler::fgBBisScratch(BasicBlock* block) -{ - return fgFirstBBisScratch() && (block == fgFirstBB); + JITDUMP("New init " FMT_BB "\n", block->bbNum); } /* @@ -4071,8 +4006,9 @@ void Compiler::fgFixEntryFlowForOSR() // Now branch from method start to the OSR entry. // - fgEnsureFirstBBisScratch(); - assert(fgFirstBB->KindIs(BBJ_ALWAYS) && fgFirstBB->JumpsToNext()); + fgCreateNewInitBB(); + assert(fgFirstBB->KindIs(BBJ_ALWAYS)); + fgRedirectTargetEdge(fgFirstBB, fgOSREntryBB); // We don't know the right weight for this block, since @@ -4952,22 +4888,9 @@ void Compiler::fgUnlinkBlock(BasicBlock* block) { assert(block == fgFirstBB); assert(block != fgLastBB); - assert((fgFirstBBScratch == nullptr) || (fgFirstBBScratch == fgFirstBB)); fgFirstBB = block->Next(); fgFirstBB->SetPrevToNull(); - - if (fgFirstBBScratch != nullptr) - { -#ifdef DEBUG - // We had created an initial scratch BB, but now we're deleting it. - if (verbose) - { - printf("Unlinking scratch " FMT_BB "\n", block->bbNum); - } -#endif // DEBUG - fgFirstBBScratch = nullptr; - } } else if (block->IsLast()) { @@ -5126,15 +5049,6 @@ BasicBlock* Compiler::fgRemoveBlock(BasicBlock* block, bool unreachable) BasicBlock* succBlock = block->GetTarget(); - bool skipUnmarkLoop = false; - - if (succBlock->isLoopHead() && bPrev && (succBlock->bbNum <= bPrev->bbNum)) - { - // It looks like `block` is the source of a back edge of a loop, and once we remove `block` the - // loop will still exist because we'll move the edge to `bPrev`. So, don't unscale the loop blocks. - skipUnmarkLoop = true; - } - // Update fgFirstFuncletBB if necessary if (block == fgFirstFuncletBB) { @@ -5925,7 +5839,7 @@ BasicBlock* Compiler::fgNewBBFromTreeAfter( */ void Compiler::fgInsertBBbefore(BasicBlock* insertBeforeBlk, BasicBlock* newBlk) { - if (fgFirstBB == insertBeforeBlk) + if (insertBeforeBlk == fgFirstBB) { newBlk->SetNext(fgFirstBB); @@ -5937,8 +5851,7 @@ void Compiler::fgInsertBBbefore(BasicBlock* insertBeforeBlk, BasicBlock* newBlk) fgInsertBBafter(insertBeforeBlk->Prev(), newBlk); } - /* Update fgFirstFuncletBB if insertBeforeBlk is the first block of the funclet region. */ - if (fgFirstFuncletBB == insertBeforeBlk) + if (insertBeforeBlk == fgFirstFuncletBB) { fgFirstFuncletBB = newBlk; } diff --git a/src/coreclr/jit/fgdiagnostic.cpp b/src/coreclr/jit/fgdiagnostic.cpp index 0abf553f2f0c60..cc65f6269fd315 100644 --- a/src/coreclr/jit/fgdiagnostic.cpp +++ b/src/coreclr/jit/fgdiagnostic.cpp @@ -2905,7 +2905,6 @@ void Compiler::fgDebugCheckBBlist(bool checkBBNum /* = false */, bool checkBBRef } fgDebugCheckBlockLinks(); - fgFirstBBisScratch(); if (fgBBcount > 10000 && expensiveDebugCheckLevel < 1) { @@ -3217,6 +3216,17 @@ void Compiler::fgDebugCheckBBlist(bool checkBBNum /* = false */, bool checkBBRef } } +//------------------------------------------------------------------------ +// fgDebugCheckInitBB: Check that the first BB is a valid init BB. +// +void Compiler::fgDebugCheckInitBB() +{ + assert(fgFirstBB != nullptr); + assert(!fgFirstBB->hasTryIndex()); + assert(fgFirstBB->bbPreds == nullptr); + assert(!opts.compDbgCode || fgFirstBB->HasFlag(BBF_INTERNAL)); +} + //------------------------------------------------------------------------ // fgDebugCheckTypes: Validate node types used in the given tree // @@ -4712,7 +4722,9 @@ void Compiler::fgDebugCheckFlowGraphAnnotations() { if (m_dfsTree == nullptr) { - assert((m_loops == nullptr) && (m_domTree == nullptr) && (m_reachabilitySets == nullptr)); + assert(m_loops == nullptr); + assert((m_domTree == nullptr) && (m_domFrontiers == nullptr)); + assert(m_reachabilitySets == nullptr); return; } diff --git a/src/coreclr/jit/fgopt.cpp b/src/coreclr/jit/fgopt.cpp index 9d80caeb5db3d5..834a53be776a61 100644 --- a/src/coreclr/jit/fgopt.cpp +++ b/src/coreclr/jit/fgopt.cpp @@ -823,9 +823,9 @@ bool Compiler::fgCanCompactBlock(BasicBlock* block) return false; } - // Don't compact the first block if it was specially created as a scratch block. + // Ensure we leave a valid init BB around. // - if (fgBBisScratch(block)) + if ((block == fgFirstBB) && !fgCanCompactInitBlock()) { return false; } @@ -851,6 +851,40 @@ bool Compiler::fgCanCompactBlock(BasicBlock* block) return true; } +//------------------------------------------------------------- +// fgCanCompactInitBlock: Check if the first BB (the init BB) can be compacted +// into its target. +// +// Returns: +// true if compaction is allowed +// +bool Compiler::fgCanCompactInitBlock() +{ + assert(fgFirstBB->KindIs(BBJ_ALWAYS)); + BasicBlock* target = fgFirstBB->GetTarget(); + if (target->hasTryIndex()) + { + // Inside a try region + return false; + } + + assert(target->bbPreds != nullptr); + if (target->bbPreds->getNextPredEdge() != nullptr) + { + // Multiple preds + return false; + } + + if (opts.compDbgCode && !target->HasFlag(BBF_INTERNAL)) + { + // Init BB must be internal for debug code to avoid conflating + // JIT-inserted code with user code. + return false; + } + + return true; +} + //------------------------------------------------------------- // fgCompactBlock: Compact BBJ_ALWAYS block and its target into one. // @@ -1389,7 +1423,7 @@ bool Compiler::fgOptimizeEmptyBlock(BasicBlock* block) if (bPrev == nullptr) { assert(block == fgFirstBB); - if (!block->JumpsToNext()) + if (!block->JumpsToNext() || !fgCanCompactInitBlock()) { break; } @@ -1401,8 +1435,9 @@ bool Compiler::fgOptimizeEmptyBlock(BasicBlock* block) break; } - // Don't remove fgEntryBB - if (block == fgEntryBB) + // Don't remove the init BB if it does not leave a proper init BB + // in place + if ((block == fgFirstBB) && !fgCanCompactInitBlock()) { break; } @@ -2161,11 +2196,6 @@ bool Compiler::fgOptimizeUncondBranchToSimpleCond(BasicBlock* block, BasicBlock* return false; } - if (fgBBisScratch(block)) - { - return false; - } - unsigned lclNum = BAD_VAR_NUM; // First check if the successor tests a local and then branches on the result @@ -2525,12 +2555,6 @@ bool Compiler::fgOptimizeBranch(BasicBlock* bJump) return false; } - // Don't hoist a conditional branch into the scratch block; we'd prefer it stay BBJ_ALWAYS. - if (fgBBisScratch(bJump)) - { - return false; - } - BasicBlock* bDest = bJump->GetTarget(); if (!bDest->KindIs(BBJ_COND)) @@ -6481,9 +6505,9 @@ PhaseStatus Compiler::fgHeadTailMerge(bool early) Statement* const stmt = info.m_stmt; BasicBlock* const predBlock = info.m_block; - // Never pick the scratch block as the victim as that would + // Never pick the init block as the victim as that would // cause us to add a predecessor to it, which is invalid. - if (fgBBisScratch(predBlock)) + if (predBlock == fgFirstBB) { continue; } diff --git a/src/coreclr/jit/fgprofile.cpp b/src/coreclr/jit/fgprofile.cpp index 18d96b3bac19a8..0b8f4378c48270 100644 --- a/src/coreclr/jit/fgprofile.cpp +++ b/src/coreclr/jit/fgprofile.cpp @@ -4474,7 +4474,7 @@ bool Compiler::fgComputeCalledCount(weight_t returnWeight) // If we allocated a scratch block as the first BB then we need // to set its profile-derived weight to be fgCalledCount - if (fgFirstBBisScratch()) + if (fgFirstBB->HasFlag(BBF_INTERNAL)) { fgFirstBB->setBBProfileWeight(fgCalledCount); madeChanges = true; diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index a90bf9c3f00e9d..127b76e63e8486 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -1388,14 +1388,9 @@ void Compiler::fgAddSyncMethodEnterExit() NYI("No support for synchronized methods"); #endif // !FEATURE_EH - // Create a scratch first BB where we can put the new variable initialization. - // Don't put the scratch BB in the protected region. - - fgEnsureFirstBBisScratch(); - // Create a block for the start of the try region, where the monitor enter call // will go. - BasicBlock* const tryBegBB = fgSplitBlockAtEnd(fgFirstBB); + BasicBlock* const tryBegBB = fgSplitBlockAtBeginning(fgFirstBB); BasicBlock* const tryLastBB = fgLastBB; // Create a block for the fault. @@ -1517,7 +1512,7 @@ void Compiler::fgAddSyncMethodEnterExit() GenTree* zero = gtNewZeroConNode(typeMonAcquired); GenTree* initNode = gtNewStoreLclVarNode(lvaMonAcquired, zero); - fgNewStmtAtEnd(fgFirstBB, initNode); + fgNewStmtAtBeg(fgFirstBB, initNode); #ifdef DEBUG if (verbose) @@ -1543,7 +1538,7 @@ void Compiler::fgAddSyncMethodEnterExit() GenTree* thisNode = gtNewLclVarNode(info.compThisArg); GenTree* initNode = gtNewStoreLclVarNode(lvaCopyThis, thisNode); - fgNewStmtAtEnd(tryBegBB, initNode); + fgNewStmtAtBeg(tryBegBB, initNode); } // For OSR, we do not need the enter tree as the monitor is acquired by the original method. @@ -1603,38 +1598,45 @@ GenTree* Compiler::fgCreateMonitorTree(unsigned lvaMonAcquired, unsigned lvaThis } #endif - if (block->KindIs(BBJ_RETURN) && block->lastStmt()->GetRootNode()->OperIs(GT_RETURN)) + if (enter) { - GenTreeUnOp* retNode = block->lastStmt()->GetRootNode()->AsUnOp(); - GenTree* retExpr = retNode->gtOp1; - - if (retExpr != nullptr) + fgNewStmtAtBeg(block, tree); + } + else + { + if (block->KindIs(BBJ_RETURN) && block->lastStmt()->GetRootNode()->OperIs(GT_RETURN)) { - // have to insert this immediately before the GT_RETURN so we transform: - // ret(...) -> - // ret(comma(comma(tmp=...,call mon_exit), tmp)) - // - TempInfo tempInfo = fgMakeTemp(retExpr); - GenTree* lclVar = tempInfo.load; + GenTreeUnOp* retNode = block->lastStmt()->GetRootNode()->AsUnOp(); + GenTree* retExpr = retNode->gtOp1; - // TODO-1stClassStructs: delete this NO_CSE propagation. Requires handling multi-regs in copy prop. - lclVar->gtFlags |= (retExpr->gtFlags & GTF_DONT_CSE); + if (retExpr != nullptr) + { + // have to insert this immediately before the GT_RETURN so we transform: + // ret(...) -> + // ret(comma(comma(tmp=...,call mon_exit), tmp)) + // + TempInfo tempInfo = fgMakeTemp(retExpr); + GenTree* lclVar = tempInfo.load; - retExpr = gtNewOperNode(GT_COMMA, lclVar->TypeGet(), tree, lclVar); - retExpr = gtNewOperNode(GT_COMMA, lclVar->TypeGet(), tempInfo.store, retExpr); - retNode->gtOp1 = retExpr; - retNode->AddAllEffectsFlags(retExpr); + // TODO-1stClassStructs: delete this NO_CSE propagation. Requires handling multi-regs in copy prop. + lclVar->gtFlags |= (retExpr->gtFlags & GTF_DONT_CSE); + + retExpr = gtNewOperNode(GT_COMMA, lclVar->TypeGet(), tree, lclVar); + retExpr = gtNewOperNode(GT_COMMA, lclVar->TypeGet(), tempInfo.store, retExpr); + retNode->gtOp1 = retExpr; + retNode->AddAllEffectsFlags(retExpr); + } + else + { + // Insert this immediately before the GT_RETURN + fgNewStmtNearEnd(block, tree); + } } else { - // Insert this immediately before the GT_RETURN - fgNewStmtNearEnd(block, tree); + fgNewStmtAtEnd(block, tree); } } - else - { - fgNewStmtAtEnd(block, tree); - } return tree; } @@ -1727,8 +1729,6 @@ void Compiler::fgAddReversePInvokeEnterExit() tree = gtNewHelperCallNode(CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER, TYP_VOID, pInvokeFrameVar); } - fgEnsureFirstBBisScratch(); - fgNewStmtAtBeg(fgFirstBB, tree); #ifdef DEBUG @@ -2307,15 +2307,6 @@ PhaseStatus Compiler::fgAddInternal() // type with a runtime lookup madeChanges |= fgCreateFiltersForGenericExceptions(); - // The backend requires a scratch BB into which it can safely insert a P/Invoke method prolog if one is - // required. Similarly, we need a scratch BB for poisoning and when we have Swift parameters to reassemble. - // Create it here. - if (compMethodRequiresPInvokeFrame() || compShouldPoisonFrame() || lvaHasAnySwiftStackParamToReassemble()) - { - madeChanges |= fgEnsureFirstBBisScratch(); - fgFirstBB->SetFlags(BBF_DONT_REMOVE); - } - /* VSW441487 @@ -2351,8 +2342,7 @@ PhaseStatus Compiler::fgAddInternal() // Now assign the original input "this" to the temp. GenTree* store = gtNewStoreLclVarNode(lvaArg0Var, gtNewLclVarNode(info.compThisArg)); - fgEnsureFirstBBisScratch(); - fgNewStmtAtEnd(fgFirstBB, store); + fgNewStmtAtBeg(fgFirstBB, store); JITDUMP("\nCopy \"this\" to lvaArg0Var in first basic block %s\n", fgFirstBB->dspToString()); DISPTREE(store); @@ -2481,8 +2471,7 @@ PhaseStatus Compiler::fgAddInternal() // Stick the conditional call at the start of the method - fgEnsureFirstBBisScratch(); - fgNewStmtAtEnd(fgFirstBB, gtNewQmarkNode(TYP_VOID, guardCheckCond, callback->AsColon())); + fgNewStmtAtBeg(fgFirstBB, gtNewQmarkNode(TYP_VOID, guardCheckCond, callback->AsColon())); madeChanges = true; } @@ -2509,10 +2498,7 @@ PhaseStatus Compiler::fgAddInternal() tree = gtNewHelperCallNode(CORINFO_HELP_MON_ENTER, TYP_VOID, tree); - /* Create a new basic block and stick the call in it */ - - fgEnsureFirstBBisScratch(); - fgNewStmtAtEnd(fgFirstBB, tree); + fgNewStmtAtBeg(fgFirstBB, tree); #ifdef DEBUG if (verbose) diff --git a/src/coreclr/jit/gschecks.cpp b/src/coreclr/jit/gschecks.cpp index ed2d7538a39787..558c30f6b35769 100644 --- a/src/coreclr/jit/gschecks.cpp +++ b/src/coreclr/jit/gschecks.cpp @@ -525,25 +525,68 @@ void Compiler::gsParamsToShadows() call->gtArgs.PushBack(this, NewCallArg::Primitive(dst)); call->gtArgs.PushBack(this, NewCallArg::Primitive(src)); - fgEnsureFirstBBisScratch(); compCurBB = fgFirstBB; // Needed by some morphing if (opts.IsReversePInvoke()) { - JITDUMP( - "Inserting special copy helper call at the end of the first block after Reverse P/Invoke transition\n"); + // If we are in a reverse P/Invoke, then we need to insert + // the call at the end of the first block as we need to do the GC transition + // before we can call the helper. + // + // TODO-Cleanup: These gymnastics indicate that we are + // inserting reverse pinvoke transitions way too early in the + // JIT. -#ifdef DEBUG - // assert that we don't have any uses of the local variable in the first block - // before we insert the shadow copy statement. + struct HasReversePInvokeEnterVisitor : GenTreeVisitor + { + enum + { + DoPreOrder = true, + }; + + HasReversePInvokeEnterVisitor(Compiler* comp) + : GenTreeVisitor(comp) + { + } + + fgWalkResult PreOrderVisit(GenTree** use, GenTree* user) + { + if (((*use)->gtFlags & GTF_CALL) == 0) + { + return fgWalkResult::WALK_SKIP_SUBTREES; + } + + if ((*use)->IsHelperCall(m_compiler, CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER) || + (*use)->IsHelperCall(m_compiler, CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER_TRACK_TRANSITIONS)) + { + return fgWalkResult::WALK_ABORT; + } + + return fgWalkResult::WALK_CONTINUE; + } + }; + + HasReversePInvokeEnterVisitor checker(this); + + Statement* reversePInvokeStmt = nullptr; for (Statement* const stmt : fgFirstBB->Statements()) { + // assert that we don't have any uses of the local variable + // at the point before we insert the shadow copy statement. assert(!gtHasRef(stmt->GetRootNode(), lclNum)); + + if (checker.WalkTree(stmt->GetRootNodePointer(), nullptr) == fgWalkResult::WALK_ABORT) + { + reversePInvokeStmt = stmt; + break; + } } -#endif - // If we are in a reverse P/Invoke, then we need to insert - // the call at the end of the first block as we need to do the GC transition - // before we can call the helper. - (void)fgNewStmtAtEnd(fgFirstBB, fgMorphTree(call)); + + noway_assert(reversePInvokeStmt != nullptr); + + JITDUMP("Inserting special copy helper call after Reverse P/Invoke transition " FMT_STMT "\n", + reversePInvokeStmt->GetID()); + + (void)fgInsertStmtAfter(fgFirstBB, reversePInvokeStmt, gtNewStmt(fgMorphTree(call))); } else { @@ -559,9 +602,8 @@ void Compiler::gsParamsToShadows() GenTree* store = gtNewStoreLclVarNode(shadowVarNum, src); - fgEnsureFirstBBisScratch(); compCurBB = fgFirstBB; // Needed by some morphing - (void)fgNewStmtAtBeg(fgFirstBB, fgMorphTree(store)); + fgNewStmtAtBeg(fgFirstBB, fgMorphTree(store)); } } compCurBB = nullptr; diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 033ea50dc20e26..08336a28d553a9 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -1274,7 +1274,7 @@ var_types Compiler::impImportCall(OPCODE opcode, { // For normal jitting we may branch back to the firstBB; this // should already be imported. - loopHead = fgFirstBB; + loopHead = fgGetFirstILBlock(); } JITDUMP("\nTail recursive call [%06u] in the method. Mark " FMT_BB " to " FMT_BB diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 53e43858ace0a9..63a03c60975853 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -5856,9 +5856,6 @@ void Lowering::InsertPInvokeMethodProlog() JITDUMP("======= Inserting PInvoke method prolog\n"); - // The first BB must be a scratch BB in order for us to be able to safely insert the P/Invoke prolog. - assert(comp->fgFirstBBisScratch()); - LIR::Range& firstBlockRange = LIR::AsRange(comp->fgFirstBB); const CORINFO_EE_INFO* pInfo = comp->eeGetEEInfo(); diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index 5748a143d29c9d..56bba3469eb27b 100644 --- a/src/coreclr/jit/lsrabuild.cpp +++ b/src/coreclr/jit/lsrabuild.cpp @@ -2526,7 +2526,6 @@ void LinearScan::buildIntervals() // handling clobbers REG_SCRATCH, so kill it here. if ((block == compiler->fgFirstBB) && compiler->lvaHasAnySwiftStackParamToReassemble()) { - assert(compiler->fgFirstBBisScratch()); addKillForRegs(genRegMask(REG_SCRATCH), currentLoc + 1); currentLoc += 2; } @@ -2536,7 +2535,6 @@ void LinearScan::buildIntervals() // into the scratch register, so it will be killed here. if (compiler->compShouldPoisonFrame() && (block == compiler->fgFirstBB)) { - assert(compiler->fgFirstBBisScratch()); regMaskTP killed; #if defined(TARGET_XARCH) // Poisoning uses EAX for small vars and rep stosd that kills edi, ecx and eax for large vars. diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index b0ac4a65d81367..eceb31f590a52c 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -51,7 +51,6 @@ PhaseStatus Compiler::fgMorphInit() impTokenLookupContextHandle /* context */) & CORINFO_INITCLASS_USE_HELPER) { - fgEnsureFirstBBisScratch(); fgNewStmtAtBeg(fgFirstBB, fgInitThisClass()); madeChanges = true; } @@ -67,8 +66,7 @@ PhaseStatus Compiler::fgMorphInit() GenTree* op = gtNewLclvNode(i, TYP_REF); op = gtNewHelperCallNode(CORINFO_HELP_CHECK_OBJ, TYP_VOID, op); - fgEnsureFirstBBisScratch(); - fgNewStmtAtEnd(fgFirstBB, op); + fgNewStmtAtBeg(fgFirstBB, op); madeChanges = true; if (verbose) { @@ -6744,24 +6742,19 @@ void Compiler::fgMorphRecursiveFastTailCallIntoLoop(BasicBlock* block, GenTreeCa if (opts.IsOSR()) { // Todo: this may not look like a viable loop header. - // Might need the moral equivalent of a scratch BB. + // Might need the moral equivalent of an init BB. FlowEdge* const newEdge = fgAddRefPred(fgEntryBB, block); block->SetKindAndTargetEdge(BBJ_ALWAYS, newEdge); } else { - // We should have ensured the first BB was scratch - // in morph init... - // assert(doesMethodHaveRecursiveTailcall()); - assert(fgFirstBBisScratch()); - // Loop detection needs to see a pred out of the loop, - // so mark the scratch block BBF_DONT_REMOVE to prevent empty - // block removal on it. - // - fgFirstBB->SetFlags(BBF_DONT_REMOVE); - FlowEdge* const newEdge = fgAddRefPred(fgFirstBB->Next(), block); + // TODO-Cleanup: We should really be expanding tailcalls into loops + // much earlier than this, at a place where we do not need to have + // hacky workarounds to figure out what the actual IL entry block is. + BasicBlock* firstILBB = fgGetFirstILBlock(); + FlowEdge* const newEdge = fgAddRefPred(firstILBB, block); block->SetKindAndTargetEdge(BBJ_ALWAYS, newEdge); } @@ -13517,14 +13510,6 @@ PhaseStatus Compiler::fgMorphBlocks() lvSetMinOptsDoNotEnreg(); } - // Ensure the first BB is scratch if we might need it as a pred for - // the recursive tail call to loop optimization. - // - if (doesMethodHaveRecursiveTailcall()) - { - fgEnsureFirstBBisScratch(); - } - // Morph all blocks. // if (!optLocalAssertionProp) @@ -13551,17 +13536,16 @@ PhaseStatus Compiler::fgMorphBlocks() MorphUnreachableInfo unreachableInfo(this); // Allow edge creation to genReturnBB (target of return merging) - // and the scratch block successor (target for tail call to loop). + // and the first IL BB (target for tail call to loop). // This will also disallow dataflow into these blocks. // if (genReturnBB != nullptr) { genReturnBB->SetFlags(BBF_CAN_ADD_PRED); } - if (fgFirstBBisScratch()) - { - fgFirstBB->Next()->SetFlags(BBF_CAN_ADD_PRED); - } + // TODO-Cleanup: Remove this by transforming tailcalls to loops earlier. + BasicBlock* firstILBB = opts.IsOSR() ? fgEntryBB : fgGetFirstILBlock(); + firstILBB->SetFlags(BBF_CAN_ADD_PRED); // Remember this so we can sanity check that no new blocks will get created. // @@ -13586,10 +13570,7 @@ PhaseStatus Compiler::fgMorphBlocks() { genReturnBB->RemoveFlags(BBF_CAN_ADD_PRED); } - if (fgFirstBBisScratch()) - { - fgFirstBB->Next()->RemoveFlags(BBF_CAN_ADD_PRED); - } + firstILBB->RemoveFlags(BBF_CAN_ADD_PRED); } // Under OSR, we no longer need to specially protect the original method entry @@ -13637,9 +13618,35 @@ PhaseStatus Compiler::fgMorphBlocks() Metrics.MorphLocals = lvaCount; } + // We may have converted a tailcall into a loop, in which case the first BB + // may no longer be canonical. + fgCanonicalizeFirstBB(); + return PhaseStatus::MODIFIED_EVERYTHING; } +//------------------------------------------------------------------------ +// fgGetFirstILBB: Obtain the first basic block that was created due to IL. +// +// Returns: +// The basic block, skipping the init BB. +// +// Remarks: +// TODO-Cleanup: Refactor users to be able to remove this function. +// +BasicBlock* Compiler::fgGetFirstILBlock() +{ + BasicBlock* firstILBB = fgFirstBB; + while (firstILBB->HasFlag(BBF_INTERNAL)) + { + assert(firstILBB->KindIs(BBJ_ALWAYS)); + firstILBB = firstILBB->GetTarget(); + assert((firstILBB != nullptr) && (firstILBB != fgFirstBB)); + } + + return firstILBB; +} + //------------------------------------------------------------------------ // gtRemoveTreesAfterNoReturnCall: // Given a statement that may contain a no-return call, try to find it and diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index c2eae010428a01..35e58d539eea8b 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -5137,33 +5137,6 @@ void Compiler::fgSetEHRegionForNewPreheaderOrExit(BasicBlock* block) } } -//------------------------------------------------------------------------------ -// fgCanonicalizeFirstBB: Canonicalize the method entry for loop and dominator -// purposes. -// -// Returns: -// Suitable phase status. -// -PhaseStatus Compiler::fgCanonicalizeFirstBB() -{ - if (fgFirstBB->hasTryIndex()) - { - JITDUMP("Canonicalizing entry because it currently is the beginning of a try region\n"); - } - else if (fgFirstBB->bbPreds != nullptr) - { - JITDUMP("Canonicalizing entry because it currently has predecessors\n"); - } - else - { - return PhaseStatus::MODIFIED_NOTHING; - } - - assert(!fgFirstBBisScratch()); - fgEnsureFirstBBisScratch(); - return PhaseStatus::MODIFIED_EVERYTHING; -} - LoopSideEffects::LoopSideEffects() : VarInOut(VarSetOps::UninitVal()) , VarUseDef(VarSetOps::UninitVal()) diff --git a/src/coreclr/jit/patchpoint.cpp b/src/coreclr/jit/patchpoint.cpp index 71622ecfc3d759..ab695c0a5c3bb1 100644 --- a/src/coreclr/jit/patchpoint.cpp +++ b/src/coreclr/jit/patchpoint.cpp @@ -47,12 +47,6 @@ class PatchpointTransformer // Number of patchpoints transformed. int Run() { - // If the first block is a patchpoint, insert a scratch block. - if (compiler->fgFirstBB->HasFlag(BBF_PATCHPOINT)) - { - compiler->fgEnsureFirstBBisScratch(); - } - int count = 0; for (BasicBlock* const block : compiler->Blocks(compiler->fgFirstBB->Next())) { @@ -196,8 +190,6 @@ class PatchpointTransformer // ppCounter = void TransformEntry(BasicBlock* block) { - assert(!block->HasFlag(BBF_PATCHPOINT)); - int initialCounterValue = JitConfig.TC_OnStackReplacement_InitialCounter(); if (initialCounterValue < 0) @@ -208,7 +200,7 @@ class PatchpointTransformer GenTree* initialCounterNode = compiler->gtNewIconNode(initialCounterValue, TYP_INT); GenTree* ppCounterStore = compiler->gtNewStoreLclVarNode(ppCounterLclNum, initialCounterNode); - compiler->fgNewStmtNearEnd(block, ppCounterStore); + compiler->fgNewStmtAtBeg(block, ppCounterStore); } //------------------------------------------------------------------------ diff --git a/src/coreclr/jit/phase.cpp b/src/coreclr/jit/phase.cpp index 9723c123137ea2..0a6299fc1fdeb5 100644 --- a/src/coreclr/jit/phase.cpp +++ b/src/coreclr/jit/phase.cpp @@ -164,6 +164,11 @@ void Phase::PostPhase(PhaseStatus status) comp->fgDebugCheckBBlist(); } + if (hasFlag(checks, PhaseChecks::CHECK_FG_INIT_BLOCK)) + { + comp->fgDebugCheckInitBB(); + } + if (hasFlag(checks, PhaseChecks::CHECK_IR)) { comp->fgDebugCheckLinks(); From d7cc79023222151c2c52ae521e318396caa59f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20K=C3=B6plinger?= Date: Fri, 13 Dec 2024 15:50:01 +0100 Subject: [PATCH 62/70] Remove FabricBot from area-owners.md (#110525) We're not using it anymore. Fixes https://github.com/dotnet/runtime/issues/109961 --- docs/area-owners.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/area-owners.md b/docs/area-owners.md index d6bcf5391850ca..e1889072f9f736 100644 --- a/docs/area-owners.md +++ b/docs/area-owners.md @@ -8,7 +8,7 @@ This list is for this **dotnet/runtime** repo. The **dotnet/aspnetcore** repo ha ## Areas -Note: Editing this file doesn't update the mapping used by `@msftbot` for area-specific issue/PR notifications. That configuration is part of the [`fabricbot.json`](../.github/fabricbot.json) file, and many areas use GitHub teams for those notifications. If you're a community member interested in receiving area-specific issue/PR notifications, you won't appear in this table or be added to those GitHub teams, but you can create a PR that updates `fabricbot.json` to add yourself to those notifications. See [automation.md](infra/automation.md) for more information on the schema and tools used by FabricBot. +Note: Editing this file doesn't update the mapping used by `@dotnet-policy-service` for area-specific issue/PR notifications. That configuration is part of the [`resourceManagement.yml`](../.github/policies/resourceManagement.yml) file, and many areas use GitHub teams for those notifications. If you're a community member interested in receiving area-specific issue/PR notifications, you won't appear in this table or be added to those GitHub teams, but you can create a PR that updates `resourceManagement.yml` to add yourself to those notifications. See [automation.md](infra/automation.md) for more information. | Area | Lead | Owners (area experts to tag in PRs and issues) | Notes | |------------------------------------------------|----------------------|------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| From 15e01d41bb18b81ae15adb7d4aa0e6922c84a7e0 Mon Sep 17 00:00:00 2001 From: Steve Date: Sat, 14 Dec 2024 01:15:43 +0900 Subject: [PATCH 63/70] JIT: Spill newarr into temp (#110518) --- src/coreclr/jit/block.cpp | 1 + src/coreclr/jit/block.h | 8 +++++--- src/coreclr/jit/fgdiagnostic.cpp | 4 ++++ src/coreclr/jit/importer.cpp | 26 ++++++++++++++++++++++++-- src/coreclr/jit/importercalls.cpp | 29 +++++++++++++++++++++++++---- 5 files changed, 59 insertions(+), 9 deletions(-) diff --git a/src/coreclr/jit/block.cpp b/src/coreclr/jit/block.cpp index f032ce7748c754..eb4d5666fcf4e8 100644 --- a/src/coreclr/jit/block.cpp +++ b/src/coreclr/jit/block.cpp @@ -507,6 +507,7 @@ void BasicBlock::dspFlags() const {BBF_HAS_IDX_LEN, "idxlen"}, {BBF_HAS_MD_IDX_LEN, "mdidxlen"}, {BBF_HAS_NEWOBJ, "newobj"}, + {BBF_HAS_NEWARR, "newarr"}, {BBF_HAS_NULLCHECK, "nullcheck"}, {BBF_BACKWARD_JUMP, "bwd"}, {BBF_BACKWARD_JUMP_TARGET, "bwd-target"}, diff --git a/src/coreclr/jit/block.h b/src/coreclr/jit/block.h index d0d09143b1eda3..03770815f0125a 100644 --- a/src/coreclr/jit/block.h +++ b/src/coreclr/jit/block.h @@ -461,12 +461,14 @@ enum BasicBlockFlags : uint64_t BBF_CAN_ADD_PRED = MAKE_BBFLAG(38), // Ok to add pred edge to this block, even when "safe" edge creation disabled BBF_HAS_VALUE_PROFILE = MAKE_BBFLAG(39), // Block has a node that needs a value probing + BBF_HAS_NEWARR = MAKE_BBFLAG(40), // BB contains 'new' of an array type. + // The following are sets of flags. // Flags to update when two blocks are compacted BBF_COMPACT_UPD = BBF_GC_SAFE_POINT | BBF_NEEDS_GCPOLL | BBF_HAS_JMP | BBF_HAS_IDX_LEN | BBF_HAS_MD_IDX_LEN | BBF_BACKWARD_JUMP | \ - BBF_HAS_NEWOBJ | BBF_HAS_NULLCHECK | BBF_HAS_MDARRAYREF | BBF_LOOP_HEAD, + BBF_HAS_NEWOBJ | BBF_HAS_NEWARR | BBF_HAS_NULLCHECK | BBF_HAS_MDARRAYREF | BBF_LOOP_HEAD, // Flags a block should not have had before it is split. @@ -484,7 +486,7 @@ enum BasicBlockFlags : uint64_t // For example, the bottom block might or might not have BBF_HAS_NULLCHECK, but we assume it has BBF_HAS_NULLCHECK. // TODO: Should BBF_RUN_RARELY be added to BBF_SPLIT_GAINED ? - BBF_SPLIT_GAINED = BBF_DONT_REMOVE | BBF_HAS_JMP | BBF_BACKWARD_JUMP | BBF_HAS_IDX_LEN | BBF_HAS_MD_IDX_LEN | BBF_PROF_WEIGHT | \ + BBF_SPLIT_GAINED = BBF_DONT_REMOVE | BBF_HAS_JMP | BBF_BACKWARD_JUMP | BBF_HAS_IDX_LEN | BBF_HAS_MD_IDX_LEN | BBF_PROF_WEIGHT | BBF_HAS_NEWARR | \ BBF_HAS_NEWOBJ | BBF_KEEP_BBJ_ALWAYS | BBF_CLONED_FINALLY_END | BBF_HAS_NULLCHECK | BBF_HAS_HISTOGRAM_PROFILE | BBF_HAS_VALUE_PROFILE | BBF_HAS_MDARRAYREF | BBF_NEEDS_GCPOLL, // Flags that must be propagated to a new block if code is copied from a block to a new block. These are flags that @@ -492,7 +494,7 @@ enum BasicBlockFlags : uint64_t // have actually copied one of these type of tree nodes, but if we only copy a portion of the block's statements, // we don't know (unless we actually pay close attention during the copy). - BBF_COPY_PROPAGATE = BBF_HAS_NEWOBJ | BBF_HAS_NULLCHECK | BBF_HAS_IDX_LEN | BBF_HAS_MD_IDX_LEN | BBF_HAS_MDARRAYREF, + BBF_COPY_PROPAGATE = BBF_HAS_NEWOBJ | BBF_HAS_NEWARR | BBF_HAS_NULLCHECK | BBF_HAS_IDX_LEN | BBF_HAS_MD_IDX_LEN | BBF_HAS_MDARRAYREF, }; FORCEINLINE diff --git a/src/coreclr/jit/fgdiagnostic.cpp b/src/coreclr/jit/fgdiagnostic.cpp index cc65f6269fd315..9003430bb0bcc0 100644 --- a/src/coreclr/jit/fgdiagnostic.cpp +++ b/src/coreclr/jit/fgdiagnostic.cpp @@ -959,6 +959,10 @@ bool Compiler::fgDumpFlowGraph(Phases phase, PhasePosition pos) { fprintf(fgxFile, "\n callsNew=\"true\""); } + if (block->HasFlag(BBF_HAS_NEWARR)) + { + fprintf(fgxFile, "\n callsNewArr=\"true\""); + } if (block->HasFlag(BBF_LOOP_HEAD)) { fprintf(fgxFile, "\n loopHead=\"true\""); diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 334cf5e411aabe..576e8be6f367ce 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -9679,10 +9679,32 @@ void Compiler::impImportBlockCode(BasicBlock* block) // Remember that this function contains 'new' of an SD array. optMethodFlags |= OMF_HAS_NEWARRAY; + block->SetFlags(BBF_HAS_NEWARR); - /* Push the result of the call on the stack */ + if (opts.OptimizationEnabled()) + { + // We assign the newly allocated object (by a GT_CALL to newarr node) + // to a temp. Note that the pattern "temp = allocArr" is required + // by ObjectAllocator phase to be able to determine newarr nodes + // without exhaustive walk over all expressions. + lclNum = lvaGrabTemp(true DEBUGARG("NewArr temp")); - impPushOnStack(op1, tiRetVal); + impStoreToTemp(lclNum, op1, CHECK_SPILL_ALL); + + assert(lvaTable[lclNum].lvSingleDef == 0); + lvaTable[lclNum].lvSingleDef = 1; + JITDUMP("Marked V%02u as a single def local\n", lclNum); + lvaSetClass(lclNum, resolvedToken.hClass, true /* is Exact */); + + /* Push the result of the call on the stack */ + + impPushOnStack(gtNewLclvNode(lclNum, TYP_REF), tiRetVal); + } + else + { + /* Push the result of the call on the stack */ + impPushOnStack(op1, tiRetVal); + } callTyp = TYP_REF; } diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 08336a28d553a9..37ba933a75a97a 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -2606,12 +2606,33 @@ GenTree* Compiler::impInitializeArrayIntrinsic(CORINFO_SIG_INFO* sig) } // - // We start by looking at the last statement, making sure it's a store, and - // that the target of the store is the array passed to InitializeArray. + // We start by looking at the last statement, making sure it's a store. // GenTree* arrayLocalStore = impLastStmt->GetRootNode(); - if (!arrayLocalStore->OperIs(GT_STORE_LCL_VAR) || !arrayLocalNode->OperIs(GT_LCL_VAR) || - (arrayLocalStore->AsLclVar()->GetLclNum() != arrayLocalNode->AsLclVar()->GetLclNum())) + if (arrayLocalStore->OperIs(GT_STORE_LCL_VAR) && arrayLocalNode->OperIs(GT_LCL_VAR)) + { + // Make sure the target of the store is the array passed to InitializeArray. + if (arrayLocalStore->AsLclVar()->GetLclNum() != arrayLocalNode->AsLclVar()->GetLclNum()) + { + if (opts.OptimizationDisabled()) + { + return nullptr; + } + + // The array can be spilled to a temp for stack allocation. + // Try getting the actual store node from the previous statement. + if (arrayLocalStore->AsLclVar()->Data()->OperIs(GT_LCL_VAR) && impLastStmt->GetPrevStmt() != nullptr) + { + arrayLocalStore = impLastStmt->GetPrevStmt()->GetRootNode(); + if (!arrayLocalStore->OperIs(GT_STORE_LCL_VAR) || + arrayLocalStore->AsLclVar()->GetLclNum() != arrayLocalNode->AsLclVar()->GetLclNum()) + { + return nullptr; + } + } + } + } + else { return nullptr; } From 07e85b6b438fb794d1e0bf43927ab51b4f8c8740 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 13 Dec 2024 08:59:59 -0800 Subject: [PATCH 64/70] [cdac] Fix ISOSDacInterface13.TraverseLoaderHeap parameter type (#110678) --- .../managed/cdacreader/src/Legacy/ISOSDacInterface.cs | 9 +-------- src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs b/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs index b2495bfb839fc2..4ff1d08ede1293 100644 --- a/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs @@ -556,19 +556,12 @@ internal unsafe partial interface ISOSDacInterface12 int GetGlobalAllocationContext(ulong* allocPtr, ulong* allocLimit); } -internal struct VISITHEAP -{ - public ulong blockData; - public nuint blockSize; - public Interop.BOOL blockIsCurrentBlock; -} - [GeneratedComInterface] [Guid("3176a8ed-597b-4f54-a71f-83695c6a8c5e")] internal unsafe partial interface ISOSDacInterface13 { [PreserveSig] - int TraverseLoaderHeap(ulong loaderHeapAddr, /*LoaderHeapKind*/ int kind, VISITHEAP pCallback); + int TraverseLoaderHeap(ulong loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged pCallback); [PreserveSig] int GetDomainLoaderAllocator(ulong domainAddress, ulong* pLoaderAllocator); [PreserveSig] diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 7fef6b111de273..75a60ba1b46095 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -1412,7 +1412,7 @@ int ISOSDacInterface12.GetGlobalAllocationContext(ulong* allocPtr, ulong* allocL #endregion ISOSDacInterface12 #region ISOSDacInterface13 - int ISOSDacInterface13.TraverseLoaderHeap(ulong loaderHeapAddr, /*LoaderHeapKind*/ int kind, VISITHEAP pCallback) + int ISOSDacInterface13.TraverseLoaderHeap(ulong loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged pCallback) => _legacyImpl13 is not null ? _legacyImpl13.TraverseLoaderHeap(loaderHeapAddr, kind, pCallback) : HResults.E_NOTIMPL; int ISOSDacInterface13.GetDomainLoaderAllocator(ulong domainAddress, ulong* pLoaderAllocator) => _legacyImpl13 is not null ? _legacyImpl13.GetDomainLoaderAllocator(domainAddress, pLoaderAllocator) : HResults.E_NOTIMPL; From 05d687e6feb5cd84dc36ca08ec5dbd75b3cf9b1b Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 13 Dec 2024 12:26:16 -0800 Subject: [PATCH 65/70] [cdac] Handle non-IL method descs in `RuntimeTypeSystem_1.GetMethodClassificationDataType` (#110602) - Add the different method desc types to the data descriptor - We only need their size right now - Add tests for different method desc classifications - Mostly fill-in for things I found we didn't cover - `GetNativeCode_StableEntryPoint_NonVtableSlot` is the one that actually hits the updated code in this change --- .../debug/runtimeinfo/datadescriptor.h | 22 ++ .../DataType.cs | 5 + .../MethodClassification.cs | 3 +- .../MethodDescOptionalSlots.cs | 10 +- .../cdacreader/tests/MethodDescTests.cs | 255 +++++++++++++++++- .../MockDescriptors.MethodDescriptors.cs | 74 ++++- .../cdacreader/tests/MockMemorySpace.cs | 4 + 7 files changed, 355 insertions(+), 18 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index b3894f4efa71d1..424a1a764039f6 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -373,6 +373,28 @@ CDAC_TYPE_SIZE(sizeof(DynamicMethodDesc)) CDAC_TYPE_FIELD(DynamicMethodDesc, /*pointer*/, MethodName, cdac_data::MethodName) CDAC_TYPE_END(DynamicMethodDesc) +CDAC_TYPE_BEGIN(ArrayMethodDesc) +CDAC_TYPE_SIZE(sizeof(ArrayMethodDesc)) +CDAC_TYPE_END(ArrayMethodDesc) + +CDAC_TYPE_BEGIN(FCallMethodDesc) +CDAC_TYPE_SIZE(sizeof(FCallMethodDesc)) +CDAC_TYPE_END(FCallMethodDesc) + +CDAC_TYPE_BEGIN(PInvokeMethodDesc) +CDAC_TYPE_SIZE(sizeof(NDirectMethodDesc)) +CDAC_TYPE_END(PInvokeMethodDesc) + +CDAC_TYPE_BEGIN(EEImplMethodDesc) +CDAC_TYPE_SIZE(sizeof(EEImplMethodDesc)) +CDAC_TYPE_END(EEImplMethodDesc) + +#ifdef FEATURE_COMINTEROP +CDAC_TYPE_BEGIN(CLRToCOMCallMethodDesc) +CDAC_TYPE_SIZE(sizeof(CLRToCOMCallMethodDesc)) +CDAC_TYPE_END(CLRToCOMCallMethodDesc) +#endif // FEATURE_COMINTEROP + CDAC_TYPE_BEGIN(CodePointer) CDAC_TYPE_SIZE(sizeof(PCODE)) CDAC_TYPE_END(CodePointer) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index ddcdfbac5789a9..ef93b469dcfe50 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -57,6 +57,11 @@ public enum DataType InstantiatedMethodDesc, DynamicMethodDesc, StoredSigMethodDesc, + ArrayMethodDesc, + FCallMethodDesc, + PInvokeMethodDesc, + EEImplMethodDesc, + CLRToCOMCallMethodDesc, RangeSectionMap, RangeSectionFragment, RangeSection, diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodClassification.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodClassification.cs index 4291b13e53cc97..2efb5bf8818ded 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodClassification.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodClassification.cs @@ -4,6 +4,7 @@ namespace Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; +// See MethodClassification in src/coreclr/vm/method.hpp internal enum MethodClassification { IL = 0, // IL @@ -12,7 +13,7 @@ internal enum MethodClassification EEImpl = 3, // special method; implementation provided by EE (like Delegate Invoke) Array = 4, // Array ECall Instantiated = 5, // Instantiated generic methods, including descriptors - // for both shared and unshared code (see InstantiatedMethodDesc) + // for both shared and unshared code (see InstantiatedMethodDesc) ComInterop = 6, Dynamic = 7, // for method desc with no metadata behind } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs index 4405ce8fc0ff71..81efb5b54115b6 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs @@ -46,12 +46,12 @@ private static uint StartOffset(MethodClassification classification, Target targ DataType type = classification switch { MethodClassification.IL => DataType.MethodDesc, - MethodClassification.FCall => throw new NotImplementedException(), //TODO[cdac]: - MethodClassification.PInvoke => throw new NotImplementedException(), //TODO[cdac]: - MethodClassification.EEImpl => throw new NotImplementedException(), //TODO[cdac]: - MethodClassification.Array => throw new NotImplementedException(), //TODO[cdac]: + MethodClassification.FCall => DataType.FCallMethodDesc, + MethodClassification.PInvoke => DataType.PInvokeMethodDesc, + MethodClassification.EEImpl => DataType.EEImplMethodDesc, + MethodClassification.Array => DataType.ArrayMethodDesc, MethodClassification.Instantiated => DataType.InstantiatedMethodDesc, - MethodClassification.ComInterop => throw new NotImplementedException(), //TODO[cdac]: + MethodClassification.ComInterop => DataType.CLRToCOMCallMethodDesc, MethodClassification.Dynamic => DataType.DynamicMethodDesc, _ => throw new InvalidOperationException($"Unexpected method classification 0x{classification:x2} for MethodDesc") }; diff --git a/src/native/managed/cdacreader/tests/MethodDescTests.cs b/src/native/managed/cdacreader/tests/MethodDescTests.cs index 3157dcfaa535b6..c9b675e8ec75e2 100644 --- a/src/native/managed/cdacreader/tests/MethodDescTests.cs +++ b/src/native/managed/cdacreader/tests/MethodDescTests.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Diagnostics.DataContractReader.Contracts; using Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; using Moq; @@ -11,20 +13,23 @@ namespace Microsoft.Diagnostics.DataContractReader.Tests; public class MethodDescTests { - private static Target CreateTarget(MockDescriptors.MethodDescriptors methodDescBuilder) + private static Target CreateTarget(MockDescriptors.MethodDescriptors methodDescBuilder, Mock mockExecutionManager = null) { MockMemorySpace.Builder builder = methodDescBuilder.Builder; var target = new TestPlaceholderTarget(builder.TargetTestHelpers.Arch, builder.GetReadContext().ReadFromTarget, methodDescBuilder.Types, methodDescBuilder.Globals); + + mockExecutionManager ??= new Mock(); target.SetContracts(Mock.Of( c => c.RuntimeTypeSystem == ((IContractFactory)new RuntimeTypeSystemFactory()).CreateContract(target, 1) && c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1) - && c.PlatformMetadata == new Mock().Object)); + && c.PlatformMetadata == new Mock().Object + && c.ExecutionManager == mockExecutionManager.Object)); return target; } [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void MethodDescGetMethodDescTokenOk(MockTarget.Architecture arch) + public void GetMethodDescHandle_ILMethod_GetBasicData(MockTarget.Architecture arch) { TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); @@ -69,6 +74,163 @@ public void MethodDescGetMethodDescTokenOk(MockTarget.Architecture arch) Assert.False(isCollectible); TargetPointer versioning = rts.GetMethodDescVersioningState(handle); Assert.Equal(TargetPointer.Null, versioning); + + // Method classification - IL method + Assert.False(rts.IsStoredSigMethodDesc(handle, out _)); + Assert.False(rts.IsNoMetadataMethod(handle, out _)); + Assert.False(rts.IsDynamicMethod(handle)); + Assert.False(rts.IsILStub(handle)); + Assert.False(rts.IsArrayMethod(handle, out _)); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void IsArrayMethod(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder); + MockDescriptors.Loader loaderBuilder = new(builder); + MockDescriptors.MethodDescriptors methodDescBuilder = new(rtsBuilder, loaderBuilder); + + ushort numVirtuals = 1; + TargetPointer methodTable = AddMethodTable(rtsBuilder, numVirtuals); + + byte count = 5; + uint methodDescSize = methodDescBuilder.Types[DataType.ArrayMethodDesc].Size.Value; + uint methodDescSizeByAlignment = methodDescSize / methodDescBuilder.MethodDescAlignment; + byte chunkSize = (byte)(count * methodDescSizeByAlignment); + TargetPointer chunk = methodDescBuilder.AddMethodDescChunk(methodTable, string.Empty, count, chunkSize, tokenRange: 0); + + TargetPointer[] arrayMethods = new TargetPointer[count]; + for (byte i = 0; i < count; i++) + { + // Add the array methods by setting the appropriate slot number + // Array vtable is: + // + // Get + // Set + // Address + // .ctor + // [optionally other constructors] + byte index = (byte)(i * methodDescSizeByAlignment); + ushort slotNum = (ushort)(numVirtuals + i); + ushort flags = (ushort)MethodClassification.Array | (ushort)MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot; + arrayMethods[i] = methodDescBuilder.SetMethodDesc(chunk, index, slotNum, flags, tokenRemainder: 0); + } + + Target target = CreateTarget(methodDescBuilder); + IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; + + for (byte i = 0; i < count; i++) + { + MethodDescHandle handle = rts.GetMethodDescHandle(arrayMethods[i]); + Assert.NotEqual(TargetPointer.Null, handle.Address); + Assert.True(rts.IsStoredSigMethodDesc(handle, out _)); + Assert.True(rts.IsArrayMethod(handle, out ArrayFunctionType functionType)); + + ArrayFunctionType expectedFunctionType = i <= (byte)ArrayFunctionType.Constructor + ? (ArrayFunctionType)i + : ArrayFunctionType.Constructor; + Assert.Equal(expectedFunctionType, functionType); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void IsDynamicMethod(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder); + MockDescriptors.Loader loaderBuilder = new(builder); + MockDescriptors.MethodDescriptors methodDescBuilder = new(rtsBuilder, loaderBuilder); + + TargetPointer methodTable = AddMethodTable(rtsBuilder); + + byte count = 2; + uint methodDescSize = methodDescBuilder.Types[DataType.DynamicMethodDesc].Size.Value; + uint methodDescSizeByAlignment = methodDescSize / methodDescBuilder.MethodDescAlignment; + byte chunkSize = (byte)(count * methodDescSizeByAlignment); + TargetPointer chunk = methodDescBuilder.AddMethodDescChunk(methodTable, string.Empty, count, chunkSize, tokenRange: 0); + + ushort flags = (ushort)MethodClassification.Dynamic; + TargetPointer dynamicMethod = methodDescBuilder.SetMethodDesc(chunk, index: 0, slotNum: 0, flags, tokenRemainder: 0); + methodDescBuilder.SetDynamicMethodDesc(dynamicMethod, (uint)RuntimeTypeSystem_1.DynamicMethodDescExtendedFlags.IsLCGMethod); + TargetPointer ilStubMethod = methodDescBuilder.SetMethodDesc(chunk, index: (byte)methodDescSizeByAlignment, slotNum: 1, flags, tokenRemainder: 0); + methodDescBuilder.SetDynamicMethodDesc(ilStubMethod, (uint)RuntimeTypeSystem_1.DynamicMethodDescExtendedFlags.IsILStub); + + Target target = CreateTarget(methodDescBuilder); + IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; + + { + MethodDescHandle handle = rts.GetMethodDescHandle(dynamicMethod); + Assert.NotEqual(TargetPointer.Null, handle.Address); + Assert.True(rts.IsStoredSigMethodDesc(handle, out _)); + Assert.True(rts.IsNoMetadataMethod(handle, out _)); + Assert.True(rts.IsDynamicMethod(handle)); + Assert.False(rts.IsILStub(handle)); + } + { + MethodDescHandle handle = rts.GetMethodDescHandle(ilStubMethod); + Assert.NotEqual(TargetPointer.Null, handle.Address); + Assert.True(rts.IsStoredSigMethodDesc(handle, out _)); + Assert.True(rts.IsNoMetadataMethod(handle, out _)); + Assert.False(rts.IsDynamicMethod(handle)); + Assert.True(rts.IsILStub(handle)); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void IsGenericMethodDefinition(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder); + MockDescriptors.Loader loaderBuilder = new(builder); + MockDescriptors.MethodDescriptors methodDescBuilder = new(rtsBuilder, loaderBuilder); + + TargetPointer methodTable = AddMethodTable(rtsBuilder); + + byte count = 2; + uint methodDescSize = methodDescBuilder.Types[DataType.InstantiatedMethodDesc].Size.Value; + uint methodDescSizeByAlignment = methodDescSize / methodDescBuilder.MethodDescAlignment; + byte chunkSize = (byte)(count * methodDescSizeByAlignment); + TargetPointer chunk = methodDescBuilder.AddMethodDescChunk(methodTable, string.Empty, count, chunkSize, tokenRange: 0); + + ushort flags = (ushort)MethodClassification.Instantiated; + TargetPointer genericMethodDef = methodDescBuilder.SetMethodDesc(chunk, index: 0, slotNum: 0, flags, tokenRemainder: 0); + methodDescBuilder.SetInstantiatedMethodDesc(genericMethodDef, (ushort)RuntimeTypeSystem_1.InstantiatedMethodDescFlags2.GenericMethodDefinition, []); + TargetPointer[] typeArgsRawAddrs = [0x1000, 0x2000, 0x3000]; + TargetPointer[] typeArgsHandles = typeArgsRawAddrs.Select(a => GetTypeDescHandlePointer(a)).ToArray(); + + TargetPointer genericWithInst = methodDescBuilder.SetMethodDesc(chunk, index: (byte)methodDescSizeByAlignment, slotNum: 1, flags, tokenRemainder: 0); + methodDescBuilder.SetInstantiatedMethodDesc(genericWithInst, (ushort)RuntimeTypeSystem_1.InstantiatedMethodDescFlags2.GenericMethodDefinition, typeArgsHandles); + + Target target = CreateTarget(methodDescBuilder); + IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; + + { + MethodDescHandle handle = rts.GetMethodDescHandle(genericMethodDef); + Assert.NotEqual(TargetPointer.Null, handle.Address); + Assert.True(rts.IsGenericMethodDefinition(handle)); + ReadOnlySpan instantiation = rts.GetGenericMethodInstantiation(handle); + Assert.Equal(0, instantiation.Length); + } + + { + MethodDescHandle handle = rts.GetMethodDescHandle(genericWithInst); + Assert.NotEqual(TargetPointer.Null, handle.Address); + Assert.True(rts.IsGenericMethodDefinition(handle)); + ReadOnlySpan instantiation = rts.GetGenericMethodInstantiation(handle); + Assert.Equal(typeArgsRawAddrs.Length, instantiation.Length); + for (int i = 0; i < typeArgsRawAddrs.Length; i++) + { + Assert.Equal(typeArgsHandles[i], instantiation[i].Address); + Assert.Equal(typeArgsRawAddrs[i], instantiation[i].TypeDescAddress()); + } + } } public static IEnumerable StdArchOptionalSlotsData() @@ -98,12 +260,7 @@ public void GetAddressOfNativeCodeSlot_OptionalSlots(MockTarget.Architecture arc MockDescriptors.MethodDescriptors methodDescBuilder = new(rtsBuilder, loaderBuilder); MethodDescFlags_1.MethodDescFlags flags = (MethodDescFlags_1.MethodDescFlags)flagsValue; - ushort numVirtuals = 1; - TargetPointer eeClass = rtsBuilder.AddEEClass(string.Empty, 0, 2, 1); - TargetPointer methodTable = rtsBuilder.AddMethodTable(string.Empty, - mtflags: default, mtflags2: default, baseSize: helpers.ObjectBaseSize, - module: TargetPointer.Null, parentMethodTable: TargetPointer.Null, numInterfaces: 0, numVirtuals: numVirtuals); - rtsBuilder.SetEEClassAndCanonMTRefs(eeClass, methodTable); + TargetPointer methodTable = AddMethodTable(rtsBuilder); uint methodDescSize = methodDescBuilder.Types[DataType.MethodDesc].Size.Value; if (flags.HasFlag(MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot)) @@ -135,4 +292,84 @@ public void GetAddressOfNativeCodeSlot_OptionalSlots(MockTarget.Architecture arc Assert.Equal(expectedCodeSlotAddr, actualNativeCodeSlotAddr); } } + + public static IEnumerable StdArchMethodDescTypeData() + { + foreach (object[] arr in new MockTarget.StdArch()) + { + MockTarget.Architecture arch = (MockTarget.Architecture)arr[0]; + yield return new object[] { arch, DataType.MethodDesc }; + yield return new object[] { arch, DataType.FCallMethodDesc }; + yield return new object[] { arch, DataType.PInvokeMethodDesc }; + yield return new object[] { arch, DataType.EEImplMethodDesc }; + yield return new object[] { arch, DataType.ArrayMethodDesc }; + yield return new object[] { arch, DataType.InstantiatedMethodDesc }; + yield return new object[] { arch, DataType.CLRToCOMCallMethodDesc }; + yield return new object[] { arch, DataType.DynamicMethodDesc }; + } + } + + [Theory] + [MemberData(nameof(StdArchMethodDescTypeData))] + public void GetNativeCode_StableEntryPoint_NonVtableSlot(MockTarget.Architecture arch, DataType methodDescType) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder); + MockDescriptors.Loader loaderBuilder = new(builder); + MockDescriptors.MethodDescriptors methodDescBuilder = new(rtsBuilder, loaderBuilder); + + TargetPointer methodTable = AddMethodTable(rtsBuilder); + MethodClassification classification = methodDescType switch + { + DataType.MethodDesc => MethodClassification.IL, + DataType.FCallMethodDesc => MethodClassification.FCall, + DataType.PInvokeMethodDesc => MethodClassification.PInvoke, + DataType.EEImplMethodDesc => MethodClassification.EEImpl, + DataType.ArrayMethodDesc => MethodClassification.Array, + DataType.InstantiatedMethodDesc => MethodClassification.Instantiated, + DataType.CLRToCOMCallMethodDesc => MethodClassification.ComInterop, + DataType.DynamicMethodDesc => MethodClassification.Dynamic, + _ => throw new ArgumentOutOfRangeException(nameof(methodDescType)) + }; + uint methodDescBaseSize = methodDescBuilder.Types[methodDescType].Size.Value; + uint methodDescSize = methodDescBaseSize + methodDescBuilder.Types[DataType.NonVtableSlot].Size!.Value; + byte chunkSize = (byte)(methodDescSize / methodDescBuilder.MethodDescAlignment); + TargetPointer chunk = methodDescBuilder.AddMethodDescChunk(methodTable, string.Empty, count: 1, chunkSize, tokenRange: 0); + + ushort flags = (ushort)((ushort)classification | (ushort)MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot); + TargetPointer methodDescAddress = methodDescBuilder.SetMethodDesc(chunk, index: 0, slotNum: 0, flags, tokenRemainder: 0, flags3: (ushort)MethodDescFlags_1.MethodDescFlags3.HasStableEntryPoint); + TargetCodePointer nativeCode = new TargetCodePointer(0x0789_abc0); + helpers.WritePointer( + methodDescBuilder.Builder.BorrowAddressRange(methodDescAddress + methodDescBaseSize, helpers.PointerSize), + nativeCode); + + Mock mockExecutionManager = new(); + CodeBlockHandle codeBlock = new CodeBlockHandle(methodDescAddress); + mockExecutionManager.Setup(em => em.GetCodeBlockHandle(nativeCode)) + .Returns(codeBlock); + mockExecutionManager.Setup(em => em.GetMethodDesc(codeBlock)) + .Returns(methodDescAddress); + Target target = CreateTarget(methodDescBuilder, mockExecutionManager); + IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; + + var handle = rts.GetMethodDescHandle(methodDescAddress); + Assert.NotEqual(TargetPointer.Null, handle.Address); + + TargetCodePointer actualNativeCode = rts.GetNativeCode(handle); + Assert.Equal(nativeCode, actualNativeCode); + } + + private TargetPointer AddMethodTable(MockDescriptors.RuntimeTypeSystem rtsBuilder, ushort numVirtuals = 5) + { + TargetPointer eeClass = rtsBuilder.AddEEClass(string.Empty, attr: 0, numMethods: 2, numNonVirtualSlots: 1); + TargetPointer methodTable = rtsBuilder.AddMethodTable(string.Empty, + mtflags: default, mtflags2: default, baseSize: rtsBuilder.Builder.TargetTestHelpers.ObjectBaseSize, + module: TargetPointer.Null, parentMethodTable: TargetPointer.Null, numInterfaces: 0, numVirtuals); + rtsBuilder.SetEEClassAndCanonMTRefs(eeClass, methodTable); + return methodTable; + } + + private static TargetPointer GetTypeDescHandlePointer(TargetPointer addr) + => addr | (ulong)RuntimeTypeSystem_1.TypeHandleBits.TypeDesc; } diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs index 2e77d0238e0efb..4ce2bb647fa011 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs @@ -40,6 +40,40 @@ public class MethodDescriptors ] }; + private static readonly TypeFields InstantiatedMethodDescFields = new TypeFields() + { + DataType = DataType.InstantiatedMethodDesc, + Fields = + [ + new(nameof(Data.InstantiatedMethodDesc.PerInstInfo), DataType.pointer), + new(nameof(Data.InstantiatedMethodDesc.NumGenericArgs), DataType.uint16), + new(nameof(Data.InstantiatedMethodDesc.Flags2), DataType.uint16), + ], + BaseTypeFields = MethodDescFields + }; + + private static readonly TypeFields StoredSigMethodDescFields = new TypeFields() + { + DataType = DataType.StoredSigMethodDesc, + Fields = + [ + new(nameof(Data.StoredSigMethodDesc.Sig), DataType.pointer), + new(nameof(Data.StoredSigMethodDesc.cSig), DataType.uint32), + new(nameof(Data.StoredSigMethodDesc.ExtendedFlags), DataType.uint32), + ], + BaseTypeFields = MethodDescFields + }; + + private static readonly TypeFields DynamicMethodDescFields = new TypeFields() + { + DataType = DataType.DynamicMethodDesc, + Fields = + [ + new(nameof(Data.DynamicMethodDesc.MethodName), DataType.pointer), + ], + BaseTypeFields = StoredSigMethodDescFields + }; + private const ulong DefaultAllocationRangeStart = 0x2000_2000; private const ulong DefaultAllocationRangeEnd = 0x2000_3000; @@ -78,10 +112,18 @@ internal MethodDescriptors(RuntimeTypeSystem rtsBuilder, Loader loaderBuilder, ( [ MethodDescFields, MethodDescChunkFields, + InstantiatedMethodDescFields, + StoredSigMethodDescFields, + DynamicMethodDescFields, ]); types[DataType.NonVtableSlot] = new Target.TypeInfo() { Size = (uint)TargetTestHelpers.PointerSize }; types[DataType.MethodImpl] = new Target.TypeInfo() { Size = (uint)TargetTestHelpers.PointerSize * 2 }; types[DataType.NativeCodeSlot] = new Target.TypeInfo() { Size = (uint)TargetTestHelpers.PointerSize }; + types[DataType.ArrayMethodDesc] = new Target.TypeInfo() { Size = types[DataType.StoredSigMethodDesc].Size.Value }; + types[DataType.FCallMethodDesc] = new Target.TypeInfo() { Size = types[DataType.MethodDesc].Size.Value + (uint)TargetTestHelpers.PointerSize }; + types[DataType.PInvokeMethodDesc] = new Target.TypeInfo() { Size = types[DataType.MethodDesc].Size.Value + (uint)TargetTestHelpers.PointerSize }; + types[DataType.EEImplMethodDesc] = new Target.TypeInfo() { Size = types[DataType.StoredSigMethodDesc].Size.Value }; + types[DataType.CLRToCOMCallMethodDesc] = new Target.TypeInfo() { Size = types[DataType.MethodDesc].Size.Value + (uint)TargetTestHelpers.PointerSize }; types = types .Concat(RTSBuilder.Types) .Concat(LoaderBuilder.Types) @@ -110,16 +152,42 @@ private TargetPointer GetMethodDescAddress(TargetPointer chunkAddress, byte inde return chunkAddress + methodDescChunkTypeInfo.Size.Value + index * MethodDescAlignment; } - internal TargetPointer SetMethodDesc(TargetPointer methodDescChunk, byte index, ushort slotNum, ushort flags, ushort tokenRemainder) + internal TargetPointer SetMethodDesc(TargetPointer methodDescChunk, byte index, ushort slotNum, ushort flags, ushort tokenRemainder, ushort flags3 = 0) { TargetPointer methodDesc = GetMethodDescAddress(methodDescChunk, index); Target.TypeInfo methodDescTypeInfo = Types[DataType.MethodDesc]; Span data = Builder.BorrowAddressRange(methodDesc, (int)methodDescTypeInfo.Size.Value); - TargetTestHelpers.Write(data.Slice(methodDescTypeInfo.Fields[nameof(Data.MethodDesc.ChunkIndex)].Offset), (byte)index); + TargetTestHelpers.Write(data.Slice(methodDescTypeInfo.Fields[nameof(Data.MethodDesc.ChunkIndex)].Offset), index); TargetTestHelpers.Write(data.Slice(methodDescTypeInfo.Fields[nameof(Data.MethodDesc.Flags)].Offset), flags); - TargetTestHelpers.Write(data.Slice(methodDescTypeInfo.Fields[nameof(Data.MethodDesc.Flags3AndTokenRemainder)].Offset), tokenRemainder); + TargetTestHelpers.Write(data.Slice(methodDescTypeInfo.Fields[nameof(Data.MethodDesc.Flags3AndTokenRemainder)].Offset), (ushort)(tokenRemainder | flags3)); TargetTestHelpers.Write(data.Slice(methodDescTypeInfo.Fields[nameof(Data.MethodDesc.Slot)].Offset), slotNum); return methodDesc; } + + internal void SetDynamicMethodDesc(TargetPointer methodDesc, uint extendedFlags) + { + Target.TypeInfo storedSigInfo = Types[DataType.StoredSigMethodDesc]; + Span data = Builder.BorrowAddressRange(methodDesc, (int)storedSigInfo.Size.Value); + TargetTestHelpers.Write(data.Slice(storedSigInfo.Fields[nameof(Data.StoredSigMethodDesc.ExtendedFlags)].Offset), extendedFlags); + } + + internal void SetInstantiatedMethodDesc(TargetPointer methodDesc, ushort flags, TargetPointer[] typesArgs) + { + Target.TypeInfo typeInfo = Types[DataType.InstantiatedMethodDesc]; + Span data = Builder.BorrowAddressRange(methodDesc, (int)typeInfo.Size.Value); + TargetTestHelpers.Write(data.Slice(typeInfo.Fields[nameof(Data.InstantiatedMethodDesc.Flags2)].Offset), flags); + TargetTestHelpers.Write(data.Slice(typeInfo.Fields[nameof(Data.InstantiatedMethodDesc.NumGenericArgs)].Offset), (ushort)typesArgs.Length); + if (typesArgs.Length > 0) + { + MockMemorySpace.HeapFragment fragment = _allocator.Allocate((ulong)(typesArgs.Length * TargetTestHelpers.PointerSize), "InstantiatedMethodDesc type args"); + Builder.AddHeapFragment(fragment); + TargetTestHelpers.WritePointer(data.Slice(typeInfo.Fields[nameof(Data.InstantiatedMethodDesc.PerInstInfo)].Offset), fragment.Address); + for (int i = 0; i < typesArgs.Length; i++) + { + Span span = fragment.Data.AsSpan().Slice(i * TargetTestHelpers.PointerSize); + TargetTestHelpers.WritePointer(span, typesArgs[i]); + } + } + } } } diff --git a/src/native/managed/cdacreader/tests/MockMemorySpace.cs b/src/native/managed/cdacreader/tests/MockMemorySpace.cs index a6a7e26781a1ed..364a43b6c3ddb8 100644 --- a/src/native/managed/cdacreader/tests/MockMemorySpace.cs +++ b/src/native/managed/cdacreader/tests/MockMemorySpace.cs @@ -120,8 +120,12 @@ internal class ReadContext internal int ReadFromTarget(ulong address, Span buffer) { + if (buffer.Length == 0) + return 0; + if (address == 0) return -1; + bool partialReadOcurred = false; HeapFragment lastHeapFragment = default; int availableLength = 0; From 97f857098968a6e8bd6c3dede763c020616d4ceb Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:28:10 -0800 Subject: [PATCH 66/70] [cDAC] Implement GCCover portion of SOSDacImpl::GetMethodDescData (#110322) * Add GetGCStressCodeCopy to ICodeVersions and IRuntimeTypeSystem --- docs/design/datacontracts/CodeVersions.md | 13 +++ .../design/datacontracts/RuntimeTypeSystem.md | 4 + .../debug/runtimeinfo/datadescriptor.cpp | 4 + .../debug/runtimeinfo/datadescriptor.h | 13 +++ src/coreclr/vm/codeversion.h | 3 + .../ContractRegistry.cs | 2 +- .../Contracts/ICodeVersions.cs | 2 + .../Contracts/IRuntimeTypeSystem.cs | 2 + .../DataType.cs | 1 + .../Contracts/CodeVersions_1.cs | 24 ++++++ .../Contracts/RuntimeTypeSystem_1.cs | 14 ++++ .../Data/MethodDesc.cs | 8 +- .../Data/NativeCodeVersionNode.cs | 8 +- .../cdacreader/src/Legacy/SOSDacImpl.cs | 12 ++- .../cdacreader/tests/CodeVersionsTests.cs | 79 +++++++++++++++++++ .../cdacreader/tests/MethodDescTests.cs | 2 + .../MockDescriptors.CodeVersions.cs | 5 +- .../MockDescriptors.MethodDescriptors.cs | 1 + .../MockDescriptors.RuntimeTypeSystem.cs | 1 + .../tests/MockDescriptors/MockDescriptors.cs | 11 +++ 20 files changed, 198 insertions(+), 11 deletions(-) diff --git a/docs/design/datacontracts/CodeVersions.md b/docs/design/datacontracts/CodeVersions.md index 27f0814c37dd34..c903bd84007cbc 100644 --- a/docs/design/datacontracts/CodeVersions.md +++ b/docs/design/datacontracts/CodeVersions.md @@ -40,6 +40,9 @@ public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc); // Return the instruction pointer corresponding to the start of the given native code version public virtual TargetCodePointer GetNativeCode(NativeCodeVersionHandle codeVersionHandle); + +// Gets the GCStressCodeCopy pointer if available, otherwise returns TargetPointer.Null +public virtual TargetPointer GetGCStressCodeCopy(NativeCodeVersionHandle codeVersionHandle); ``` ### Extension Methods ```csharp @@ -61,6 +64,7 @@ Data descriptors used: | NativeCodeVersionNode | NativeCode | indicates an explicit native code version node | | NativeCodeVersionNode | Flags | `NativeCodeVersionNodeFlags` flags, see below | | NativeCodeVersionNode | VersionId | Version ID corresponding to the parent IL code version | +| NativeCodeVersionNode | GCCoverageInfo | GCStress debug info, if supported | | ILCodeVersioningState | FirstVersionNode | pointer to the first `ILCodeVersionNode` | | ILCodeVersioningState | ActiveVersionKind | an `ILCodeVersionKind` value indicating which fields of the active version are value | | ILCodeVersioningState | ActiveVersionNode | if the active version is explicit, the NativeCodeVersionNode for the active version | @@ -68,6 +72,7 @@ Data descriptors used: | ILCodeVersioningState | ActiveVersionMethodDef | if the active version is synthetic or unknown, the MethodDef token for the method | | ILCodeVersionNode | VersionId | Version ID of the node | | ILCodeVersionNode | Next | Pointer to the next `ILCodeVersionNode`| +| GCCoverageInfo | SavedCode | Pointer to the GCCover saved code copy, if supported | The flag indicates that the default version of the code for a method desc is active: ```csharp @@ -249,3 +254,11 @@ bool ICodeVersions.CodeVersionManagerSupportsMethod(TargetPointer methodDescAddr return true; } ``` + +### Finding GCStress Code Copy +```csharp +public virtual TargetPointer GetGCStressCodeCopy(NativeCodeVersionHandle codeVersionHandle); +``` + +1. If `codeVersionHandle` is synthetic, use the `IRuntimeTypeSystem` to find the GCStressCodeCopy. +2. If `codeVersionHandle` is explicit, read the `NativeCodeVersionNode` for the `GCCoverageInfo` pointer. This value only exists in some builds. If the value doesn't exist or is a nullptr, return `TargetPointer.Null`. Otherwise return the `SavedCode` pointer from the `GCCoverageInfo` struct. diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 84726124483c73..b1d65b7c825273 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -159,6 +159,8 @@ partial interface IRuntimeTypeSystem : IContract // Get an instruction pointer that can be called to cause the MethodDesc to be executed public virtual TargetCodePointer GetNativeCode(MethodDescHandle methodDesc); + // Gets the GCStressCodeCopy pointer if available, otherwise returns TargetPointer.Null + public virtual TargetPointer GetGCStressCodeCopy(MethodDescHandle methodDesc); } ``` @@ -638,6 +640,7 @@ We depend on the following data descriptors: | `MethodDesc` | `Slot` | The method's slot | | `MethodDesc` | `Flags` | The method's flags | | `MethodDesc` | `Flags3AndTokenRemainder` | More flags for the method, and the low bits of the method's token's RID | +| `MethodDesc` | `GCCoverageInfo` | The method's GCCover debug info, if supported | | `MethodDescCodeData` | `VersioningState` | The IL versioning state associated with a method descriptor | `MethodDescChunk` | `MethodTable` | The method table set of methods belongs to | | `MethodDescChunk` | `Next` | The next chunk of methods | @@ -654,6 +657,7 @@ We depend on the following data descriptors: | `StoredSigMethodDesc` | `cSig` | Count of bytes in the metadata signature | | `StoredSigMethodDesc` | `ExtendedFlags` | Flags field for the `StoredSigMethodDesc` | | `DynamicMethodDesc` | `MethodName` | Pointer to Null-terminated UTF8 string describing the Method desc | +| `GCCoverageInfo` | `SavedCode` | Pointer to the GCCover saved code copy, if supported | The contract depends on the following other contracts diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp index a6443a730985e0..72a1ee18304578 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp @@ -13,6 +13,10 @@ #include "methodtable.h" #include "threads.h" +#ifdef HAVE_GCCOVER +#include "gccover.h" +#endif // HAVE_GCCOVER + // begin blob definition extern "C" diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 424a1a764039f6..e58d91bd6cab3a 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -331,6 +331,9 @@ CDAC_TYPE_FIELD(MethodDesc, /*uint16*/, Flags, cdac_data::Flags) CDAC_TYPE_FIELD(MethodDesc, /*uint16*/, Flags3AndTokenRemainder, cdac_data::Flags3AndTokenRemainder) CDAC_TYPE_FIELD(MethodDesc, /*uint8*/, EntryPointFlags, cdac_data::EntryPointFlags) CDAC_TYPE_FIELD(MethodDesc, /*pointer*/, CodeData, cdac_data::CodeData) +#ifdef HAVE_GCCOVER +CDAC_TYPE_FIELD(MethodDesc, /*pointer*/, GCCoverageInfo, offsetof(MethodDesc, m_GcCover)) +#endif // HAVE_GCCOVER CDAC_TYPE_END(MethodDesc) CDAC_TYPE_BEGIN(MethodDescChunk) @@ -544,6 +547,9 @@ CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, MethodDesc, cdac_data::NativeCode) CDAC_TYPE_FIELD(NativeCodeVersionNode, /*uint32*/, Flags, cdac_data::Flags) CDAC_TYPE_FIELD(NativeCodeVersionNode, /*nuint*/, ILVersionId, cdac_data::ILVersionId) +#ifdef HAVE_GCCOVER +CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, GCCoverageInfo, cdac_data::GCCoverageInfo) +#endif // HAVE_GCCOVER CDAC_TYPE_END(NativeCodeVersionNode) CDAC_TYPE_BEGIN(ILCodeVersionNode) @@ -557,6 +563,13 @@ CDAC_TYPE_BEGIN(ProfControlBlock) CDAC_TYPE_FIELD(ProfControlBlock, /*uint64*/, GlobalEventMask, offsetof(ProfControlBlock, globalEventMask)) CDAC_TYPE_END(ProfControlBlock) +#ifdef HAVE_GCCOVER +CDAC_TYPE_BEGIN(GCCoverageInfo) +CDAC_TYPE_INDETERMINATE(GCCoverageInfo) +CDAC_TYPE_FIELD(GCCoverageInfo, /*pointer*/, SavedCode, offsetof(GCCoverageInfo, savedCode)) +CDAC_TYPE_END(GCCoverageInfo) +#endif // HAVE_GCCOVER + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() diff --git a/src/coreclr/vm/codeversion.h b/src/coreclr/vm/codeversion.h index 442a2846543757..b9dc76b90e3066 100644 --- a/src/coreclr/vm/codeversion.h +++ b/src/coreclr/vm/codeversion.h @@ -330,6 +330,9 @@ struct cdac_data static constexpr size_t NativeCode = offsetof(NativeCodeVersionNode, m_pNativeCode); static constexpr size_t Flags = offsetof(NativeCodeVersionNode, m_flags); static constexpr size_t ILVersionId = offsetof(NativeCodeVersionNode, m_parentId); +#ifdef HAVE_GCCOVER + static constexpr size_t GCCoverageInfo = offsetof(NativeCodeVersionNode, m_gcCover); +#endif // HAVE_GCCOVER }; class NativeCodeVersionCollection diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index a81bdfe92fce95..901084cd2c7d5e 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -58,5 +58,5 @@ internal abstract class ContractRegistry /// /// Gets an instance of the ReJIT contract for the target. /// - public abstract IReJIT ReJIT { get; } + public abstract IReJIT ReJIT { get; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs index a6db692f7dede5..a422ccabfbd3e0 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs @@ -22,6 +22,8 @@ internal interface ICodeVersions : IContract public virtual TargetCodePointer GetNativeCode(NativeCodeVersionHandle codeVersionHandle) => throw new NotImplementedException(); + public virtual TargetPointer GetGCStressCodeCopy(NativeCodeVersionHandle codeVersionHandle) => throw new NotImplementedException(); + public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc) => throw new NotImplementedException(); } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index 31131c4475422d..ecf243f9b6ce8a 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -166,6 +166,8 @@ internal interface IRuntimeTypeSystem : IContract public virtual bool HasNativeCodeSlot(MethodDescHandle methodDesc) => throw new NotImplementedException(); public virtual TargetPointer GetAddressOfNativeCodeSlot(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + public virtual TargetPointer GetGCStressCodeCopy(MethodDescHandle methodDesc) => throw new NotImplementedException(); #endregion MethodDesc inspection APIs } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index ef93b469dcfe50..8ea30a324bfaee 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -81,4 +81,5 @@ public enum DataType NonVtableSlot, MethodImpl, NativeCodeSlot, + GCCoverageInfo, } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs index e6b1755004c710..70fec695684029 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs @@ -183,6 +183,30 @@ NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersionForILCodeVersion }); } + TargetPointer ICodeVersions.GetGCStressCodeCopy(NativeCodeVersionHandle codeVersionHandle) + { + Debug.Assert(codeVersionHandle.Valid); + + if (!codeVersionHandle.IsExplicit) + { + // NativeCodeVersion::GetGCCoverageInfo + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(codeVersionHandle.MethodDescAddress); + return rts.GetGCStressCodeCopy(md); + } + else + { + // NativeCodeVersionNode::GetGCCoverageInfo + NativeCodeVersionNode codeVersionNode = AsNode(codeVersionHandle); + if (codeVersionNode.GCCoverageInfo is TargetPointer gcCoverageInfoAddr && gcCoverageInfoAddr != TargetPointer.Null) + { + Target.TypeInfo gcCoverageInfoType = _target.GetTypeInfo(DataType.GCCoverageInfo); + return gcCoverageInfoAddr + (ulong)gcCoverageInfoType.Fields["SavedCode"].Offset; + } + return TargetPointer.Null; + } + } + [Flags] internal enum MethodDescVersioningStateFlags : byte { diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index c29930d49fced7..d32fae6d4b07c7 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -141,6 +141,9 @@ private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.Metho public bool IsUnboxingStub => HasFlags(MethodDescFlags_1.MethodDescFlags3.IsUnboxingStub); public TargetPointer CodeData => _desc.CodeData; + + public TargetPointer? GCCoverageInfo => _desc.GCCoverageInfo; + public bool IsIL => Classification == MethodClassification.IL || Classification == MethodClassification.Instantiated; internal bool HasNonVtableSlot => MethodDescOptionalSlots.HasNonVtableSlot(_desc.Flags); @@ -1012,6 +1015,17 @@ private TargetCodePointer GetMethodEntryPointIfExists(MethodDesc md) return _target.ReadCodePointer(addrOfSlot); } + TargetPointer IRuntimeTypeSystem.GetGCStressCodeCopy(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + if (md.GCCoverageInfo is TargetPointer gcCoverageInfoAddr && gcCoverageInfoAddr != TargetPointer.Null) + { + Target.TypeInfo gcCoverageInfoType = _target.GetTypeInfo(DataType.GCCoverageInfo); + return gcCoverageInfoAddr + (ulong)gcCoverageInfoType.Fields["SavedCode"].Offset; + } + return TargetPointer.Null; + } + private class NonValidatedMethodTableQueries : MethodValidation.IMethodTableQueries { private readonly RuntimeTypeSystem_1 _rts; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDesc.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDesc.cs index eea2a616194218..0181d329a8413a 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDesc.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDesc.cs @@ -18,6 +18,10 @@ public MethodDesc(Target target, TargetPointer address) Flags3AndTokenRemainder = target.Read(address + (ulong)type.Fields[nameof(Flags3AndTokenRemainder)].Offset); EntryPointFlags = target.Read(address + (ulong)type.Fields[nameof(EntryPointFlags)].Offset); CodeData = target.ReadPointer(address + (ulong)type.Fields[nameof(CodeData)].Offset); + if (type.Fields.ContainsKey(nameof(GCCoverageInfo))) + { + GCCoverageInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(GCCoverageInfo)].Offset); + } } public byte ChunkIndex { get; init; } @@ -26,7 +30,9 @@ public MethodDesc(Target target, TargetPointer address) public ushort Flags3AndTokenRemainder { get; init; } public byte EntryPointFlags { get; init; } - public TargetPointer CodeData { get; set; } + public TargetPointer CodeData { get; init; } + + public TargetPointer? GCCoverageInfo { get; init; } } internal sealed class InstantiatedMethodDesc : IData diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs index 02a7d37eb1e068..e68345e4499d23 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs @@ -17,12 +17,18 @@ public NativeCodeVersionNode(Target target, TargetPointer address) NativeCode = target.ReadCodePointer(address + (ulong)type.Fields[nameof(NativeCode)].Offset); Flags = target.Read(address + (ulong)type.Fields[nameof(Flags)].Offset); ILVersionId = target.ReadNUInt(address + (ulong)type.Fields[nameof(ILVersionId)].Offset); + if (type.Fields.ContainsKey(nameof(GCCoverageInfo))) + { + GCCoverageInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(GCCoverageInfo)].Offset); + } } public TargetPointer Next { get; init; } public TargetPointer MethodDesc { get; init; } public TargetCodePointer NativeCode { get; init; } - public uint Flags{ get; init; } + public uint Flags { get; init; } public TargetNUInt ILVersionId { get; init; } + + public TargetPointer? GCCoverageInfo { get; init; } } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 75a60ba1b46095..b99fbdee26cac1 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -350,16 +350,14 @@ int ISOSDacInterface.GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDes } } -#if false // TODO[cdac]: HAVE_GCCOVER + // HAVE_GCCOVER if (requestedNativeCodeVersion.Valid) { - TargetPointer gcCoverAddr = nativeCodeContract.GetGCCoverageInfo(requestedNativeCodeVersion); - if (gcCoverAddr != TargetPointer.Null) - { - throw new NotImplementedException(); // TODO[cdac]: gc stress code copy - } + // TargetPointer.Null if GCCover information is not available. + // In certain minidumps, we won't save the GCCover information. + // (it would be unwise to do so, it is heavy and not a customer scenario). + data->GCStressCodeCopy = nativeCodeContract.GetGCStressCodeCopy(requestedNativeCodeVersion); } -#endif // Unlike the legacy implementation, the cDAC does not currently populate // data->managedDynamicMethodObject. This field is unused in both SOS and CLRMD diff --git a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs index bdd6cacdd3a106..7a30e81ad99384 100644 --- a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs +++ b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs @@ -254,6 +254,24 @@ internal static Target CreateTarget( return target; } + internal static Target CreateTarget( + MockTarget.Architecture arch, + MockCodeVersions builder, + Mock mockRuntimeTypeSystem) + { + TestPlaceholderTarget target = new TestPlaceholderTarget( + arch, + builder.Builder.GetReadContext().ReadFromTarget, + builder.Types); + + IContractFactory cvfactory = new CodeVersionsFactory(); + ContractRegistry reg = Mock.Of( + c => c.CodeVersions == cvfactory.CreateContract(target, 1) + && c.RuntimeTypeSystem == mockRuntimeTypeSystem.Object); + target.SetContracts(reg); + return target; + } + [Theory] [ClassData(typeof(MockTarget.StdArch))] public void GetNativeCodeVersion_Null(MockTarget.Architecture arch) @@ -678,4 +696,65 @@ public void IlToNativeToIlCodeVersion_SyntheticAndExplicit(MockTarget.Architectu NativeCodeVersionHandle syntheticNativeCodeVersion = codeVersions.GetActiveNativeCodeVersionForILCodeVersion(methodDescAddress, syntheticILcodeVersion); Assert.True(syntheticILcodeVersion.Equals(codeVersions.GetILCodeVersion(syntheticNativeCodeVersion))); } + + private void GetGCStressCodeCopy_Impl(MockTarget.Architecture arch, bool returnsNull) + { + MockCodeVersions builder = new(arch); + Mock mockRTS = new(); + + // Setup synthetic NativeCodeVersion + TargetPointer expectedSyntheticCodeCopyAddr = returnsNull ? TargetPointer.Null : new(0x2345_6789); + TargetPointer syntheticMethodDescAddr = new(0x2345_8000); + NativeCodeVersionHandle syntheticHandle = NativeCodeVersionHandle.CreateSynthetic(syntheticMethodDescAddr); + MethodDescHandle methodDescHandle = new MethodDescHandle(syntheticMethodDescAddr); + mockRTS.Setup(rts => rts.GetMethodDescHandle(syntheticMethodDescAddr)).Returns(methodDescHandle); + mockRTS.Setup(rts => rts.GetGCStressCodeCopy(methodDescHandle)).Returns(expectedSyntheticCodeCopyAddr); + + // Setup explicit NativeCodeVersion + TargetPointer? explicitGCCoverageInfoAddr = returnsNull ? TargetPointer.Null : new(0x1234_5678); + TargetPointer nativeCodeVersionNode = builder.AddNativeCodeVersionNode(); + builder.FillNativeCodeVersionNode( + nativeCodeVersionNode, + methodDesc: TargetPointer.Null, + nativeCode: TargetCodePointer.Null, + next: TargetPointer.Null, + isActive: true, + ilVersionId: new(1), + gcCoverageInfo: explicitGCCoverageInfoAddr); + NativeCodeVersionHandle explicitHandle = NativeCodeVersionHandle.CreateExplicit(nativeCodeVersionNode); + + var target = CreateTarget(arch, builder, mockRTS); + var codeVersions = target.Contracts.CodeVersions; + + // TEST + TargetPointer actualSyntheticCodeCopyAddr = codeVersions.GetGCStressCodeCopy(syntheticHandle); + Assert.Equal(expectedSyntheticCodeCopyAddr, actualSyntheticCodeCopyAddr); + + if(returnsNull) + { + TargetPointer actualExplicitCodeCopyAddr = codeVersions.GetGCStressCodeCopy(explicitHandle); + Assert.Equal(TargetPointer.Null, actualExplicitCodeCopyAddr); + } + else + { + Target.TypeInfo gcCoverageInfoType = target.GetTypeInfo(DataType.GCCoverageInfo); + TargetPointer expectedExplicitCodeCopyAddr = explicitGCCoverageInfoAddr.Value + (ulong)gcCoverageInfoType.Fields["SavedCode"].Offset; + TargetPointer actualExplicitCodeCopyAddr = codeVersions.GetGCStressCodeCopy(explicitHandle); + Assert.Equal(expectedExplicitCodeCopyAddr, actualExplicitCodeCopyAddr); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetGCStressCodeCopy_Null(MockTarget.Architecture arch) + { + GetGCStressCodeCopy_Impl(arch, returnsNull: true); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetGCStressCodeCopy_NotNull(MockTarget.Architecture arch) + { + GetGCStressCodeCopy_Impl(arch, returnsNull: false); + } } diff --git a/src/native/managed/cdacreader/tests/MethodDescTests.cs b/src/native/managed/cdacreader/tests/MethodDescTests.cs index c9b675e8ec75e2..a53d201acc2351 100644 --- a/src/native/managed/cdacreader/tests/MethodDescTests.cs +++ b/src/native/managed/cdacreader/tests/MethodDescTests.cs @@ -74,6 +74,8 @@ public void GetMethodDescHandle_ILMethod_GetBasicData(MockTarget.Architecture ar Assert.False(isCollectible); TargetPointer versioning = rts.GetMethodDescVersioningState(handle); Assert.Equal(TargetPointer.Null, versioning); + TargetPointer gcStressCodeCopy = rts.GetGCStressCodeCopy(handle); + Assert.Equal(TargetPointer.Null, gcStressCodeCopy); // Method classification - IL method Assert.False(rts.IsStoredSigMethodDesc(handle, out _)); diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs index a4b2575eb2ac4c..bdfa03acefb2e6 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs @@ -34,6 +34,7 @@ public class CodeVersions new(nameof(Data.NativeCodeVersionNode.NativeCode), DataType.pointer), new(nameof(Data.NativeCodeVersionNode.Flags), DataType.uint32), new(nameof(Data.NativeCodeVersionNode.ILVersionId), DataType.nuint), + new(nameof(Data.NativeCodeVersionNode.GCCoverageInfo), DataType.pointer), ] }; @@ -94,6 +95,7 @@ public CodeVersions(MockMemorySpace.Builder builder, (ulong Start, ulong End) al NativeCodeVersionNodeFields, ILCodeVersioningStateFields, ILCodeVersionNodeFields, + GCCoverageInfoFields, ]); } @@ -116,7 +118,7 @@ public TargetPointer AddNativeCodeVersionNode() return fragment.Address; } - public void FillNativeCodeVersionNode(TargetPointer dest, TargetPointer methodDesc, TargetCodePointer nativeCode, TargetPointer next, bool isActive, TargetNUInt ilVersionId) + public void FillNativeCodeVersionNode(TargetPointer dest, TargetPointer methodDesc, TargetCodePointer nativeCode, TargetPointer next, bool isActive, TargetNUInt ilVersionId, TargetPointer? gcCoverageInfo = null) { Target.TypeInfo info = Types[DataType.NativeCodeVersionNode]; Span ncvn = Builder.BorrowAddressRange(dest, (int)info.Size!); @@ -125,6 +127,7 @@ public void FillNativeCodeVersionNode(TargetPointer dest, TargetPointer methodDe Builder.TargetTestHelpers.WritePointer(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.NativeCode)].Offset, Builder.TargetTestHelpers.PointerSize), nativeCode); Builder.TargetTestHelpers.Write(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.Flags)].Offset, sizeof(uint)), isActive ? (uint)CodeVersions_1.NativeCodeVersionNodeFlags.IsActiveChild : 0u); Builder.TargetTestHelpers.WriteNUInt(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.ILVersionId)].Offset, Builder.TargetTestHelpers.PointerSize), ilVersionId); + Builder.TargetTestHelpers.WritePointer(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.GCCoverageInfo)].Offset, Builder.TargetTestHelpers.PointerSize), gcCoverageInfo ?? TargetPointer.Null); } public (TargetPointer First, TargetPointer Active) AddNativeCodeVersionNodesForMethod(TargetPointer methodDesc, int count, int activeIndex, TargetCodePointer activeNativeCode, TargetNUInt ilVersion, TargetPointer? firstNode = null) diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs index 4ce2bb647fa011..4a0787ac65c27f 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs @@ -24,6 +24,7 @@ public class MethodDescriptors new(nameof(Data.MethodDesc.Flags3AndTokenRemainder), DataType.uint16), new(nameof(Data.MethodDesc.EntryPointFlags), DataType.uint8), new(nameof(Data.MethodDesc.CodeData), DataType.pointer), + new(nameof(Data.MethodDesc.GCCoverageInfo), DataType.pointer), ] }; diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs index bade23b8cb390a..8034cf22f70437 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs @@ -72,6 +72,7 @@ public class RuntimeTypeSystem FnPtrTypeDescFields, ParamTypeDescFields, TypeVarTypeDescFields, + GCCoverageInfoFields, ]); } diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.cs index 50d3c76135c768..23444bb58d55ba 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.cs @@ -198,6 +198,17 @@ internal record TypeFields ] }; + private static readonly TypeFields GCCoverageInfoFields = new TypeFields() + { + DataType = DataType.GCCoverageInfo, + Fields = + [ + // Add DummyField to ensure the offset of SavedCode is not added to the TargetPointer.Null + new("DummyField", DataType.pointer), + new("SavedCode", DataType.pointer), + ] + }; + internal static Dictionary GetTypesForTypeFields(TargetTestHelpers helpers, TypeFields[] typeFields) { Dictionary types = new(); From f1b1f3dec4e5212c2f547db2464d8c2d0a7799fa Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 13 Dec 2024 15:57:44 -0800 Subject: [PATCH 67/70] Remove unused Precode::IsCorrectMethodDesc (#110703) --- src/coreclr/vm/precode.cpp | 17 ----------------- src/coreclr/vm/precode.h | 1 - 2 files changed, 18 deletions(-) diff --git a/src/coreclr/vm/precode.cpp b/src/coreclr/vm/precode.cpp index 74111c297fc25f..e793425d2bec50 100644 --- a/src/coreclr/vm/precode.cpp +++ b/src/coreclr/vm/precode.cpp @@ -150,23 +150,6 @@ MethodDesc* Precode::GetMethodDesc(BOOL fSpeculative /*= FALSE*/) return (PTR_MethodDesc)pMD; } -BOOL Precode::IsCorrectMethodDesc(MethodDesc * pMD) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - MethodDesc * pMDfromPrecode = GetMethodDesc(TRUE); - - if (pMDfromPrecode == pMD) - return TRUE; - - return FALSE; -} - BOOL Precode::IsPointingToPrestub(PCODE target) { CONTRACTL diff --git a/src/coreclr/vm/precode.h b/src/coreclr/vm/precode.h index aca583676a20fe..894a708e195f6b 100644 --- a/src/coreclr/vm/precode.h +++ b/src/coreclr/vm/precode.h @@ -531,7 +531,6 @@ class Precode { PTR_PCODE GetTargetSlot(); MethodDesc * GetMethodDesc(BOOL fSpeculative = FALSE); - BOOL IsCorrectMethodDesc(MethodDesc * pMD); static Precode* Allocate(PrecodeType t, MethodDesc* pMD, LoaderAllocator *pLoaderAllocator, AllocMemTracker *pamTracker); From 1502947d1e6adb45dd01d067ba0edb04d138e06a Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Fri, 13 Dec 2024 17:00:51 -0800 Subject: [PATCH 68/70] JIT: capture class types when spilling a GDV arg (#110675) If we need to spill ref type args for a GDV, try to annotate the spilled locals with type information. --- src/coreclr/jit/indirectcalltransformer.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/indirectcalltransformer.cpp b/src/coreclr/jit/indirectcalltransformer.cpp index 9fbac3b55a46ee..f2149f62546b87 100644 --- a/src/coreclr/jit/indirectcalltransformer.cpp +++ b/src/coreclr/jit/indirectcalltransformer.cpp @@ -754,8 +754,21 @@ class IndirectCallTransformer // void SpillArgToTempBeforeGuard(CallArg* arg) { - unsigned tmpNum = compiler->lvaGrabTemp(true DEBUGARG("guarded devirt arg temp")); - GenTree* store = compiler->gtNewTempStore(tmpNum, arg->GetNode()); + unsigned tmpNum = compiler->lvaGrabTemp(true DEBUGARG("guarded devirt arg temp")); + GenTree* const argNode = arg->GetNode(); + GenTree* store = compiler->gtNewTempStore(tmpNum, argNode); + + if (argNode->TypeIs(TYP_REF)) + { + bool isExact = false; + bool isNonNull = false; + CORINFO_CLASS_HANDLE cls = compiler->gtGetClassHandle(argNode, &isExact, &isNonNull); + if (cls != NO_CLASS_HANDLE) + { + compiler->lvaSetClass(tmpNum, cls, isExact); + } + } + Statement* storeStmt = compiler->fgNewStmtFromTree(store, stmt->GetDebugInfo()); compiler->fgInsertStmtAtEnd(checkBlock, storeStmt); From fd3c397893f4ed1b3fb264893ca85121b239f0f3 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 13 Dec 2024 18:23:22 -0800 Subject: [PATCH 69/70] [cdac] Clear cached data as part of IXCLRDataProcess::Flush (#110700) Found this while running the diagnostics repo SOS tests with the cDAC enabled. General sequence for the repro was: ``` !sethostruntime -none !bpmd !bpmd g g !clrstack ``` Printed stack shows `` for some method(s). Between the first and second breakpoints, more methods were jitted and the corresponding code heap list updated. When a new method in the stack for `` had the same code heap list as any method from ``, we'd end up with a stale end address for the heap list and determine that the method was invalid (outside the address range). The cdac was assuming that the target would be created every time the state changes, but that is not the case (for the repro above, `!sethostruntime -none` resulted in not re-creating the target). We need to handle `IXCLRDataProcess::Flush` calls to clear out any cached data. With this change, the SOS tests with the cDAC enabled run successfully with both a Release and Debug cdacreader (on a Release runtime). --- .../Target.cs | 4 ++++ .../ContractDescriptorTarget.cs | 5 +++++ .../src/Legacy/SOSDacImpl.IXCLRDataProcess.cs | 10 +++++++++- .../cdacreader/tests/TestPlaceholderTarget.cs | 19 ++++++++++--------- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs index 26ff88bdacc09a..aa1174cd0b2e16 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs @@ -139,6 +139,10 @@ public interface IDataCache /// On return, set to the cached data value, or null if the data hasn't been cached yet. /// True if a copy of the data is cached, or false otherwise bool TryGet(ulong address, [NotNullWhen(true)] out T? data); + /// + /// Clear all cached data + /// + void Clear(); } /// diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs index 965c0a1507aec5..668c9c988d1d4d 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs @@ -519,6 +519,11 @@ public bool TryGet(ulong address, [NotNullWhen(true)] out T? data) return false; } + + public void Clear() + { + _readDataByAddress.Clear(); + } } private readonly struct Reader(ReadFromTargetDelegate readFromTarget) diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.IXCLRDataProcess.cs index 58799a26cf645d..f04d39aa8000a1 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -14,7 +14,15 @@ namespace Microsoft.Diagnostics.DataContractReader.Legacy; internal sealed unsafe partial class SOSDacImpl : IXCLRDataProcess, IXCLRDataProcess2 { int IXCLRDataProcess.Flush() - => _legacyProcess is not null ? _legacyProcess.Flush() : HResults.E_NOTIMPL; + { + _target.ProcessedData.Clear(); + + // As long as any part of cDAC falls back to the legacy DAC, we need to propagate the Flush call + if (_legacyProcess is not null) + return _legacyProcess.Flush(); + + return HResults.S_OK; + } int IXCLRDataProcess.StartEnumTasks(ulong* handle) => _legacyProcess is not null ? _legacyProcess.StartEnumTasks(handle) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index 843266f69f370c..007574ca7d7431 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -230,19 +230,17 @@ public override Target.TypeInfo GetTypeInfo(DataType dataType) public override ContractRegistry Contracts => _contractRegistry; // A data cache that stores data in a dictionary and calls IData.Create to construct the data. - private class DefaultDataCache : Target.IDataCache + private sealed class DefaultDataCache : Target.IDataCache { - protected readonly Target _target; - protected readonly Dictionary<(ulong, Type), object?> _readDataByAddress = []; + private readonly Target _target; + private readonly Dictionary<(ulong, Type), object?> _readDataByAddress = []; public DefaultDataCache(Target target) { _target = target; } - public virtual T GetOrAdd(TargetPointer address) where T : Data.IData => DefaultGetOrAdd(address); - - protected T DefaultGetOrAdd(TargetPointer address) where T : Data.IData + public T GetOrAdd(TargetPointer address) where T : Data.IData { if (TryGet(address, out T? result)) return result; @@ -258,9 +256,7 @@ protected T DefaultGetOrAdd(TargetPointer address) where T : Data.IData return result!; } - public virtual bool TryGet(ulong address, [NotNullWhen(true)] out T? data) => DefaultTryGet(address, out data); - - protected bool DefaultTryGet(ulong address, [NotNullWhen(true)] out T? data) + public bool TryGet(ulong address, [NotNullWhen(true)] out T? data) { data = default; if (!_readDataByAddress.TryGetValue((address, typeof(T)), out object? dataObj)) @@ -273,6 +269,11 @@ protected bool DefaultTryGet(ulong address, [NotNullWhen(true)] out T? data) } return false; } + + public void Clear() + { + _readDataByAddress.Clear(); + } } } From f52248fa12b007232481864f44b8680a6f563fa4 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 13 Dec 2024 18:49:19 -0800 Subject: [PATCH 70/70] Improve codegen for Vector512.ExtractMostSignificatBits (#110662) --- src/coreclr/jit/decomposelongs.cpp | 105 +++++++++++++++++++++++++++ src/coreclr/jit/decomposelongs.h | 1 + src/coreclr/jit/hwintrinsicxarch.cpp | 17 ++--- 3 files changed, 111 insertions(+), 12 deletions(-) diff --git a/src/coreclr/jit/decomposelongs.cpp b/src/coreclr/jit/decomposelongs.cpp index 84802400feeb0a..9280f459f978b9 100644 --- a/src/coreclr/jit/decomposelongs.cpp +++ b/src/coreclr/jit/decomposelongs.cpp @@ -1707,6 +1707,11 @@ GenTree* DecomposeLongs::DecomposeHWIntrinsic(LIR::Use& use) return DecomposeHWIntrinsicGetElement(use, hwintrinsicTree); } + case NI_EVEX_MoveMask: + { + return DecomposeHWIntrinsicMoveMask(use, hwintrinsicTree); + } + default: { noway_assert(!"unexpected GT_HWINTRINSIC node in long decomposition"); @@ -1830,6 +1835,106 @@ GenTree* DecomposeLongs::DecomposeHWIntrinsicGetElement(LIR::Use& use, GenTreeHW return FinalizeDecomposition(use, loResult, hiResult, hiResult); } +//------------------------------------------------------------------------ +// DecomposeHWIntrinsicMoveMask: Decompose GT_HWINTRINSIC -- NI_EVEX_MoveMask +// +// Decompose a MoveMask(x) node on Vector512<*>. For: +// +// GT_HWINTRINSIC{MoveMask}[*](simd_var) +// +// create: +// +// tmp_simd_var = simd_var +// tmp_simd_lo = GT_HWINTRINSIC{GetLower}(tmp_simd_var) +// lo_result = GT_HWINTRINSIC{MoveMask}(tmp_simd_lo) +// tmp_simd_hi = GT_HWINTRINSIC{GetUpper}(tmp_simd_var) +// hi_result = GT_HWINTRINSIC{MoveMask}(tmp_simd_hi) +// return: GT_LONG(lo_result, hi_result) +// +// Noting that for all types except byte/sbyte, hi_result will be exclusively +// zero and so we can actually optimize this a bit more directly +// +// Arguments: +// use - the LIR::Use object for the def that needs to be decomposed. +// node - the hwintrinsic node to decompose +// +// Return Value: +// The next node to process. +// +GenTree* DecomposeLongs::DecomposeHWIntrinsicMoveMask(LIR::Use& use, GenTreeHWIntrinsic* node) +{ + assert(node == use.Def()); + assert(varTypeIsLong(node)); + assert(node->GetHWIntrinsicId() == NI_EVEX_MoveMask); + + GenTree* op1 = node->Op(1); + CorInfoType simdBaseJitType = node->GetSimdBaseJitType(); + var_types simdBaseType = node->GetSimdBaseType(); + unsigned simdSize = node->GetSimdSize(); + + assert(varTypeIsArithmetic(simdBaseType)); + assert(op1->TypeGet() == TYP_MASK); + assert(simdSize == 64); + + GenTree* loResult = nullptr; + GenTree* hiResult = nullptr; + + if (varTypeIsByte(simdBaseType)) + { + // Create: + // simdTmpVar = op1 + + GenTree* simdTmpVar = RepresentOpAsLocalVar(op1, node, &node->Op(1)); + unsigned simdTmpVarNum = simdTmpVar->AsLclVarCommon()->GetLclNum(); + JITDUMP("[DecomposeHWIntrinsicMoveMask]: Saving op1 tree to a temp var:\n"); + DISPTREERANGE(Range(), simdTmpVar); + Range().Remove(simdTmpVar); + + Range().InsertBefore(node, simdTmpVar); + + // Create: + // loResult = GT_HWINTRINSIC{MoveMask}(simdTmpVar) + + loResult = m_compiler->gtNewSimdHWIntrinsicNode(TYP_INT, simdTmpVar, NI_EVEX_MoveMask, simdBaseJitType, 32); + Range().InsertBefore(node, loResult); + + simdTmpVar = m_compiler->gtNewLclLNode(simdTmpVarNum, simdTmpVar->TypeGet()); + Range().InsertBefore(node, simdTmpVar); + + // Create: + // simdTmpVar = GT_HWINTRINSIC{ShiftRightMask}(simdTmpVar, 32) + // hiResult = GT_HWINTRINSIC{MoveMask}(simdTmpVar) + + GenTree* shiftIcon = m_compiler->gtNewIconNode(32, TYP_INT); + Range().InsertBefore(node, shiftIcon); + + simdTmpVar = m_compiler->gtNewSimdHWIntrinsicNode(TYP_MASK, simdTmpVar, shiftIcon, NI_EVEX_ShiftRightMask, + simdBaseJitType, 64); + Range().InsertBefore(node, simdTmpVar); + + hiResult = m_compiler->gtNewSimdHWIntrinsicNode(TYP_INT, simdTmpVar, NI_EVEX_MoveMask, simdBaseJitType, 32); + Range().InsertBefore(node, hiResult); + } + else + { + // Create: + // loResult = GT_HWINTRINSIC{MoveMask}(op1) + + loResult = m_compiler->gtNewSimdHWIntrinsicNode(TYP_INT, op1, NI_EVEX_MoveMask, simdBaseJitType, simdSize); + Range().InsertBefore(node, loResult); + + // Create: + // hiResult = GT_ICON(0) + + hiResult = m_compiler->gtNewZeroConNode(TYP_INT); + Range().InsertBefore(node, hiResult); + } + + // Done with the original tree; remove it. + Range().Remove(node); + + return FinalizeDecomposition(use, loResult, hiResult, hiResult); +} #endif // FEATURE_HW_INTRINSICS //------------------------------------------------------------------------ diff --git a/src/coreclr/jit/decomposelongs.h b/src/coreclr/jit/decomposelongs.h index 744061091e42be..02681322a552e9 100644 --- a/src/coreclr/jit/decomposelongs.h +++ b/src/coreclr/jit/decomposelongs.h @@ -64,6 +64,7 @@ class DecomposeLongs #ifdef FEATURE_HW_INTRINSICS GenTree* DecomposeHWIntrinsic(LIR::Use& use); GenTree* DecomposeHWIntrinsicGetElement(LIR::Use& use, GenTreeHWIntrinsic* node); + GenTree* DecomposeHWIntrinsicMoveMask(LIR::Use& use, GenTreeHWIntrinsic* node); #endif // FEATURE_HW_INTRINSICS GenTree* OptimizeCastFromDecomposedLong(GenTreeCast* cast, GenTree* nextNode); diff --git a/src/coreclr/jit/hwintrinsicxarch.cpp b/src/coreclr/jit/hwintrinsicxarch.cpp index d4651444bf5e7b..7ba8c6615f3617 100644 --- a/src/coreclr/jit/hwintrinsicxarch.cpp +++ b/src/coreclr/jit/hwintrinsicxarch.cpp @@ -2511,14 +2511,13 @@ GenTree* Compiler::impSpecialIntrinsic(NamedIntrinsic intrinsic, break; } + case NI_Vector128_ExtractMostSignificantBits: + case NI_Vector256_ExtractMostSignificantBits: case NI_Vector512_ExtractMostSignificantBits: { -#if defined(TARGET_X86) - // TODO-XARCH-CQ: It may be beneficial to decompose this operation - break; -#endif // TARGET_X86 + assert(sig->numArgs == 1); - if (IsBaselineVector512IsaSupportedOpportunistically()) + if ((simdSize == 64) || (varTypeIsShort(simdBaseType) && canUseEvexEncoding())) { op1 = impSIMDPopStack(); @@ -2527,14 +2526,8 @@ GenTree* Compiler::impSpecialIntrinsic(NamedIntrinsic intrinsic, op1 = gtNewSimdCvtVectorToMaskNode(TYP_MASK, op1, simdBaseJitType, simdSize); } retNode = gtNewSimdHWIntrinsicNode(retType, op1, NI_EVEX_MoveMask, simdBaseJitType, simdSize); + break; } - break; - } - - case NI_Vector128_ExtractMostSignificantBits: - case NI_Vector256_ExtractMostSignificantBits: - { - assert(sig->numArgs == 1); if ((simdSize != 32) || varTypeIsFloating(simdBaseType) || compOpportunisticallyDependsOn(InstructionSet_AVX2))