diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index f2a6d8a9..4336b722 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -2,157 +2,162 @@
## Repository Overview
-This is the **Base Class Library (BCL)** for [.NET nanoFramework](https://www.nanoframework.net/) — a free, open-source platform that implements .NET for resource-constrained embedded devices (microcontrollers). The library produces `mscorlib.dll`, the core runtime library for nanoFramework, and is analogous to `System.Private.CoreLib` in full .NET.
+This repository contains the **Base Class Library (BCL)** for [.NET nanoFramework](https://www.nanoframework.net/) — a free, open-source platform that enables writing managed .NET code for embedded systems and microcontrollers. The BCL provides the core `System` namespace types and is the nanoFramework equivalent of `mscorlib`.
-The BCL is split into two NuGet packages:
-- **`nanoFramework.CoreLibrary`** (`nanoFramework.CoreLibrary/` project) — includes `System.Reflection` API.
-- **`nanoFramework.CoreLibrary.NoReflection`** (`nanoFramework.CoreLibrary.NoReflection/` project) — excludes `System.Reflection` to save flash space on constrained targets.
+## Key Architectural Concepts
-## Project System
+### This Is NOT Standard .NET
+- The code targets embedded systems (MCUs) with severe memory and flash constraints.
+- Many methods are implemented in native C++ ("native stubs") and are declared with `[MethodImpl(MethodImplOptions.InternalCall)]` — **do not add a body to these methods**.
+- Some standard .NET APIs are intentionally unsupported (throw `NotSupportedException`) to preserve assembly/image size. Do not remove these stubs; they exist to satisfy interface contracts.
+- The library targets `TargetFrameworkVersion v1.0` (the nanoFramework target, not desktop .NET).
-- Projects use the **nanoFramework project system** with `.nfproj` file extension (not `.csproj`). The project type GUIDs are `{11A8DD76-328B-46DF-9F39-F559912D0360};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}`.
-- The solution file is `nanoFramework.CoreLibrary.sln`.
-- The output assembly name is `mscorlib` (set via `mscorlib` and `true`).
-- The target framework version is `v1.0` (nanoFramework's own TFM, not standard .NET).
-- This is a **core assembly** (`true` and `True`), meaning it has no external managed dependencies.
+### Two Library Flavours
+| Project | Description | NuGet |
+|---|---|---|
+| `nanoFramework.CoreLibrary` | Full BCL **with** `System.Reflection` | `nanoFramework.CoreLibrary` |
+| `nanoFramework.CoreLibrary.NoReflection` | BCL **without** reflection (smaller flash footprint) | `nanoFramework.CoreLibrary.NoReflection` |
-## Key Conventions
+Both produce an assembly named `mscorlib`. The no-reflection variant excludes files under `System/Reflection/` and sets no `NANOCLR_REFLECTION` define.
-### Native Calls
-Many methods are implemented natively in the nanoFramework C++ runtime (nanoCLR). These are declared with `extern` and `[MethodImpl(MethodImplOptions.InternalCall)]`:
+### Project File Format
+Projects use `.nfproj` files (nanoFramework MSBuild project system), not standard `.csproj`. These require Visual Studio with the **nanoFramework VS extension** installed, or MSBuild with `NFProjectSystem.CSharp.targets`.
-```csharp
-using System.Runtime.CompilerServices;
+## Repository Structure
-[MethodImpl(MethodImplOptions.InternalCall)]
-public static extern bool Equals(String a, String b);
```
-
-When implementing new methods that require native support, mark them with `[MethodImpl(MethodImplOptions.InternalCall)]` and `extern`. The native counterpart must be implemented in the [nf-interpreter](https://github.com/nanoframework/nf-interpreter) repository.
-
-### Reflection Conditional Compilation
-Code that depends on reflection must be wrapped with `#if NANOCLR_REFLECTION`:
-
-```csharp
-#if NANOCLR_REFLECTION
-[DebuggerDisplay("Count = {Count}")]
-#endif // NANOCLR_REFLECTION
+nanoFramework.CoreLibrary/ # Main BCL project (with reflection)
+ System/ # All System.* source files
+ Collections/ # IEnumerable, ArrayList, generic interfaces
+ Diagnostics/ # Debug, Debugger, attributes
+ Globalization/ # CultureInfo, DateTimeFormatInfo, etc.
+ IO/ # IOException
+ Reflection/ # Assembly, MethodInfo, FieldInfo, etc.
+ Runtime/ # CompilerServices, InteropServices, Remoting
+ Threading/ # Thread, Monitor, Timer, WaitHandle, etc.
+ CoreLibrary.nfproj
+ Directory.Build.props
+
+nanoFramework.CoreLibrary.NoReflection/ # BCL without reflection
+ System/ # Subset of System.* files (no Reflection/)
+ CoreLibrary.NoReflection.nfproj
+
+Tests/ # Unit tests (one folder per test suite)
+ NFUnitTestArithmetic/
+ NFUnitTestArray/
+ NFUnitTestBitConverter/
+ NFUnitTestSystemLib/ # Covers most primitive types, strings, DateTime, etc.
+ NFUnitTestThread/
+ ... (21 test suites total)
+
+nanoFramework.TestFramework/ # Git submodule: test framework source
+azure-pipelines.yml # CI/CD pipeline (Azure Pipelines, Windows)
+nanoFramework.CoreLibrary.sln # Main solution (library + tests)
+nanoFramework.CoreLibrary.Benchmarks.sln # Benchmarks solution
+version.json # Nerdbank.GitVersioning configuration
```
-The `NANOCLR_REFLECTION` constant is defined in the `nanoFramework.CoreLibrary` project but **not** in `nanoFramework.CoreLibrary.NoReflection`.
-
-### Namespace Layout
-- Source files live under `nanoFramework.CoreLibrary/System/` and `nanoFramework.CoreLibrary.NoReflection/System/`, mirroring the `System` namespace hierarchy.
-- Sub-namespaces map to subdirectories: `Collections/`, `Threading/`, `Reflection/`, `Diagnostics/`, `Globalization/`, `IO/`, `Runtime/CompilerServices/`, etc.
+## Build & Test
-### XML Documentation
-All public APIs must have complete XML documentation comments (``, ``, ``, ``, etc.). Keep them concise; avoid adding message strings to exceptions to preserve assembly/memory size. Example:
+### Build Requirements
+- **Windows only** — the nanoFramework build toolchain runs on Windows (Azure Pipelines uses `windows-latest`).
+- Visual Studio 2022 with the nanoFramework VS extension, or MSBuild 17+.
+- NuGet packages are restored automatically; `packages.lock.json` enforces locked restore in CI.
-```csharp
-// (no message to preserve assembly size/memory consumption)
-throw new NotSupportedException();
-```
+### Building
+```powershell
+# Restore NuGet packages first
+nuget restore nanoFramework.CoreLibrary.sln
-### License Header
-Every source file starts with:
+# Build the solution (Release)
+msbuild nanoFramework.CoreLibrary.sln /p:Configuration=Release /p:Platform="Any CPU"
-```csharp
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
+# Build only CoreLibrary (Debug)
+msbuild nanoFramework.CoreLibrary\CoreLibrary.nfproj /p:Configuration=Debug
```
-### Pragma Suppression
-Some `#pragma warning disable/restore` directives for CS0659, CS0661, and S1206 (SonarAnalyzer) are used where native implementations handle `GetHashCode`. Follow this pattern when adding types that have native hash code support.
-
-## Building
-
-Build uses **Azure Pipelines** (`azure-pipelines.yml`), not GitHub Actions. The only GitHub Actions workflow is `generate-changelog.yml`, which triggers on version tags.
-
-For local builds:
-- Install the **nanoFramework Visual Studio extension** which brings the nanoFramework project system and `MSBuildExtensionsPath\nanoFramework\v1.0\`.
-- Open `nanoFramework.CoreLibrary.sln` in Visual Studio (Windows only).
-- Restore NuGet packages before building.
-- The pipeline uses `windows-latest` and references the shared [nf-tools](https://github.com/nanoframework/nf-tools) template library.
-- Versioning is managed by **Nerdbank.GitVersioning** (`version.json`); the current version prefix is `1.17`.
-- The assembly is strong-named using `key.snk`.
+### Running Tests
+Tests use the [nanoFramework.TestFramework](https://github.com/nanoframework/nanoFramework.TestFramework) with the **nanoCLR Win32 emulator** (no real hardware needed by default).
-## Testing
+```powershell
+# From Developer Command Prompt for VS 2022:
+vstest.console.exe .\Tests\NFUnitTestBitConverter\bin\Release\NFUnitTest.dll ^
+ /Settings:.\.runsettings ^
+ /TestAdapterPath:.\nanoFramework.TestFramework\source\TestAdapter\bin\Debug\net4.8
-### Framework
-Tests use the [nanoFramework.TestFramework](https://github.com/nanoframework/nanoFramework.TestFramework) (included as a git submodule at `nanoFramework.TestFramework/`). Tests run on the **nanoCLR Win32 emulator** (no real hardware required by default).
-
-### Test Layout
-- All test projects live under `Tests/`, one project per test area (e.g., `Tests/NFUnitTestSystemLib/`, `Tests/NFUnitTestArray/`, etc.).
-- Test class files are named `UnitTest.cs`.
-- Each test class uses `[TestClass]` and each test method uses `[TestMethod]`.
-- Data-driven tests use `[DataRow(...)]`.
-
-### Critical Setup for Test Projects
-Because this repository IS the core library, test projects must use **project references** instead of NuGet references for:
-1. `mscorlib` (this repo)
-2. `nanoFramework.TestFramework`
-3. `nanoFramework.UnitTestLauncher`
-
-Do **not** add NuGet references for these three; reference the submodule/project directly.
-
-Each test subdirectory needs a `nano.runsettings` file. The root `.runsettings` configures the emulator run:
-```xml
-
- Verbose
- False
- --forcegc
-
+# Run all tests using runsettings:
+# .runsettings -> uses preview nanoCLR from NuGet (default)
+# local_clr.runsettings -> uses a locally built nanoCLR
```
-### Running Tests from Command Line
-Use the `Developer Command Prompt for VS 2019` (or 2022):
-```cmd
-vstest.console.exe .\Tests\NFUnitTestBitConverter\bin\Release\NFUnitTest.dll \
- /Settings:.\Tests\NFUnitTestAdapater\nano.runsettings \
- /TestAdapterPath:.\nanoFramework.TestFramework\source\TestAdapter\bin\Debug\net4.8 \
- /Diag:.\log.txt /Logger:trx
-```
-
-Note: The `NFUnitTestAdapter` project (in `Tests/`) must remain untouched — it provides the core nanoCLR Win32 executable needed by the test adapter.
+Key runsettings config:
+- `IsRealHardware=False` — runs on emulator
+- `UsePreviewClr=True` — downloads latest preview nanoCLR
+- `MaxCpuCount=1` — tests run sequentially
+- `TestSessionTimeout=1200000` (20 min)
-### Test Coverage Requirements
-**All new code must include tests** covering:
-- All public methods and properties
-- All events (where applicable)
-- All documented exceptions
-- Do not introduce more test failures than already exist.
+### Adding Tests
+- Each test suite is a separate `.nfproj` in `Tests/`.
+- Use `nanoFramework.TestFramework` attributes: `[TestClass]`, `[TestMethod]`, `[Setup]`, `[Cleanup]`, `[DataRow]`.
+- **Do not** reference the NuGet `nanoFramework.TestFramework` — use the project reference from `nanoFramework.TestFramework/` submodule instead (this is required for the CoreLibrary because it replaces mscorlib).
+- **Do not** reference `mscorlib`, `nanoFramework.TestFramework`, or `nanoFramework.UnitTestLauncher` NuGet packages; use project references.
+- Every new API **must** have test coverage for all methods, properties, events, and exceptions.
-## Repository Structure Summary
+## Coding Conventions
+### File Header
+Every `.cs` file must begin with:
+```csharp
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
```
-nanoFramework.CoreLibrary/ # Main BCL project (with reflection)
- System/ # Source files mirroring System namespace
-nanoFramework.CoreLibrary.NoReflection/ # BCL without reflection
- System/ # Source files (subset, no Reflection/)
-Tests/
- NFUnitTest/ # One test project per feature area
- TestFramework/ # Test framework source (submodule)
- UnitTestLauncher/ # Launcher project (submodule)
-nanoFramework.TestFramework/ # Git submodule: test framework source
-azure-pipelines/ # Pipeline template overrides
-azure-pipelines.yml # Main CI pipeline (Azure DevOps)
-nanoFramework.CoreLibrary.sln # Main solution
-nanoFramework.CoreLibrary.nuspec # NuGet packaging spec
-version.json # Nerdbank.GitVersioning config
-.runsettings # Root test run settings
-```
-
-## Common Pitfalls
-
-1. **Do not use `System.Span`** — only `SpanByte` (a custom byte-only span) is supported due to embedded constraints.
-2. **No LINQ, no async/await** — these are not supported in nanoFramework.
-3. **Memory is precious** — avoid string allocations in exception messages, prefer no-argument exception constructors.
-4. **NoReflection parity** — any new managed (non-reflection) API added to `nanoFramework.CoreLibrary` must also be added to `nanoFramework.CoreLibrary.NoReflection` unless it inherently requires reflection.
-5. **Strong naming** — the assembly must be signed with `key.snk`; do not remove or replace the key file.
-6. **`[Reflection.FieldNoReflection]`** — fields that must not be exposed via reflection (e.g., native object handles) use this attribute.
-
-## Known Errors & Workarounds
-- **`NFProjectSystem.Default.props` not found**: Occurs when the nanoFramework VS extension is not installed or `MSBuildExtensionsPath` is not set. Install the [nanoFramework VS extension](https://marketplace.visualstudio.com/items?itemName=nanoframework.nanoFramework-VS2022-Extension) and build within Visual Studio.
-- **Test NuGet conflicts**: If you see "could not find mscorlib" during test runs, ensure test projects use project references (not NuGet) for mscorlib, TestFramework, and UnitTestLauncher. Check the `.nfproj` `` vs `` items.
-- **Device firmware mismatch**: When running on real hardware, flash the device with the firmware built from this source to ensure the native checksum matches. The pipeline embeds the native version checksum in the NuGet package description.
+### Naming
+| Item | Convention | Example |
+|---|---|---|
+| Constants | PascalCase | `MaxValue` |
+| Private/internal fields | `_camelCase` | `_thread` |
+| Private/internal static fields | `s_camelCase` | `s_instance` |
+| Public members | PascalCase | `GetEnumerator` |
+
+### Formatting (from `.editorconfig`)
+- Indent: 4 spaces (C#), 2 spaces (XML/project files)
+- Line endings: CRLF
+- Encoding: UTF-8 BOM
+- Always use braces, even for single-line blocks
+- `using` directives outside namespace
+- System directives first
+
+### C# Language
+- Language version: C# 13.0 (main project); `default` for NoReflection variant
+- Avoid `var` — use explicit types
+- Prefer expression-bodied members where appropriate
+- `[MethodImpl(MethodImplOptions.InternalCall)]` for native implementations — never add a body
+
+### Assembly Size Awareness
+- Avoid adding features that significantly increase binary size.
+- Throw `NotSupportedException` (without a message string) when a method cannot be implemented on the constrained target.
+- Comment rationale when omitting functionality for size reasons.
+
+## CI/CD Pipeline
+
+CI runs on **Azure Pipelines** (`azure-pipelines.yml`), not GitHub Actions. There is no GitHub Actions CI workflow for the main build. The pipeline:
+1. Builds `nanoFramework.CoreLibrary.sln` on Windows
+2. Runs all unit tests with nanoCLR emulator
+3. Packages and publishes two NuGet packages
+4. Updates dependent repositories on tag releases
+
+## Important Notes for Contributions
+
+1. **Avoid breaking native interop**: Methods marked `[MethodImpl(MethodImplOptions.InternalCall)]` have matching native C++ implementations in the nanoFramework interpreter. Changing their signatures requires coordinated changes to the native runtime.
+2. **Two-project sync**: When adding or removing files from `nanoFramework.CoreLibrary`, evaluate whether the same change applies to `nanoFramework.CoreLibrary.NoReflection`. Reflection-related files belong only in the main project.
+3. **Versioning**: Versions are managed by [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning) via `version.json`. Do not manually edit version numbers.
+4. **Assembly signing**: The assembly is strong-named with `key.snk`. Do not replace or remove this file.
+5. **`Friends.cs`**: This file declares `InternalsVisibleTo` attributes for test projects. Add new test projects here if they need access to internals.
+
+## Known Constraints & Workarounds
+
+- The build requires Windows and the nanoFramework MSBuild extension. Running builds in a Linux environment (e.g., standard GitHub Actions) will fail because `NFProjectSystem.CSharp.targets` is not available there.
+- Tests must be run via `vstest.console.exe` with the nanoFramework test adapter; standard `dotnet test` does not work.
+- The `RestoreLockedMode` is enabled in CI to prevent unexpected package version changes — if packages need updating, the `packages.lock.json` must be regenerated and committed.
+- Some APIs intentionally throw `NotSupportedException` or return stub values — this is by design for embedded constraints, not a bug.
diff --git a/.runsettings b/.runsettings
index 3d17b679..536c73fb 100644
--- a/.runsettings
+++ b/.runsettings
@@ -1,4 +1,4 @@
-
+
@@ -12,5 +12,6 @@
Verbose
False
--forcegc
+ True
-
\ No newline at end of file
+
diff --git a/Tests/NFUnitTestGC/TestGC.cs b/Tests/NFUnitTestGC/TestGC.cs
index 0bec97a8..bf52e121 100644
--- a/Tests/NFUnitTestGC/TestGC.cs
+++ b/Tests/NFUnitTestGC/TestGC.cs
@@ -1,7 +1,8 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// 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 nanoFramework.TestFramework;
namespace NFUnitTestGC
@@ -15,8 +16,6 @@ public void TestGCStress()
int maxArraySize = 1024 * 32;
object[] arrays = new object[600];
- // Starting TestGCStress
-
for (int loop = 0; loop < 100; loop++)
{
OutputHelper.WriteLine($"Running iteration {loop}");
@@ -24,7 +23,7 @@ public void TestGCStress()
for (int i = 0; i < arrays.Length - 1;)
{
OutputHelper.WriteLine($"Alloc array of {maxArraySize} bytes @ pos {i}");
- arrays[i++] = new byte[maxArraySize]; ;
+ arrays[i++] = new byte[maxArraySize];
OutputHelper.WriteLine($"Alloc array of 64 bytes @ pos {i}");
arrays[i++] = new byte[64];
@@ -37,8 +36,35 @@ public void TestGCStress()
arrays[i] = null;
}
}
+ }
+
+ [TestMethod]
+ public void TestGetTotalMemory()
+ {
+ // create several objects
+ object[] objects = new object[100];
+
+ for (int i = 0; i < objects.Length; i++)
+ {
+ objects[i] = new object();
+ }
+
+ // get total memory
+ long totalMemory = GC.GetTotalMemory(false);
+ OutputHelper.WriteLine($"Total memory: {totalMemory} bytes");
+
+ // release objects
+ for (int i = 0; i < objects.Length; i++)
+ {
+ objects[i] = null;
+ }
+
+ // get total memory, forcing full collection
+ long totalMemoryAfterCollection = GC.GetTotalMemory(true);
+ OutputHelper.WriteLine($"Total memory: {totalMemoryAfterCollection} bytes");
- // Completed TestGCStress
+ // check if memory was released
+ Assert.IsTrue(totalMemory > totalMemoryAfterCollection, "Memory was not released");
}
}
}
diff --git a/Tests/NFUnitTestSystemLib/NFUnitTestSystemLib.nfproj b/Tests/NFUnitTestSystemLib/NFUnitTestSystemLib.nfproj
index 593d741a..cf3c5a11 100644
--- a/Tests/NFUnitTestSystemLib/NFUnitTestSystemLib.nfproj
+++ b/Tests/NFUnitTestSystemLib/NFUnitTestSystemLib.nfproj
@@ -21,9 +21,13 @@
true
UnitTest
v1.0
+ 13.0
+
+
+
diff --git a/Tests/NFUnitTestSystemLib/RuntimeHelpersTests.cs b/Tests/NFUnitTestSystemLib/RuntimeHelpersTests.cs
new file mode 100644
index 00000000..d02d1c00
--- /dev/null
+++ b/Tests/NFUnitTestSystemLib/RuntimeHelpersTests.cs
@@ -0,0 +1,68 @@
+// 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 nanoFramework.TestFramework;
+
+namespace NFUnitTestSystemLib
+{
+ [TestClass]
+ class RuntimeHelpersTests
+ {
+ [TestMethod]
+ public static void IsReferenceOrContainsReferences()
+ {
+ Assert.IsFalse(RuntimeHelpers.IsReferenceOrContainsReferences());
+ Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences());
+ Assert.IsFalse(RuntimeHelpers.IsReferenceOrContainsReferences());
+ Assert.IsFalse(RuntimeHelpers.IsReferenceOrContainsReferences());
+ Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences());
+ Assert.IsFalse(RuntimeHelpers.IsReferenceOrContainsReferences());
+ Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences());
+ Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences>());
+ Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences>());
+ //Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences());
+ //Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences());
+ }
+
+ private struct StructWithoutReferences
+ {
+ public int a, b, c;
+ }
+
+ private struct StructWithReferences
+ {
+ public int a, b, c;
+ public object d;
+ }
+
+ private ref struct RefStructWithoutReferences
+ {
+ public int a;
+ public long b;
+ }
+
+ private ref struct RefStructWithReferences
+ {
+ public int a;
+ public object b;
+ }
+
+ // TODO: add after checking viability of ref fields in ref structs
+ //private ref struct RefStructWithRef
+ //{
+ // public ref int a;
+
+ // internal RefStructWithRef(ref int aVal)
+ // {
+ // a = ref aVal;
+ // }
+ //}
+
+ //private ref struct RefStructWithNestedRef
+ //{
+ // public Span a;
+ //}
+ }
+}
diff --git a/Tests/NFUnitTestSystemLib/UnitTestGCTest.cs b/Tests/NFUnitTestSystemLib/UnitTestGCTest.cs
index 05c73642..42a3e8f0 100644
--- a/Tests/NFUnitTestSystemLib/UnitTestGCTest.cs
+++ b/Tests/NFUnitTestSystemLib/UnitTestGCTest.cs
@@ -9,6 +9,11 @@ namespace NFUnitTestSystemLib
[TestClass]
public class UnitTestGCTest
{
+#pragma warning disable S1215 // this is intended to test the GC
+#pragma warning disable S1854 // this is intended to test the GC
+#pragma warning disable S2696 // this is intended to test the GC
+#pragma warning disable S3971 // this is intended to test the GC
+
internal class FinalizeObject
{
public static FinalizeObject m_currentInstance = null;
@@ -54,17 +59,20 @@ public void SystemGC1_Test()
/// 6. Verify that object has been collected
///
///
- // Tests ReRegisterForFinalize
- // Create a FinalizeObject.
+
+ OutputHelper.WriteLine("Tests ReRegisterForFinalize");
+ OutputHelper.WriteLine("Create a FinalizeObject.");
+
FinalizeObject mfo = new FinalizeObject();
m_hasFinalized1 = false;
m_hasFinalized2 = false;
// Release reference
+ OutputHelper.WriteLine("Release reference");
mfo = null;
- // Allow GC
- GC.WaitForPendingFinalizers();
+ OutputHelper.WriteLine("Allow GC");
+ GC.Collect();
int sleepTime = 1000;
int slept = 0;
@@ -85,10 +93,10 @@ public void SystemGC1_Test()
// FinalizeObject.m_currentInstance field. Setting this value
// to null and forcing another garbage collection will now
// cause the object to Finalize permanently.
- // Reregister and allow for GC
- FinalizeObject.m_currentInstance = null;
- GC.WaitForPendingFinalizers();
+ OutputHelper.WriteLine("Reregister and allow for GC");
+ FinalizeObject.m_currentInstance = null;
+ GC.Collect();
sleepTime = 1000;
slept = 0;
@@ -119,18 +127,19 @@ public void SystemGC2_Test()
/// 6. Verify that object has not been collected
///
///
- // Tests SuppressFinalize
- // Create a FinalizeObject.
+
+ OutputHelper.WriteLine("Tests SuppressFinalize");
+ OutputHelper.WriteLine("Create a FinalizeObject");
FinalizeObject mfo = new FinalizeObject();
m_hasFinalized1 = false;
m_hasFinalized2 = false;
- // Releasing
+ OutputHelper.WriteLine("Releasing");
GC.SuppressFinalize(mfo);
mfo = null;
- // Allow GC
- GC.WaitForPendingFinalizers();
+ OutputHelper.WriteLine("Allow GC");
+ GC.Collect();
int sleepTime = 1000;
int slept = 0;
@@ -138,7 +147,7 @@ public void SystemGC2_Test()
while (!m_hasFinalized1 && slept < sleepTime)
{
// force GC run caused by memory allocation
- var dummyArray = new byte[1024 * 1024 * 1];
+ _ = new byte[1024 * 1024 * 1];
System.Threading.Thread.Sleep(10);
slept += 10;
@@ -161,59 +170,35 @@ public void SystemGC3_Test()
///
///
- // Tests WaitForPendingFinalizers, dependant on test 1
- // will auto-fail if test 1 fails.
OutputHelper.Write("Tests WaitForPendingFinalizers, dependant on test 1");
- OutputHelper.WriteLine("will auto-fail if test 1 fails.");
+ OutputHelper.WriteLine("will fail if test 1 fails.");
- Assert.IsTrue(m_Test1Result);
+ Assert.IsTrue(m_Test1Result, "Can't run this test as SystemGC1_Test has failed.");
- // Create a FinalizeObject.
+ OutputHelper.WriteLine("Create a FinalizeObject");
FinalizeObject mfo = new FinalizeObject();
m_hasFinalized1 = false;
m_hasFinalized2 = false;
- // Releasing
+ OutputHelper.WriteLine("Releasing");
mfo = null;
- int sleepTime = 1000;
- int slept = 0;
-
- while (!m_hasFinalized1 && slept < sleepTime)
- {
- // force GC run caused by memory allocation
- var dummyArray = new byte[1024 * 1024 * 1];
-
- System.Threading.Thread.Sleep(10);
- slept += 10;
- }
-
- OutputHelper.WriteLine($"GC took {slept}");
-
- // Wait for GC
+ OutputHelper.WriteLine("Wait for GC");
+ GC.Collect();
GC.WaitForPendingFinalizers();
- // Releasing again
+ OutputHelper.WriteLine("Releasing again");
FinalizeObject.m_currentInstance = null;
- sleepTime = 1000;
- slept = 0;
-
- while (!m_hasFinalized2 && slept < sleepTime)
- {
- // force GC run caused by memory allocation
- var dummyArray = new byte[1024 * 1024 * 1];
-
- System.Threading.Thread.Sleep(10);
- slept += 10;
- }
-
- OutputHelper.WriteLine($"GC took {slept}");
-
- // Wait for GC
+ OutputHelper.WriteLine("Wait for GC");
+ GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsTrue(m_hasFinalized2);
}
}
+#pragma warning restore S1215 // "GC.Collect" should not be called
+#pragma warning restore S1854 // Unused assignments should be removed
+#pragma warning restore S2696 // Instance members should not write to "static" fields
+#pragma warning restore S3971 // "GC.SuppressFinalize" should not be called
}
diff --git a/Tests/NFUnitTestSystemLib/UnitTestInitLocalTests.cs b/Tests/NFUnitTestSystemLib/UnitTestInitLocalTests.cs
index fea53af7..a26039e7 100644
--- a/Tests/NFUnitTestSystemLib/UnitTestInitLocalTests.cs
+++ b/Tests/NFUnitTestSystemLib/UnitTestInitLocalTests.cs
@@ -49,6 +49,9 @@ public void SystemType1_GetType_Test()
// ConstructorInfo)
// NOTE: We add the reflection items to the ArrayList to assure that they can be properly
// assigned to a object container (this used to lead to a access violation)
+
+ OutputHelper.WriteLine("Testing Int32");
+
Type type = typeof(int);
list.Add(type);
string name = ((Type)list[i]).Name;
@@ -68,12 +71,16 @@ public void SystemType1_GetType_Test()
//fRes &= name.ToLower() == "mscorlib";
//i++;
+ OutputHelper.WriteLine("Testing NFUnitTestSystemLib.UnitTestInitLocalTests+TestObj");
+
type = Type.GetType("NFUnitTestSystemLib.UnitTestInitLocalTests+TestObj");
list.Add(type);
name = ((Type)list[i]).Name;
Assert.AreEqual(name, "TestObj");
i++;
+ OutputHelper.WriteLine("Testing IEmptyInterface");
+
Type iface = type.GetInterfaces()[0];
list.Add(iface);
name = ((Type)list[i]).Name;
@@ -81,6 +88,8 @@ public void SystemType1_GetType_Test()
Assert.AreEqual(iface.Name, "IEmptyInterface");
i++;
+ OutputHelper.WriteLine("Testing FieldInfo");
+
FieldInfo fi = type.GetField("Field1");
list.Add(fi);
name = ((FieldInfo)list[i]).Name;
@@ -88,6 +97,8 @@ public void SystemType1_GetType_Test()
Assert.AreEqual(fi.Name, "Field1");
i++;
+ OutputHelper.WriteLine("Testing MethodInfo");
+
MethodInfo mi = type.GetMethod("Method1");
list.Add(mi);
name = ((MethodInfo)list[i]).Name;
@@ -95,6 +106,8 @@ public void SystemType1_GetType_Test()
Assert.AreEqual(mi.Name, "Method1");
i++;
+ OutputHelper.WriteLine("Testing ConstructorInfo");
+
ConstructorInfo ci = type.GetConstructor(new Type[] { });
list.Add(ci);
name = ((ConstructorInfo)list[i]).Name;
@@ -104,7 +117,10 @@ public void SystemType1_GetType_Test()
//
// Now test arrays of reflection types
- //
+ //
+
+ OutputHelper.WriteLine("Testing Array of Type");
+
Type[] types = new Type[] { typeof(int), typeof(bool), Type.GetType("NFUnitTestSystemLib.UnitTestInitLocalTests+TestObj") };
list.Add(types[2]);
name = ((Type)list[i]).Name;
@@ -127,6 +143,8 @@ public void SystemType1_GetType_Test()
//fRes &= asms[0].GetName().Name == "Microsoft.SPOT.Platform.Tests.Systemlib2";
//i++;
+ OutputHelper.WriteLine("Testing Array of FieldInfo");
+
FieldInfo[] fis = new FieldInfo[] { types[2].GetField("Field1"), type.GetFields()[0] };
list.Add(fis[0]);
name = ((FieldInfo)list[i]).Name;
@@ -134,6 +152,8 @@ public void SystemType1_GetType_Test()
Assert.AreEqual(fis[0].Name, "Field1");
i++;
+ OutputHelper.WriteLine("Testing Array of MethodInfo");
+
MethodInfo[] mis = new MethodInfo[] { type.GetMethods()[2], types[2].GetMethod("Method2", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) };
list.Add(mis[1]);
name = ((MethodInfo)list[i]).Name;
@@ -141,6 +161,8 @@ public void SystemType1_GetType_Test()
Assert.AreEqual(mis[1].Name, "Method2");
i++;
+ OutputHelper.WriteLine("Testing Array of ConstructorInfo");
+
ConstructorInfo[] cis = new ConstructorInfo[] { types[2].GetConstructor(new Type[] { }), typeof(TestObj).GetConstructor(new Type[] { typeof(int) }) };
list.Add(cis[0]);
name = ((ConstructorInfo)list[i]).Name;
@@ -148,6 +170,8 @@ public void SystemType1_GetType_Test()
Assert.AreEqual(cis[0].Name, ".ctor");
i++;
+ OutputHelper.WriteLine("Testing Array of System.Collections.ArrayList");
+
Array ar = Array.CreateInstance(typeof(Type), 3);
((IList)ar)[0] = typeof(Type);
((IList)ar)[1] = Type.GetType("System.Collections.ArrayList");
@@ -157,7 +181,6 @@ public void SystemType1_GetType_Test()
Assert.AreEqual(name, "ArrayList");
Assert.AreEqual(((Type)((IList)ar)[0]).Name, "Type");
Assert.AreEqual(((Type)ar.GetValue(1)).Name, "ArrayList");
- i++;
list.Clear();
}
@@ -192,14 +215,24 @@ public void SystemType1_ArrayListToArrayForStruct_Test()
// make sure boxing of struct value type (Guid) is handled properly
// this test was a result of a bug found by a customer.
Guid ret = new Guid();
+
ArrayList guidList = new ArrayList();
guidList.Add(Guid.NewGuid());
guidList.Add(Guid.NewGuid());
+
Guid[] guidArray = (Guid[])guidList.ToArray(typeof(Guid));
+ // Verify the array has the correct length
+ Assert.AreEqual(2, guidArray.Length);
+
+ // Verify each element is not empty and matches the source list
+ int i = 0;
foreach (Guid g in guidArray)
{
+ Assert.IsFalse(Guid.Empty.Equals(g), "Guid should not be empty");
+ Assert.AreEqual(guidList[i], g, "Guid should match the source ArrayList element");
ret = g;
+ i++;
}
}
diff --git a/Tests/NFUnitTestSystemLib/UnitTestMemoryExtensions.cs b/Tests/NFUnitTestSystemLib/UnitTestMemoryExtensions.cs
new file mode 100644
index 00000000..757623bb
--- /dev/null
+++ b/Tests/NFUnitTestSystemLib/UnitTestMemoryExtensions.cs
@@ -0,0 +1,238 @@
+// 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 nanoFramework.TestFramework;
+
+namespace NFUnitTestSystemLib
+{
+ [TestClass]
+ public class UnitTestMemoryExtensions
+ {
+ [TestMethod]
+ public void CopyTo_WithValidArray_ShouldCopyAllElements()
+ {
+ // Arrange
+ byte[] source = new byte[] { 1, 2, 3, 4, 5 };
+ Span destination = new Span(new byte[5]);
+
+ // Act
+ source.CopyTo(destination);
+
+ // Assert
+ for (byte i = 0; i < source.Length; i++)
+ {
+ Assert.AreEqual(source[i], destination[i], $"Element at index {i} should match");
+ }
+ }
+
+ [TestMethod]
+ public void CopyTo_WithLargerDestination_ShouldCopyAllElements()
+ {
+ // Arrange
+ int[] source = new int[] { 10, 20, 30 };
+ Span destination = new Span(new int[5]);
+
+ // Act
+ source.CopyTo(destination);
+
+ // Assert
+ for (int i = 0; i < source.Length; i++)
+ {
+ Assert.AreEqual(source[i], destination[i], $"Element at index {i} should match");
+ }
+
+ // Verify remaining elements are default
+ for (int i = source.Length; i < destination.Length; i++)
+ {
+ Assert.AreEqual(0, destination[i], $"Element at index {i} should be default value");
+ }
+ }
+
+ [TestMethod]
+ public void CopyTo_WithSmallerDestination_ShouldThrowArgumentException()
+ {
+ // Arrange, Act & Assert
+ Assert.ThrowsException(typeof(ArgumentException), () =>
+ {
+ int[] source = new int[] { 1, 2, 3, 4, 5 };
+ Span destination = new Span(new int[3]);
+
+ source.CopyTo(destination);
+ });
+ }
+
+ [TestMethod]
+ public void CopyTo_WithNullArray_ShouldCopyAsEmpty()
+ {
+ // Arrange
+ int[] source = null;
+ Span destination = new Span(new int[5]);
+
+ // Act
+ source.CopyTo(destination);
+
+ // Assert - all elements should remain at default value (0)
+ for (int i = 0; i < destination.Length; i++)
+ {
+ Assert.AreEqual(0, destination[i], $"Element at index {i} should be default value");
+ }
+ }
+
+ [TestMethod]
+ public void CopyTo_WithEmptyArray_ShouldNotModifyDestination()
+ {
+ // Arrange
+ int[] source = new int[0];
+ int[] destArray = new int[5] { 1, 2, 3, 4, 5 };
+ Span destination = new Span(destArray);
+
+ // Act
+ source.CopyTo(destination);
+
+ // Assert - destination should remain unchanged
+ for (int i = 0; i < destArray.Length; i++)
+ {
+ Assert.AreEqual(i + 1, destination[i], $"Element at index {i} should remain unchanged");
+ }
+ }
+
+ [TestMethod]
+ public void CopyTo_WithByteArray_ShouldCopyCorrectly()
+ {
+ // Arrange
+ byte[] source = new byte[] { 0x01, 0x02, 0x03, 0x04 };
+ Span destination = new Span(new byte[4]);
+
+ // Act
+ source.CopyTo(destination);
+
+ // Assert
+ for (int i = 0; i < source.Length; i++)
+ {
+ Assert.AreEqual(source[i], destination[i], $"Byte at index {i} should match");
+ }
+ }
+
+ [TestMethod]
+ public void CopyTo_WithStringArray_ShouldCopyReferences()
+ {
+ // Arrange
+ string[] source = new string[] { "hello", "world", "test" };
+ Span destination = new Span(new string[3]);
+
+ // Act
+ source.CopyTo(destination);
+
+ // Assert
+ for (int i = 0; i < source.Length; i++)
+ {
+ Assert.AreSame(source[i], destination[i], $"String reference at index {i} should be the same");
+ }
+ }
+
+ [TestMethod]
+ public void CopyTo_WithValueTypeArray_ShouldCopyValues()
+ {
+ // Arrange
+ double[] source = new double[] { 1.5, 2.5, 3.5 };
+ Span destination = new Span(new double[3]);
+
+ // Act
+ source.CopyTo(destination);
+
+ // Assert
+ for (int i = 0; i < source.Length; i++)
+ {
+ Assert.AreEqual(source[i], destination[i], $"Double at index {i} should match");
+ }
+ }
+
+ [TestMethod]
+ public void CopyTo_WithCustomStruct_ShouldCopyStructValues()
+ {
+ // Arrange
+ TestStruct[] source = new TestStruct[]
+ {
+ new TestStruct { Value = 10, Name = "First" },
+ new TestStruct { Value = 20, Name = "Second" },
+ new TestStruct { Value = 30, Name = "Third" }
+ };
+ Span destination = new Span(new TestStruct[3]);
+
+ // Act
+ source.CopyTo(destination);
+
+ // Assert
+ for (int i = 0; i < source.Length; i++)
+ {
+ Assert.AreEqual(source[i].Value, destination[i].Value, $"Struct Value at index {i} should match");
+ Assert.AreEqual(source[i].Name, destination[i].Name, $"Struct Name at index {i} should match");
+ }
+ }
+
+ [TestMethod]
+ public void CopyTo_MultipleConsecutiveCalls_ShouldWorkCorrectly()
+ {
+ // Arrange
+ int[] source1 = new int[] { 1, 2, 3 };
+ int[] source2 = new int[] { 4, 5, 6 };
+ Span destination = new Span(new int[6]);
+
+ // Act
+ source1.CopyTo(destination);
+ source2.CopyTo(destination.Slice(3));
+
+ // Assert
+ int[] expected = new int[] { 1, 2, 3, 4, 5, 6 };
+ for (int i = 0; i < expected.Length; i++)
+ {
+ Assert.AreEqual(expected[i], destination[i], $"Element at index {i} should match");
+ }
+ }
+
+ [TestMethod]
+ public void CopyTo_WithSingleElementArray_ShouldCopy()
+ {
+ // Arrange
+ int[] source = new int[] { 42 };
+ Span destination = new Span(new int[1]);
+
+ // Act
+ source.CopyTo(destination);
+
+ // Assert
+ Assert.AreEqual(42, destination[0], "Single element should be copied");
+ }
+
+ [TestMethod]
+ public void CopyTo_WithLargeArray_ShouldCopyAllElements()
+ {
+ // Arrange
+ int size = 1000;
+ int[] source = new int[size];
+ for (int i = 0; i < size; i++)
+ {
+ source[i] = i;
+ }
+
+ Span destination = new Span(new int[size]);
+
+ // Act
+ source.CopyTo(destination);
+
+ // Assert
+ for (int i = 0; i < size; i++)
+ {
+ Assert.AreEqual(source[i], destination[i], $"Element at index {i} should match");
+ }
+ }
+
+ // Helper struct for testing
+ private struct TestStruct
+ {
+ public int Value { get; set; }
+ public string Name { get; set; }
+ }
+ }
+}
diff --git a/Tests/NFUnitTestSystemLib/UnitTestNullable.cs b/Tests/NFUnitTestSystemLib/UnitTestNullable.cs
new file mode 100644
index 00000000..74a98741
--- /dev/null
+++ b/Tests/NFUnitTestSystemLib/UnitTestNullable.cs
@@ -0,0 +1,266 @@
+// 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 nanoFramework.TestFramework;
+
+namespace NFUnitTestSystemLib
+{
+ [TestClass]
+ public class UnitTestNullable
+ {
+ [TestMethod]
+ public void Ctor_Empty()
+ {
+ // Nullable and Nullable are mostly verbatim ports so we don't test much here.
+
+ int? n = default(int?);
+ Assert.IsFalse(n.HasValue);
+
+ // TODO replace with Assert.ThrowsException when available
+ Assert.ThrowsException(
+ typeof(InvalidOperationException),
+ () => _ = n.Value);
+ Assert.ThrowsException(
+ typeof(InvalidOperationException),
+ () => _ = (int)n);
+
+ Assert.IsNull(n);
+ Assert.AreNotEqual(7, n);
+ Assert.AreEqual(0, n.GetHashCode());
+ Assert.AreEqual("", n.ToString());
+ Assert.AreEqual(default(int), n.GetValueOrDefault());
+ Assert.AreEqual(999, n.GetValueOrDefault(999));
+
+ n = new int?(42);
+ Assert.IsTrue(n.HasValue);
+ Assert.AreEqual(42, n.Value);
+ Assert.AreEqual(42, (int)n);
+ Assert.IsNotNull(n);
+ Assert.AreNotEqual(7, n);
+ Assert.AreEqual(42, n);
+ Assert.AreEqual(42.GetHashCode(), n.GetHashCode());
+ Assert.AreEqual(42.ToString(), n.ToString());
+ Assert.AreEqual(42, n.GetValueOrDefault());
+ Assert.AreEqual(42, n.GetValueOrDefault(999));
+
+ n = 88;
+ Assert.IsTrue(n.HasValue);
+ Assert.AreEqual(88, n.Value);
+ }
+
+ [TestMethod]
+ public static void Boxing()
+ {
+ int? n = new int?(42);
+ Unbox(n);
+ }
+
+ private static void Unbox(object o)
+ {
+ Type t = o.GetType();
+
+ // TOOD: replace with Assert.IsNotType(t); when available
+ Assert.IsNotInstanceOfType(typeof(int?), t);
+
+ Assert.AreEqual(typeof(int), t);
+ }
+
+ [TestMethod]
+ public static void ExplicitCast_T()
+ {
+ int? nullable = 5;
+ int value = (int)nullable;
+ Assert.AreEqual(5, value);
+
+ nullable = null;
+ // TODO replace with Assert.Throws(() => (int)nullable); when available
+ Assert.ThrowsException(
+ typeof(InvalidOperationException),
+ () => _ = (int)nullable);
+ }
+
+ [TestMethod]
+ public static void GetUnderlyingType()
+ {
+ Assert.AreEqual(typeof(int), Nullable.GetUnderlyingType(typeof(int?)));
+ Assert.AreEqual(null, Nullable.GetUnderlyingType(typeof(int)));
+ Assert.AreEqual(null, Nullable.GetUnderlyingType(typeof(G)));
+ }
+
+ [TestMethod]
+ public static void GetUnderlyingType_NullType_ThrowsArgumentNullException()
+ {
+ // TODO replace with Assert.Throws("nullableType", () => Nullable.GetUnderlyingType(null)); when available
+ Assert.ThrowsException(
+ typeof(ArgumentNullException),
+ () => Nullable.GetUnderlyingType(null)
+ );
+ }
+
+ // TODO: Uncomment when available
+ //[TestMethod]
+ //public static void GetValueRefOrDefaultRef_WithValue()
+ //{
+ // static void Test(T before, T after)
+ // where T : struct
+ // {
+ // T? nullable = before;
+ // ref readonly T reference = ref Nullable.GetValueRefOrDefaultRef(in nullable);
+
+ // Assert.AreEqual(before, nullable!.Value);
+
+ // Unsafe.AsRef(in reference) = after;
+
+ // Assert.Equal(after, nullable.Value);
+ // }
+
+ // Test((byte)0, (byte)42);
+ // Test(0, 42);
+ // Test(1.3f, 3.14f);
+ // Test(0.555, 8.49);
+ // Test(Guid.NewGuid(), Guid.NewGuid());
+ //}
+
+ // TODO: Uncomment when available
+ //[TestMethod]
+ //public static void GetValueRefOrDefaultRef_WithDefault()
+ //{
+ // static void Test()
+ // where T : struct
+ // {
+ // T? nullable = null;
+ // ref readonly T reference = ref Nullable.GetValueRefOrDefaultRef(in nullable);
+
+ // Assert.Equal(nullable!.GetValueOrDefault(), reference);
+ // }
+
+ // Test();
+ // Test();
+ // Test();
+ // Test();
+ // Test();
+ //}
+
+ // TODO: Uncomment when available
+ //[TestMethod]
+ //public static void GetValueRefOrDefaultRef_UnsafeWriteToNullMaintainsExpectedBehavior()
+ //{
+ // static void Test(T after)
+ // where T : struct
+ // {
+ // T? nullable = null;
+ // ref readonly T reference = ref Nullable.GetValueRefOrDefaultRef(in nullable);
+
+ // Unsafe.AsRef(in reference) = after;
+
+ // Assert.Equal(after, nullable.GetValueOrDefault()); // GetValueOrDefault() unconditionally returns the field
+ // Assert.False(nullable.HasValue);
+ // Assert.Equal(0, nullable.GetHashCode()); // GetHashCode() returns 0 if HasValue is false, without reading the field
+ // Assert.Throws(() => nullable.Value); // Accessing the value should still throw despite the write
+ // Assert.Throws(() => (T)nullable);
+ // }
+
+ // Test((byte)42);
+ // Test(42);
+ // Test(3.14f);
+ // Test(8.49);
+ // Test(Guid.NewGuid());
+ //}
+
+ [TestMethod]
+ public static void Compare_Equals()
+ {
+ // Case 1: (null, null, 0)
+ int? n1 = null;
+ int? n2 = null;
+ int expected = 0;
+ Assert.AreEqual(expected == 0, Nullable.Equals(n1, n2));
+ Assert.AreEqual(expected == 0, n1.Equals(n2));
+ // TODO Assert.AreEqual(expected, Nullable.Compare(n1, n2));
+
+ // Case 2: (7, null, 1)
+ n1 = 7;
+ n2 = null;
+ expected = 1;
+ Assert.AreEqual(expected == 0, Nullable.Equals(n1, n2));
+ Assert.AreEqual(expected == 0, n1.Equals(n2));
+ // TODO Assert.AreEqual(expected, Nullable.Compare(n1, n2));
+
+ // Case 3: (null, 7, -1)
+ n1 = null;
+ n2 = 7;
+ expected = -1;
+ Assert.AreEqual(expected == 0, Nullable.Equals(n1, n2));
+ Assert.AreEqual(expected == 0, n1.Equals(n2));
+ // TODO Assert.AreEqual(expected, Nullable.Compare(n1, n2));
+
+ // Case 4: (7, 7, 0)
+ n1 = 7;
+ n2 = 7;
+ expected = 0;
+ Assert.AreEqual(expected == 0, Nullable.Equals(n1, n2));
+ Assert.AreEqual(expected == 0, n1.Equals(n2));
+ // TODO Assert.AreEqual(expected, Nullable.Compare(n1, n2));
+
+ // Case 5: (7, 5, 1)
+ n1 = 7;
+ n2 = 5;
+ expected = 1;
+ Assert.AreEqual(expected == 0, Nullable.Equals(n1, n2));
+ Assert.AreEqual(expected == 0, n1.Equals(n2));
+ // TODO Assert.AreEqual(expected, Nullable.Compare(n1, n2));
+
+ // Case 6: (5, 7, -1)
+ n1 = 5;
+ n2 = 7;
+ expected = -1;
+ Assert.AreEqual(expected == 0, Nullable.Equals(n1, n2));
+ Assert.AreEqual(expected == 0, n1.Equals(n2));
+ // TODO Assert.AreEqual(expected, Nullable.Compare(n1, n2));
+ }
+
+ //[TestMethod]
+ //public static void MutatingMethods_MutationsAffectOriginal()
+ //{
+ // MutatingStruct? ms = new MutatingStruct() { Value = 1 };
+
+ // for (int i = 1; i <= 2; i++)
+ // {
+ // Assert.AreEqual(i.ToString(), ms.Value.ToString());
+ // Assert.AreEqual(i, ms.Value.Value);
+
+ // Assert.AreEqual(i.ToString(), ms.ToString());
+ // Assert.AreEqual(i + 1, ms.Value.Value);
+ // }
+
+ // for (int i = 3; i <= 4; i++)
+ // {
+ // Assert.AreEqual(i, ms.Value.GetHashCode());
+ // Assert.AreEqual(i, ms.Value.Value);
+
+ // Assert.AreEqual(i, ms.GetHashCode());
+ // Assert.AreEqual(i + 1, ms.Value.Value);
+ // }
+
+ // for (int i = 5; i <= 6; i++)
+ // {
+ // ms.Value.Equals(new object());
+ // Assert.AreEqual(i, ms.Value.Value);
+
+ // ms.Equals(new object());
+ // Assert.AreEqual(i + 1, ms.Value.Value);
+ // }
+ //}
+
+ //private struct MutatingStruct
+ //{
+ // public int Value;
+ // public override string ToString() => Value++.ToString();
+ // public override bool Equals(object obj) => Value++.Equals(null);
+ // public override int GetHashCode() => Value++.GetHashCode();
+ //}
+
+ public class G { }
+ }
+}
diff --git a/Tests/NFUnitTestSystemLib/UnitTestReflectionTypeTest.cs b/Tests/NFUnitTestSystemLib/UnitTestReflectionTypeTest.cs
index 709f2122..805c2b93 100644
--- a/Tests/NFUnitTestSystemLib/UnitTestReflectionTypeTest.cs
+++ b/Tests/NFUnitTestSystemLib/UnitTestReflectionTypeTest.cs
@@ -5,6 +5,7 @@
using System.Collections;
using System.Reflection;
using nanoFramework.TestFramework;
+using static NFUnitTestSystemLib.UnitTestNullable;
namespace NFUnitTestSystemLib
{
@@ -52,85 +53,103 @@ public void SystemReflectionType_RuntimeType_Test1()
///
TestClass cls = new TestClass();
+ OutputHelper.WriteLine("Testing Type members for a class type");
+
// Test Type members for a class type
Type t = cls.GetType();
Assembly asm = t.Assembly;
list.Add(asm);
- Assert.AreEqual(((Assembly)list[i]).GetName().Name, "NFUnitTest");
- Assert.AreEqual(asm.GetName().Name, "NFUnitTest");
- Assert.AreEqual(t.Name, "TestClass");
- Assert.AreEqual(t.FullName, "NFUnitTestSystemLib.UnitTestReflectionTypeTest+TestClass");
+ Assert.AreEqual("NFUnitTest", ((Assembly)list[i]).GetName().Name);
+ Assert.AreEqual("NFUnitTest", asm.GetName().Name);
+ Assert.AreEqual("TestClass", t.Name);
+ Assert.AreEqual("NFUnitTestSystemLib.UnitTestReflectionTypeTest+TestClass", t.FullName);
Assert.IsInstanceOfType(t.BaseType, typeof(object));
Assert.IsNull(t.GetElementType());
+ OutputHelper.WriteLine("Testing methods of class type");
+
MethodInfo[] mis = t.GetMethods();
- Assert.AreEqual(mis[0].Name, "Method1");
+ Assert.AreEqual("Method1", mis[0].Name);
mis = t.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic);
- Assert.AreEqual(mis[0].Name, "Method2");
+ Assert.AreEqual("Method2", mis[0].Name);
Assert.IsNotNull(t.GetMethod("Method1"));
Assert.IsNotNull(t.GetMethod("Method2", BindingFlags.Instance | BindingFlags.NonPublic));
+ OutputHelper.WriteLine("Testing fields of class type");
+
FieldInfo[] fis = t.GetFields();
- Assert.AreEqual(fis[0].Name, "m_Field1");
+ Assert.AreEqual("m_Field1", fis[0].Name);
fis = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
- Assert.AreEqual(fis[0].Name, "m_Field2");
+ Assert.AreEqual("m_Field2", fis[0].Name);
Assert.IsNotNull(t.GetField("m_Field1"));
Assert.IsNotNull(t.GetField("m_Field2", BindingFlags.NonPublic | BindingFlags.Instance));
Assert.IsNotNull(t.GetConstructor(new Type[] { }));
+ OutputHelper.WriteLine("Testing interfaces of class type");
+
Type[] ifaces = t.GetInterfaces();
- Assert.AreEqual(ifaces.Length, 2);
- Assert.AreEqual(ifaces[0].Name, "IInterface1");
- Assert.AreEqual(ifaces[1].Name, "IInterface2");
+ Assert.AreEqual(2, ifaces.Length);
+ Assert.AreEqual("IInterface1", ifaces[0].Name);
+ Assert.AreEqual("IInterface2", ifaces[1].Name);
Assert.IsTrue(t.IsSubclassOf(typeof(object)));
i++;
+ OutputHelper.WriteLine("Testing Type members for a struct valuetype");
+
// test Type members for a struct valuetype
TestStruct str = new TestStruct();
t = str.GetType();
asm = t.Assembly;
list.Add(asm);
- Assert.AreEqual(((Assembly)list[i]).GetName().Name, "NFUnitTest");
- Assert.AreEqual(asm.GetName().Name, "NFUnitTest");
- Assert.AreEqual(t.Name, "TestStruct");
- Assert.AreEqual(t.FullName, "NFUnitTestSystemLib.UnitTestReflectionTypeTest+TestStruct");
+ Assert.AreEqual("NFUnitTest", ((Assembly)list[i]).GetName().Name);
+ Assert.AreEqual("NFUnitTest", asm.GetName().Name);
+ Assert.AreEqual("TestStruct", t.Name);
+ Assert.AreEqual("NFUnitTestSystemLib.UnitTestReflectionTypeTest+TestStruct", t.FullName);
Assert.IsInstanceOfType(t.BaseType, typeof(ValueType));
- Assert.AreEqual(t.GetInterfaces().Length, 0);
+ Assert.AreEqual(0, t.GetInterfaces().Length);
Assert.IsNull(t.GetElementType());
i++;
+ OutputHelper.WriteLine("Testing Type members for an Assembly reflection type");
+
// test Type members for an Assembly reflection type
//Assembly asmObj = typeof(TestClass).Assembly;
//t = asmObj.GetType();
//asm = t.Assembly;
//list.Add(asm);
- //Assert.AreEqual(((Assembly)list[i]).GetName().Name, "mscorlib");
- //Assert.AreEqual(asm.GetName().Name, "mscorlib");
- //Assert.AreEqual(t.Name, "Assembly");
- //Assert.AreEqual(t.FullName, "System.Reflection.Assembly");
+ //Assert.AreEqual("mscorlib", ((Assembly)list[i]).GetName().Name);
+ //Assert.AreEqual("mscorlib", asm.GetName().Name);
+ //Assert.AreEqual("Assembly", t.Name);
+ //Assert.AreEqual("System.Reflection.Assembly", t.FullName);
//Assert.IsInstanceOfType(t.BaseType, typeof(Object));
- //Assert.AreEqual(t.GetInterfaces().Length, 0);
+ //Assert.AreEqual(0, t.GetInterfaces().Length);
//Assert.IsNull(t.GetElementType());
+ OutputHelper.WriteLine("Testing Type members for a MethodInfo reflection type");
+
mis = typeof(TestClass).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
t = mis.GetType();
- Assert.AreEqual(t.Name, "RuntimeMethodInfo[]");
- Assert.AreEqual(t.FullName, "System.Reflection.RuntimeMethodInfo[]");
+ Assert.AreEqual("RuntimeMethodInfo[]", t.Name);
+ Assert.AreEqual("System.Reflection.RuntimeMethodInfo[]", t.FullName);
Assert.IsInstanceOfType(t.BaseType, typeof(Array));
Assert.IsTrue(t.GetInterfaces().Length > 0);
- Assert.AreEqual(t.GetElementType().Name, "RuntimeMethodInfo");
+ Assert.AreEqual("RuntimeMethodInfo", t.GetElementType().Name);
+
+ OutputHelper.WriteLine("Testing Type members for a delegate");
// test Type members for a delegate
Delegate del = new MyDelegate(MyDelegateImpl);
t = del.GetType();
Assert.IsNotNull(t.DeclaringType);
- Assert.AreEqual(t.Name, "MyDelegate");
+ Assert.AreEqual("MyDelegate", t.Name);
Assert.IsInstanceOfType(t.BaseType, typeof(MulticastDelegate));
+ OutputHelper.WriteLine("Testing Type members for an enum");
+
// test Type members for an enum
TestEnum en = TestEnum.Item1;
t = en.GetType();
- Assert.IsInstanceOfType(t.DeclaringType, typeof(UnitTestReflectionTypeTest));
+ Assert.IsInstanceOfType(typeof(UnitTestReflectionTypeTest), t.DeclaringType);
Assert.IsTrue(t.IsEnum);
Assert.IsFalse(t.IsAbstract);
Assert.IsFalse(t.IsClass);
@@ -145,10 +164,11 @@ public void SystemReflectionType_SystemType_Test2()
Assert.IsTrue(typeof(Array).IsInstanceOfType(blah));
Assert.IsTrue(typeof(TestStruct[]).IsArray);
+ Assert.IsTrue(typeof(G[]).IsArray);
Assert.IsFalse(typeof(Array).IsValueType);
Assert.IsTrue(typeof(TestStruct).IsValueType);
Assert.IsTrue(typeof(Type).IsSubclassOf(typeof(MemberInfo)));
- Assert.AreEqual(typeof(Type).GetInterfaces()[0].Name, "IReflect");
+ Assert.AreEqual("IReflect", typeof(Type).GetInterfaces()[0].Name);
Assert.IsTrue(typeof(MyDelegate).IsInstanceOfType(new MyDelegate(MyDelegateImpl)));
// Get known type from assembly qualified type name Culture and PublicKeyToken are used by debugger to identify types
diff --git a/Tests/NFUnitTestTypes/NFUnitTestTypes.nfproj b/Tests/NFUnitTestTypes/NFUnitTestTypes.nfproj
index 7ede25bb..f6f14b4a 100644
--- a/Tests/NFUnitTestTypes/NFUnitTestTypes.nfproj
+++ b/Tests/NFUnitTestTypes/NFUnitTestTypes.nfproj
@@ -24,7 +24,9 @@
+
+
@@ -47,4 +49,4 @@
-
+
\ No newline at end of file
diff --git a/Tests/NFUnitTestTypes/UnitTestGuid.cs b/Tests/NFUnitTestTypes/UnitTestGuid.cs
new file mode 100644
index 00000000..886207cb
--- /dev/null
+++ b/Tests/NFUnitTestTypes/UnitTestGuid.cs
@@ -0,0 +1,140 @@
+// 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 nanoFramework.TestFramework;
+
+namespace NFUnitTestTypes
+{
+ [TestClass]
+ class UnitTestGuid
+ {
+ [TestMethod]
+ public void Guid_Compare_To_Empty()
+ {
+ var empty = Guid.Empty;
+ var notEmpty = Guid.NewGuid();
+ Assert.IsFalse(empty == notEmpty);
+ }
+
+ [TestMethod]
+ public void Guid_Empty_IsAllZeros()
+ {
+ var empty = Guid.Empty;
+ var bytes = empty.ToByteArray();
+ foreach (var b in bytes)
+ {
+ Assert.AreEqual((byte)0, b);
+ }
+ }
+
+ [TestMethod]
+ public void Guid_Constructor_ByteArray_RoundTrip()
+ {
+ var original = Guid.NewGuid();
+ var bytes = original.ToByteArray();
+ var roundTrip = new Guid(bytes);
+ Assert.AreEqual(original, roundTrip);
+ }
+
+ [TestMethod]
+ public void Guid_Equals_And_Operator()
+ {
+ var g1 = Guid.NewGuid();
+ var g2 = new Guid(g1.ToByteArray());
+ Assert.IsTrue(g1.Equals(g2));
+ Assert.IsTrue(g1 == g2);
+ Assert.IsFalse(g1 != g2);
+ }
+
+ [TestMethod]
+ public void Guid_NotEquals_And_Operator()
+ {
+ var g1 = Guid.NewGuid();
+ var g2 = Guid.NewGuid();
+ Assert.IsFalse(g1.Equals(g2));
+ Assert.IsFalse(g1 == g2);
+ Assert.IsTrue(g1 != g2);
+ }
+
+ [TestMethod]
+ public void Guid_ToString_And_Parse()
+ {
+ var g1 = Guid.NewGuid();
+ var str = g1.ToString();
+ var g2 = new Guid(str);
+ Assert.AreEqual(g1, g2);
+ }
+
+ [TestMethod]
+ public void Guid_GetHashCode_Consistent()
+ {
+ var g1 = Guid.NewGuid();
+ var g2 = new Guid(g1.ToByteArray());
+ Assert.AreEqual(g1.GetHashCode(), g2.GetHashCode());
+ }
+
+ [TestMethod]
+ public void Guid_CompareTo_Object_And_Self()
+ {
+ var g1 = Guid.NewGuid();
+ var g2 = new Guid(g1.ToByteArray());
+ Assert.AreEqual(0, g1.CompareTo(g2));
+ Assert.AreEqual(0, g1.CompareTo((object)g2));
+ Assert.AreEqual(1, g1.CompareTo(null));
+ }
+
+ [TestMethod]
+ public void Guid_CompareTo_InvalidType_Throws()
+ {
+ var g1 = Guid.NewGuid();
+ Assert.ThrowsException(typeof(ArgumentException), () =>
+ {
+ g1.CompareTo("not a guid");
+ });
+ }
+
+ [TestMethod]
+ public void Guid_TryParseGuidWithDashes_Valid()
+ {
+ var g1 = Guid.NewGuid();
+ var str = g1.ToString();
+ bool parsed = Guid.TryParse(str, out var g2);
+ Assert.IsTrue(parsed);
+ Assert.AreEqual(g1, g2);
+ }
+
+ [TestMethod]
+ public void Guid_TryParseGuidWithDashes_Invalid()
+ {
+ bool parsed = Guid.TryParse("invalid-guid", out var g2);
+ Assert.IsFalse(parsed);
+ Assert.AreEqual(Guid.Empty, g2);
+ }
+
+ [TestMethod]
+ public void Guid_Constructor_String_WithBraces()
+ {
+ var g1 = Guid.NewGuid();
+ var str = "{" + g1.ToString() + "}";
+ var g2 = new Guid(str);
+ Assert.AreEqual(g1, g2);
+ }
+
+ [TestMethod]
+ public void Guid_Constructor_String_Invalid_Throws()
+ {
+ Assert.ThrowsException(typeof(ArgumentException), () =>
+ {
+ var g = new Guid("invalid-guid");
+ });
+ }
+
+ [TestMethod]
+ public void Guid_GetHashCode_Empty()
+ {
+ var empty = Guid.Empty;
+ Assert.AreEqual(0, empty.GetHashCode());
+ }
+ }
+}
diff --git a/Tests/NFUnitTestTypes/UnitTestsReadOnlySpanByte.cs b/Tests/NFUnitTestTypes/UnitTestsReadOnlySpanByte.cs
new file mode 100644
index 00000000..dfd939af
--- /dev/null
+++ b/Tests/NFUnitTestTypes/UnitTestsReadOnlySpanByte.cs
@@ -0,0 +1,988 @@
+// 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 nanoFramework.TestFramework;
+
+namespace NFUnitTestTypes
+{
+ [TestClass]
+ public class UnitTestsReadOnlySpanByte
+ {
+ [TestMethod]
+ public void EmptySpanTests()
+ {
+ // Empty span using default constructor
+ ReadOnlySpan span = new ReadOnlySpan();
+
+ Assert.AreEqual(0, span.Length, "Empty ReadOnlySpan should have length of 0");
+ Assert.IsTrue(span.IsEmpty, "Empty ReadOnlySpan should be IsEmpty");
+
+ // Empty span from null array
+ span = new ReadOnlySpan(null);
+
+ Assert.AreEqual(0, span.Length, "ReadOnlySpan from null should have length of 0");
+ Assert.IsTrue(span.IsEmpty, "ReadOnlySpan from null should be IsEmpty");
+
+ // Empty span using Empty property
+ span = ReadOnlySpan.Empty;
+
+ Assert.AreEqual(0, span.Length, "Empty ReadOnlySpan should have length of 0");
+ Assert.IsTrue(span.IsEmpty, "Empty ReadOnlySpan should be IsEmpty");
+ }
+
+ [TestMethod]
+ public void RaisingExceptionsOfAllKindsTests()
+ {
+ // Should raise an exception on creation
+ Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { ReadOnlySpan span = new ReadOnlySpan(null, 1, 2); }, "ArgumentOutOfRangeException when array is null, start is 1 and length is 2");
+ Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { ReadOnlySpan span = new ReadOnlySpan(new byte[1], 1, 2); }, "ArgumentOutOfRangeException when array is new byte[1], start is 1 and length is 2");
+ Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { ReadOnlySpan span = new ReadOnlySpan(new byte[1], 0, 2); }, "ArgumentOutOfRangeException when array is new byte[1], start is 0 and length is 2");
+ Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { ReadOnlySpan span = new ReadOnlySpan(new byte[1], 2, 0); }, "ArgumentOutOfRangeException when array is new byte[1], start is 2 and length is 0");
+
+ // Exception on index access
+ byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ ReadOnlySpan span = new ReadOnlySpan(array);
+ _ = span[span.Length];
+ }, "IndexOutOfRangeException when accessing index equal to Length");
+
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ ReadOnlySpan span = new ReadOnlySpan(array);
+ _ = span[-1];
+ }, "IndexOutOfRangeException when accessing negative index");
+
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ ReadOnlySpan span = new ReadOnlySpan(array);
+ _ = span[span.Length + 1];
+ }, "IndexOutOfRangeException when accessing index beyond Length");
+
+ // CopyTo with too small destination
+ Assert.ThrowsException(typeof(ArgumentException), () =>
+ {
+ ReadOnlySpan span = new ReadOnlySpan(array);
+ Span destination = new Span(new byte[span.Length - 1]);
+
+ span.CopyTo(destination);
+ });
+
+ // Slicing arguments
+ Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () =>
+ {
+ ReadOnlySpan span = new ReadOnlySpan(array);
+ _ = span.Slice(span.Length + 1);
+ });
+
+ Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () =>
+ {
+ ReadOnlySpan span = new ReadOnlySpan(array);
+ _ = span.Slice(1, span.Length);
+ });
+
+ Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () =>
+ {
+ ReadOnlySpan span = new ReadOnlySpan(array);
+ _ = span.Slice(-1, span.Length);
+ });
+
+ Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () =>
+ {
+ ReadOnlySpan span = new ReadOnlySpan(array);
+ _ = span.Slice(1, -1);
+ });
+ }
+
+ [TestMethod]
+ public void IndexOutOfRangeExceptionTests()
+ {
+ // Comprehensive tests for IndexOutOfRangeException in the indexer
+ byte[] array = new byte[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ ReadOnlySpan span = new ReadOnlySpan(array);
+
+ // Test reading at exact boundary
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ _ = GetReadOnlySpanElement(array, 10);
+ }, "Should throw IndexOutOfRangeException at index equal to Length");
+
+ // Test reading beyond boundary
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ _ = GetReadOnlySpanElement(array, 100);
+ }, "Should throw IndexOutOfRangeException at large index");
+
+ // Test negative index
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ _ = GetReadOnlySpanElement(array, -1);
+ }, "Should throw IndexOutOfRangeException at negative index");
+
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ _ = GetReadOnlySpanElement(array, -100);
+ }, "Should throw IndexOutOfRangeException at large negative index");
+
+ // Valid access should not throw
+ byte value = span[0];
+ Assert.AreEqual((byte)0, value, "Valid index 0 should return correct value");
+
+ value = span[9];
+ Assert.AreEqual((byte)9, value, "Valid index 9 should return correct value");
+
+ value = span[5];
+ Assert.AreEqual((byte)5, value, "Valid index 5 should return correct value");
+ }
+
+ // Helper methods to avoid capturing ref struct in lambdas
+ private static byte GetReadOnlySpanElement(byte[] array, int index)
+ {
+ ReadOnlySpan span = new ReadOnlySpan(array);
+ return span[index];
+ }
+
+ private static byte GetPartialReadOnlySpanElement(byte[] array, int start, int length, int index)
+ {
+ ReadOnlySpan span = new ReadOnlySpan(array, start, length);
+ return span[index];
+ }
+
+ private static byte GetEmptyReadOnlySpanElement(int index)
+ {
+ ReadOnlySpan span = ReadOnlySpan.Empty;
+ return span[index];
+ }
+
+ private static byte GetNullReadOnlySpanElement(int index)
+ {
+ ReadOnlySpan span = new ReadOnlySpan(null);
+ return span[index];
+ }
+
+ private static byte GetSlicedReadOnlySpanElement(byte[] array, int sliceStart, int sliceLength, int index)
+ {
+ ReadOnlySpan span = new ReadOnlySpan(array);
+ ReadOnlySpan sliced = span.Slice(sliceStart, sliceLength);
+ return sliced[index];
+ }
+
+ private static byte GetStackAllocReadOnlySpanElement(int spanLength, int index)
+ {
+ ReadOnlySpan span = spanLength == 8
+ ? stackalloc byte[8] { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80 }
+ : stackalloc byte[0];
+ return span[index];
+ }
+
+ [TestMethod]
+ public void PartialSpanIndexOutOfRangeExceptionTests()
+ {
+ // Test IndexOutOfRangeException with partial ReadOnlySpans
+ byte[] array = new byte[20];
+ ReadOnlySpan span = new ReadOnlySpan(array, 5, 10);
+
+ // Should have length 10
+ Assert.AreEqual(10, span.Length, "Partial ReadOnlySpan should have length 10");
+
+ // Valid access
+ byte value = span[0];
+ value = span[9];
+
+ // Invalid access - at boundary
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ _ = GetPartialReadOnlySpanElement(array, 5, 10, 10);
+ }, "Should throw IndexOutOfRangeException at index equal to partial span Length");
+
+ // Invalid access - negative index
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ _ = GetPartialReadOnlySpanElement(array, 5, 10, -1);
+ }, "Should throw IndexOutOfRangeException at negative index in partial span");
+
+ // Invalid access - beyond boundary
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ _ = GetPartialReadOnlySpanElement(array, 5, 10, 15);
+ }, "Should throw IndexOutOfRangeException at index beyond partial span Length");
+ }
+
+ [TestMethod]
+ public void EmptySpanIndexOutOfRangeExceptionTests()
+ {
+ // Test IndexOutOfRangeException with empty ReadOnlySpans
+ ReadOnlySpan emptySpan = ReadOnlySpan.Empty;
+
+ Assert.AreEqual(0, emptySpan.Length, "Empty ReadOnlySpan should have length 0");
+
+ // Any index access should throw
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ _ = GetEmptyReadOnlySpanElement(0);
+ }, "Should throw IndexOutOfRangeException on empty span at index 0");
+
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ _ = GetEmptyReadOnlySpanElement(-1);
+ }, "Should throw IndexOutOfRangeException on empty span at negative index");
+
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ _ = GetEmptyReadOnlySpanElement(1);
+ }, "Should throw IndexOutOfRangeException on empty span at index 1");
+ }
+
+ [TestMethod]
+ public void NullArrayIndexOutOfRangeExceptionTests()
+ {
+ // Test IndexOutOfRangeException with null-based ReadOnlySpan
+ ReadOnlySpan nullSpan = new ReadOnlySpan(null);
+
+ Assert.AreEqual(0, nullSpan.Length, "ReadOnlySpan from null should have length 0");
+ Assert.IsTrue(nullSpan.IsEmpty, "ReadOnlySpan from null should be empty");
+
+ // Any index access should throw
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ _ = GetNullReadOnlySpanElement(0);
+ }, "Should throw IndexOutOfRangeException when accessing null-based span at index 0");
+
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ _ = GetNullReadOnlySpanElement(-1);
+ }, "Should throw IndexOutOfRangeException when accessing null-based span at negative index");
+ }
+
+ [TestMethod]
+ public void SlicedSpanIndexOutOfRangeExceptionTests()
+ {
+ // Test IndexOutOfRangeException with sliced ReadOnlySpans
+ byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
+ ReadOnlySpan span = new ReadOnlySpan(array);
+ ReadOnlySpan sliced = span.Slice(4, 8);
+
+ // Should have length 8
+ Assert.AreEqual(8, sliced.Length, "Sliced ReadOnlySpan should have length 8");
+
+ // Valid access
+ byte value = sliced[0];
+ Assert.AreEqual((byte)0x04, value, "First element of slice should be 0x04");
+
+ value = sliced[7];
+ Assert.AreEqual((byte)0x0B, value, "Last element of slice should be 0x0B");
+
+ // Invalid access at boundary
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ _ = GetSlicedReadOnlySpanElement(array, 4, 8, 8);
+ }, "Should throw IndexOutOfRangeException at sliced span boundary");
+
+ // Invalid access beyond boundary
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ _ = GetSlicedReadOnlySpanElement(array, 4, 8, 10);
+ }, "Should throw IndexOutOfRangeException beyond sliced span boundary");
+
+ // Invalid negative access
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ _ = GetSlicedReadOnlySpanElement(array, 4, 8, -1);
+ }, "Should throw IndexOutOfRangeException at negative index in sliced span");
+ }
+
+ [TestMethod]
+ public void StackAllocIndexOutOfRangeExceptionTests()
+ {
+ // Test IndexOutOfRangeException with stack-allocated ReadOnlySpans
+ ReadOnlySpan span = stackalloc byte[8] { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80 };
+
+ Assert.AreEqual(8, span.Length, "Stack-allocated ReadOnlySpan should have length 8");
+
+ // Valid access
+ byte value = span[0];
+ Assert.AreEqual((byte)0x10, value, "First element should be 0x10");
+
+ value = span[7];
+ Assert.AreEqual((byte)0x80, value, "Last element should be 0x80");
+
+ // Invalid access at boundary
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ _ = GetStackAllocReadOnlySpanElement(8, 8);
+ }, "Should throw IndexOutOfRangeException at stack-allocated span boundary");
+
+ // Invalid negative access
+ Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
+ {
+ _ = GetStackAllocReadOnlySpanElement(8, -1);
+ }, "Should throw IndexOutOfRangeException at negative index in stack-allocated span");
+ }
+
+ [TestMethod]
+ public void ToArrayTest()
+ {
+ byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
+
+ ReadOnlySpan span = new ReadOnlySpan(array);
+
+ byte[] toArray = span.ToArray();
+
+ CollectionAssert.AreEqual(array, toArray, "Original array and ReadOnlySpan.ToArray should be the same");
+ }
+
+ [TestMethod]
+ public void ConstructorsOfAllKindsTests()
+ {
+ // Empty span using default constructor
+ ReadOnlySpan span = new ReadOnlySpan();
+
+ Assert.AreEqual(span.Length, 0, "Empty ReadOnlySpan should have length of 0");
+ Assert.IsTrue(span.IsEmpty, "Empty ReadOnlySpan should be IsEmpty");
+
+ // Empty span from null with zero start and length
+ span = new ReadOnlySpan(null, 0, 0);
+
+ Assert.AreEqual(span.Length, 0, "Empty ReadOnlySpan should have length of 0");
+ Assert.IsTrue(span.IsEmpty, "Empty ReadOnlySpan should be IsEmpty");
+
+ // Empty span using Empty property
+ span = ReadOnlySpan.Empty;
+
+ Assert.AreEqual(span.Length, 0, "Empty ReadOnlySpan should have length of 0");
+ Assert.IsTrue(span.IsEmpty, "Empty ReadOnlySpan should be IsEmpty");
+
+ // ReadOnlySpan from normal array
+ byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
+ span = new ReadOnlySpan(array);
+
+ Assert.AreEqual(span.Length, array.Length, $"ReadOnlySpan should have length of the array it takes: {array.Length}");
+ Assert.IsFalse(span.IsEmpty, "ReadOnlySpan should NOT be IsEmpty");
+
+ // ReadOnlySpan from normal array with different start and length
+ span = new ReadOnlySpan(array, 2, 8);
+
+ Assert.AreEqual(span.Length, 8, $"ReadOnlySpan should have length of 8");
+ Assert.IsFalse(span.IsEmpty, "ReadOnlySpan should NOT be IsEmpty");
+
+ // ReadOnlySpan from array initializer
+ span = new byte[] { 0xAA, 0xBB, 0xCC, 0xDD };
+
+ Assert.AreEqual(span.Length, 4, "ReadOnlySpan from array initializer should have length of 4");
+ Assert.IsFalse(span.IsEmpty, "ReadOnlySpan from array initializer should NOT be IsEmpty");
+ Assert.AreEqual(span[0], (byte)0xAA, "First element should be 0xAA");
+ Assert.AreEqual(span[1], (byte)0xBB, "Second element should be 0xBB");
+ Assert.AreEqual(span[2], (byte)0xCC, "Third element should be 0xCC");
+ Assert.AreEqual(span[3], (byte)0xDD, "Fourth element should be 0xDD");
+ }
+
+ [TestMethod]
+ public void SliceTests()
+ {
+ // ReadOnlySpan from normal array
+ byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
+ ReadOnlySpan span = new ReadOnlySpan(array);
+
+ // Slice 2 elements and check
+ ReadOnlySpan sliced = span.Slice(0, 2);
+
+ Assert.AreEqual(sliced.Length, 2, "Sliced span length must be 2");
+ Assert.AreEqual(sliced[0], (byte)0x00, "Sliced first element must be value 0");
+ Assert.AreEqual(sliced[1], (byte)0x01, "Sliced second element must be value 1");
+
+ // Slice 4 elements starting at index 2 and check
+ sliced = span.Slice(2, 4);
+
+ Assert.AreEqual(sliced.Length, 4, "Sliced span length must be 4");
+ Assert.AreEqual(sliced[0], (byte)0x02, "Sliced first element must be value 2");
+ Assert.AreEqual(sliced[1], (byte)0x03, "Sliced second element must be value 3");
+ Assert.AreEqual(sliced[2], (byte)0x04, "Sliced third element must be value 4");
+ Assert.AreEqual(sliced[3], (byte)0x05, "Sliced fourth element must be value 5");
+
+ // Slice starting at element 4 and check
+ sliced = span.Slice(4);
+
+ Assert.AreEqual(sliced.Length, 12, "Sliced span length must be 12");
+
+ for (int i = 0; i < sliced.Length; i++)
+ {
+ Assert.AreEqual(sliced[i], span[i + 4], "ReadOnlySpan value should be the same as from the original span");
+ }
+
+ // Slice of slice
+ ReadOnlySpan secondSliced = sliced.Slice(2, 4);
+
+ Assert.AreEqual(secondSliced.Length, 4, "Sliced span length must be 4");
+
+ for (int i = 0; i < secondSliced.Length; i++)
+ {
+ Assert.AreEqual(secondSliced[i], sliced[i + 2], "ReadOnlySpan value should be the same as from the original span");
+ }
+
+ // Should be an empty one
+ ReadOnlySpan empty = span.Slice(span.Length);
+
+ Assert.AreEqual(empty.Length, 0, "Slicing all the span should result in an empty span");
+ Assert.IsTrue(empty.IsEmpty, "Empty span should be empty");
+ }
+
+ [TestMethod]
+ public void GetElementsTests()
+ {
+ // ReadOnlySpan from normal array
+ byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
+ ReadOnlySpan span = new ReadOnlySpan(array);
+ for (int i = 0; i < span.Length; i++)
+ {
+ Assert.AreEqual(span[i], array[i], "ReadOnlySpan value should be the same as from the original array");
+ }
+
+ // Partial span
+ span = new ReadOnlySpan(array, 2, 8);
+ for (int i = 0; i < span.Length; i++)
+ {
+ Assert.AreEqual(span[i], array[i + 2], "ReadOnlySpan value should be the same as from the original array");
+ }
+ }
+
+ [TestMethod]
+ public void CopyToTests()
+ {
+ byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
+ ReadOnlySpan span = new ReadOnlySpan(array);
+
+ // Copy to with the exact same size
+ Span destination = new Span(new byte[span.Length]);
+ span.CopyTo(destination);
+
+ CollectionAssert.AreEqual(array, destination.ToArray(), "Original array and ReadOnlySpan.CopyTo should be the same");
+
+ // Copy to a larger span
+ destination = new Span(new byte[span.Length + 4]);
+ span.CopyTo(destination);
+
+ Assert.AreEqual(destination.Length, span.Length + 4, "Destination should maintain its original size");
+
+ // Verify that the copied elements match
+ for (int i = 0; i < span.Length; i++)
+ {
+ Assert.AreEqual(span[i], destination[i], $"Element at index {i} should match after CopyTo");
+ }
+
+ // Verify that extra elements in destination remain zero
+ for (int i = span.Length; i < destination.Length; i++)
+ {
+ Assert.AreEqual((byte)0, destination[i], $"Element at index {i} beyond copied range should remain zero");
+ }
+ }
+
+ [TestMethod]
+ public void CopyToEmptySpanTests()
+ {
+ // Empty ReadOnlySpan CopyTo empty Span
+ ReadOnlySpan emptySource = ReadOnlySpan.Empty;
+ Span emptyDestination = Span.Empty;
+
+ emptySource.CopyTo(emptyDestination);
+
+ Assert.AreEqual(0, emptyDestination.Length, "Empty destination should remain empty");
+
+ // Empty ReadOnlySpan CopyTo larger Span
+ Span largerDestination = new Span(new byte[5]);
+ emptySource.CopyTo(largerDestination);
+
+ Assert.AreEqual(5, largerDestination.Length, "Destination should maintain its size");
+
+ // Verify all elements remain zero
+ for (int i = 0; i < largerDestination.Length; i++)
+ {
+ Assert.AreEqual((byte)0, largerDestination[i], $"Element at index {i} should be zero");
+ }
+ }
+
+ [TestMethod]
+ public void CopyToPartialSpanTests()
+ {
+ byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
+
+ // Create a partial ReadOnlySpan
+ ReadOnlySpan span = new ReadOnlySpan(array, 4, 8);
+
+ // Copy to a span of exact size
+ Span destination = new Span(new byte[8]);
+ span.CopyTo(destination);
+
+ // Verify copied elements
+ for (int i = 0; i < span.Length; i++)
+ {
+ Assert.AreEqual(span[i], destination[i], $"Element at index {i} should match");
+ Assert.AreEqual(array[i + 4], destination[i], $"Element should match original array at offset");
+ }
+ }
+
+ [TestMethod]
+ public void CopyToSlicedSpanTests()
+ {
+ byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
+ ReadOnlySpan span = new ReadOnlySpan(array);
+
+ // Slice the ReadOnlySpan and then copy
+ ReadOnlySpan sliced = span.Slice(2, 6);
+ Span destination = new Span(new byte[6]);
+
+ sliced.CopyTo(destination);
+
+ // Verify copied elements match the sliced portion
+ for (int i = 0; i < sliced.Length; i++)
+ {
+ Assert.AreEqual(sliced[i], destination[i], $"Element at index {i} should match sliced span");
+ Assert.AreEqual(array[i + 2], destination[i], $"Element should match original array at offset 2");
+ }
+ }
+
+ [TestMethod]
+ public void CopyToWithDestinationSliceTests()
+ {
+ byte[] array = new byte[8] { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80 };
+ ReadOnlySpan source = new ReadOnlySpan(array);
+
+ // Create a larger destination and copy to a slice of it
+ byte[] destArray = new byte[12];
+ Span destination = new Span(destArray);
+ Span destSlice = destination.Slice(2, 8);
+
+ source.CopyTo(destSlice);
+
+ // Verify first 2 elements remain zero
+ Assert.AreEqual((byte)0, destArray[0], "Element before copied range should be zero");
+ Assert.AreEqual((byte)0, destArray[1], "Element before copied range should be zero");
+
+ // Verify copied elements
+ for (int i = 0; i < source.Length; i++)
+ {
+ Assert.AreEqual(source[i], destArray[i + 2], $"Copied element at index {i + 2} should match source");
+ }
+
+ // Verify last elements remain zero
+ Assert.AreEqual((byte)0, destArray[10], "Element after copied range should be zero");
+ Assert.AreEqual((byte)0, destArray[11], "Element after copied range should be zero");
+ }
+
+ [TestMethod]
+ public void CopyToWithNullableArrayTests()
+ {
+ // Test that CopyTo works correctly when the internal _array field is nullable
+ // Create ReadOnlySpan from null (tests nullable _array field)
+ ReadOnlySpan nullSpan = new ReadOnlySpan(null);
+ Span destination = new Span(new byte[5]);
+
+ // CopyTo from empty span should not throw
+ nullSpan.CopyTo(destination);
+
+ // Verify destination remains unchanged (all zeros)
+ for (int i = 0; i < destination.Length; i++)
+ {
+ Assert.AreEqual((byte)0, destination[i], $"Element at index {i} should be zero");
+ }
+ }
+
+ [TestMethod]
+ public void StackAllocReadOnlySpanTests()
+ {
+ // Create a ReadOnlySpan from stack-allocated memory
+ ReadOnlySpan span = stackalloc byte[16];
+
+ Assert.AreEqual(16, span.Length, "Stack-allocated ReadOnlySpan should have length of 16");
+ Assert.IsFalse(span.IsEmpty, "Stack-allocated ReadOnlySpan should NOT be IsEmpty");
+
+ // Verify all elements are initialized to zero
+ for (int i = 0; i < span.Length; i++)
+ {
+ Assert.AreEqual((byte)0, span[i], "Stack-allocated ReadOnlySpan elements should be initialized to 0");
+ }
+ }
+
+ [TestMethod]
+ public void StackAllocReadOnlySpanWithInitializerTests()
+ {
+ // Create a ReadOnlySpan from stack-allocated memory with initializer
+ ReadOnlySpan span = stackalloc byte[8] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
+
+ Assert.AreEqual(8, span.Length, "Stack-allocated ReadOnlySpan with initializer should have length of 8");
+ Assert.IsFalse(span.IsEmpty, "Stack-allocated ReadOnlySpan should NOT be IsEmpty");
+
+ // Verify the initialized values
+ for (int i = 0; i < span.Length; i++)
+ {
+ Assert.AreEqual((byte)(i + 1), span[i], $"Stack-allocated ReadOnlySpan element at index {i} should be {i + 1}");
+ }
+ }
+
+ [TestMethod]
+ public void StackAllocReadOnlySpanSliceTests()
+ {
+ // Create a ReadOnlySpan from stack-allocated memory
+ ReadOnlySpan span = stackalloc byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
+
+ // Slice the stack-allocated span
+ ReadOnlySpan sliced = span.Slice(4, 8);
+
+ Assert.AreEqual(8, sliced.Length, "Sliced stack-allocated ReadOnlySpan should have length of 8");
+
+ for (int i = 0; i < sliced.Length; i++)
+ {
+ Assert.AreEqual((byte)(i + 4), sliced[i], $"Sliced element at index {i} should be {i + 4}");
+ }
+ }
+
+ [TestMethod]
+ public void StackAllocReadOnlySpanToArrayTests()
+ {
+ // Create a ReadOnlySpan from stack-allocated memory
+ ReadOnlySpan span = stackalloc byte[6] { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
+
+ // Convert to array
+ byte[] array = span.ToArray();
+
+ Assert.AreEqual(6, array.Length, "ToArray should create an array with the same length");
+
+ byte[] expected = new byte[] { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
+ CollectionAssert.AreEqual(expected, array, "Stack-allocated ReadOnlySpan ToArray should match expected values");
+ }
+
+ [TestMethod]
+ public void StackAllocReadOnlySpanCopyToTests()
+ {
+ // Create a ReadOnlySpan from stack-allocated memory
+ ReadOnlySpan source = stackalloc byte[8] { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 };
+
+ // Create a destination Span
+ Span destination = new Span(new byte[8]);
+
+ // Copy from stack-allocated ReadOnlySpan to heap-allocated Span
+ source.CopyTo(destination);
+
+ // Verify the copy
+ for (int i = 0; i < source.Length; i++)
+ {
+ Assert.AreEqual(source[i], destination[i], $"Element at index {i} should match after CopyTo from stack-allocated span");
+ }
+ }
+
+ [TestMethod]
+ public void StackAllocEmptyReadOnlySpanTests()
+ {
+ // Create an empty stack-allocated ReadOnlySpan
+ ReadOnlySpan span = stackalloc byte[0];
+
+ Assert.AreEqual(0, span.Length, "Empty stack-allocated ReadOnlySpan should have length of 0");
+ Assert.IsTrue(span.IsEmpty, "Empty stack-allocated ReadOnlySpan should be IsEmpty");
+ }
+
+ [TestMethod]
+ public void ImplicitConversionFromArrayTests()
+ {
+ // Test implicit conversion from array to ReadOnlySpan
+ byte[] array = new byte[8] { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80 };
+
+ ReadOnlySpan span = array; // Implicit conversion
+
+ Assert.AreEqual(array.Length, span.Length, "Implicitly converted ReadOnlySpan should have same length as array");
+
+ for (int i = 0; i < array.Length; i++)
+ {
+ Assert.AreEqual(array[i], span[i], $"Element at index {i} should match");
+ }
+ }
+
+ [TestMethod]
+ public void EqualityOperatorTests()
+ {
+ byte[] array1 = new byte[4] { 0x01, 0x02, 0x03, 0x04 };
+ byte[] array2 = new byte[4] { 0x01, 0x02, 0x03, 0x04 };
+ byte[] array3 = new byte[4] { 0x01, 0x02, 0x03, 0x05 };
+
+ ReadOnlySpan span1 = new ReadOnlySpan(array1);
+ ReadOnlySpan span2 = new ReadOnlySpan(array2);
+ ReadOnlySpan span3 = new ReadOnlySpan(array3);
+
+ // Test equality with same content (content-based equality)
+ Assert.IsTrue(span1 == span2, "ReadOnlySpans with same content should be equal");
+ Assert.IsFalse(span1 != span2, "ReadOnlySpans with same content should not be not-equal");
+
+ // Test inequality with different content
+ Assert.IsTrue(span1 != span3, "ReadOnlySpans with different content should not be equal");
+ Assert.IsFalse(span1 == span3, "ReadOnlySpans with different content should not be equal");
+
+ // Test empty spans
+ ReadOnlySpan empty1 = new ReadOnlySpan();
+ ReadOnlySpan empty2 = new ReadOnlySpan();
+
+ Assert.IsTrue(empty1 == empty2, "Empty ReadOnlySpans should be equal");
+ Assert.IsFalse(empty1 != empty2, "Empty ReadOnlySpans should not be not-equal");
+ }
+
+ [TestMethod]
+ public void EqualityWithPartialSpansTests()
+ {
+ byte[] array1 = new byte[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ byte[] array2 = new byte[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+
+ // Create two spans from different arrays with same content
+ ReadOnlySpan span1 = new ReadOnlySpan(array1, 2, 5);
+ ReadOnlySpan span2 = new ReadOnlySpan(array2, 2, 5);
+
+ // They should be equal (content-based equality)
+ Assert.IsTrue(span1 == span2, "Partial spans with same content should be equal");
+ Assert.IsFalse(span1 != span2, "Partial spans with same content should not be not-equal");
+
+ // Create span from different array with different content
+ byte[] array3 = new byte[10] { 0, 1, 2, 3, 99, 5, 6, 7, 8, 9 };
+ ReadOnlySpan span3 = new ReadOnlySpan(array3, 2, 5);
+ Assert.IsFalse(span1 == span3, "Partial spans with different content should not be equal");
+ Assert.IsTrue(span1 != span3, "Partial spans with different content should be not-equal");
+
+ // Create span with different length
+ ReadOnlySpan span4 = new ReadOnlySpan(array1, 2, 4);
+ Assert.IsFalse(span1 == span4, "Partial spans with different length should not be equal");
+ Assert.IsTrue(span1 != span4, "Partial spans with different length should be not-equal");
+ }
+
+ [TestMethod]
+ public void EqualityWithSlicedSpansTests()
+ {
+ byte[] array1 = new byte[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ byte[] array2 = new byte[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+
+ ReadOnlySpan originalSpan1 = new ReadOnlySpan(array1);
+ ReadOnlySpan originalSpan2 = new ReadOnlySpan(array2);
+
+ // Create two slices from different arrays with same content
+ ReadOnlySpan slice1 = originalSpan1.Slice(2, 5);
+ ReadOnlySpan slice2 = originalSpan2.Slice(2, 5);
+
+ // They should be equal (content-based equality)
+ Assert.IsTrue(slice1 == slice2, "Slices with same content should be equal");
+ Assert.IsFalse(slice1 != slice2, "Slices with same content should not be not-equal");
+
+ // Create a slice with different content
+ byte[] array3 = new byte[10] { 0, 1, 2, 99, 4, 5, 6, 7, 8, 9 };
+ ReadOnlySpan originalSpan3 = new ReadOnlySpan(array3);
+ ReadOnlySpan slice3 = originalSpan3.Slice(2, 5);
+ Assert.IsFalse(slice1 == slice3, "Slices with different content should not be equal");
+
+ // Create a slice with different length
+ ReadOnlySpan slice4 = originalSpan1.Slice(2, 4);
+ Assert.IsFalse(slice1 == slice4, "Slices with different length should not be equal");
+ }
+
+ [TestMethod]
+ public void EqualityWithNullArrayTests()
+ {
+ // Test equality with null-backed spans
+ ReadOnlySpan nullSpan1 = new ReadOnlySpan(null);
+ ReadOnlySpan nullSpan2 = new ReadOnlySpan(null);
+ ReadOnlySpan nullSpan3 = new ReadOnlySpan(null, 0, 0);
+
+ // All null-backed spans should be equal (both are empty)
+ Assert.IsTrue(nullSpan1 == nullSpan2, "Null-backed spans should be equal");
+ Assert.IsTrue(nullSpan1 == nullSpan3, "Null-backed spans with explicit 0,0 should be equal");
+ Assert.IsFalse(nullSpan1 != nullSpan2, "Null-backed spans should not be not-equal");
+
+ // Compare with Empty
+ ReadOnlySpan emptySpan = ReadOnlySpan.Empty;
+ Assert.IsTrue(nullSpan1 == emptySpan, "Null-backed span should equal Empty span");
+ }
+
+ [TestMethod]
+ public void EqualityContentBasedTests()
+ {
+ // Create two arrays with identical content but different references
+ byte[] array1 = new byte[5] { 10, 20, 30, 40, 50 };
+ byte[] array2 = new byte[5] { 10, 20, 30, 40, 50 };
+
+ ReadOnlySpan span1 = new ReadOnlySpan(array1);
+ ReadOnlySpan span2 = new ReadOnlySpan(array2);
+
+ // ReadOnlySpans with same content should be equal (content-based equality)
+ Assert.IsTrue(span1 == span2, "ReadOnlySpans with same content should be equal");
+ Assert.IsFalse(span1 != span2, "ReadOnlySpans with same content should not be not-equal");
+
+ // Now create another span from array1
+ ReadOnlySpan span1Copy = new ReadOnlySpan(array1);
+ Assert.IsTrue(span1 == span1Copy, "ReadOnlySpans from same array should be equal");
+
+ // Modify array1 content
+ array1[2] = 99;
+
+ // Spans should no longer be equal because content changed
+ Assert.IsFalse(span1 == span2, "ReadOnlySpans should not be equal after content changes");
+ Assert.IsTrue(span1 != span2, "ReadOnlySpans should be not-equal after content changes");
+
+ // But span1 and span1Copy still reference the same array so should see same content
+ Assert.IsTrue(span1 == span1Copy, "ReadOnlySpans from same array should remain equal");
+ }
+
+ [TestMethod]
+ public void EqualityWithStackAllocatedSpansTests()
+ {
+ // Stack-allocated spans with same content should be equal
+ ReadOnlySpan stackSpan1 = stackalloc byte[5] { 1, 2, 3, 4, 5 };
+ ReadOnlySpan stackSpan2 = stackalloc byte[5] { 1, 2, 3, 4, 5 };
+
+ // Stack-allocated spans with identical content should be equal (content-based)
+ Assert.IsTrue(stackSpan1 == stackSpan2, "Stack-allocated spans with same content should be equal");
+ Assert.IsFalse(stackSpan1 != stackSpan2, "Stack-allocated spans with same content should not be not-equal");
+
+ // Stack-allocated spans with different content should not be equal
+ ReadOnlySpan stackSpan3 = stackalloc byte[5] { 1, 2, 99, 4, 5 };
+ Assert.IsFalse(stackSpan1 == stackSpan3, "Stack-allocated spans with different content should not be equal");
+ Assert.IsTrue(stackSpan1 != stackSpan3, "Stack-allocated spans with different content should be not-equal");
+
+ // Create a "copy" reference to the same stack span
+ ReadOnlySpan stackSpan1Copy = stackSpan1;
+ Assert.IsTrue(stackSpan1 == stackSpan1Copy, "Same stack-allocated span should equal itself");
+ }
+
+ [TestMethod]
+ public void EqualityTransitivityTests()
+ {
+ byte[] array1 = new byte[5] { 1, 2, 3, 4, 5 };
+ byte[] array2 = new byte[5] { 1, 2, 3, 4, 5 };
+ byte[] array3 = new byte[5] { 1, 2, 3, 4, 5 };
+
+ ReadOnlySpan span1 = new ReadOnlySpan(array1);
+ ReadOnlySpan span2 = new ReadOnlySpan(array2);
+ ReadOnlySpan span3 = new ReadOnlySpan(array3);
+
+ // Test transitivity: if span1 == span2 and span2 == span3, then span1 == span3
+ Assert.IsTrue(span1 == span2, "span1 should equal span2");
+ Assert.IsTrue(span2 == span3, "span2 should equal span3");
+ Assert.IsTrue(span1 == span3, "span1 should equal span3 (transitivity)");
+ }
+
+ [TestMethod]
+ public void EqualityReflexivityTests()
+ {
+ byte[] array = new byte[5] { 1, 2, 3, 4, 5 };
+ ReadOnlySpan span = new ReadOnlySpan(array);
+
+ // Test reflexivity: span should equal itself
+ Assert.IsTrue(span == span, "ReadOnlySpan should equal itself (reflexivity)");
+ Assert.IsFalse(span != span, "ReadOnlySpan should not be not-equal to itself");
+
+ // Test with empty span
+ ReadOnlySpan emptySpan = ReadOnlySpan.Empty;
+ Assert.IsTrue(emptySpan == emptySpan, "Empty ReadOnlySpan should equal itself");
+ }
+
+ [TestMethod]
+ public void EqualitySymmetryTests()
+ {
+ byte[] array1 = new byte[5] { 1, 2, 3, 4, 5 };
+ byte[] array2 = new byte[5] { 1, 2, 3, 4, 5 };
+ byte[] array3 = new byte[5] { 1, 2, 99, 4, 5 };
+
+ ReadOnlySpan span1 = new ReadOnlySpan(array1);
+ ReadOnlySpan span2 = new ReadOnlySpan(array2);
+ ReadOnlySpan span3 = new ReadOnlySpan(array3);
+
+ // Test symmetry: if span1 == span2, then span2 == span1
+ Assert.IsTrue(span1 == span2, "span1 should equal span2");
+ Assert.IsTrue(span2 == span1, "span2 should equal span1 (symmetry)");
+
+ // Test symmetry with inequality
+ Assert.IsFalse(span1 == span3, "span1 should not equal span3");
+ Assert.IsFalse(span3 == span1, "span3 should not equal span1 (symmetry)");
+ }
+
+ [TestMethod]
+ public void EqualityWithDifferentOffsetsTests()
+ {
+ byte[] array = new byte[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+
+ // Create spans with overlapping but different regions
+ ReadOnlySpan span1 = new ReadOnlySpan(array, 2, 5); // [2,3,4,5,6]
+ ReadOnlySpan span2 = new ReadOnlySpan(array, 3, 5); // [3,4,5,6,7]
+
+ // They have different content so should not be equal
+ Assert.IsFalse(span1 == span2, "ReadOnlySpans with different content should not be equal");
+ Assert.IsTrue(span1 != span2, "ReadOnlySpans with different content should be not-equal");
+
+ // Create span with same content from different array
+ byte[] array2 = new byte[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ ReadOnlySpan span3 = new ReadOnlySpan(array2, 2, 5);
+ Assert.IsTrue(span1 == span3, "ReadOnlySpans with same content should be equal");
+ }
+
+ [TestMethod]
+ public void EqualityZeroLengthSpansTests()
+ {
+ byte[] array1 = new byte[10];
+ byte[] array2 = new byte[10];
+
+ // Create zero-length spans from different arrays
+ ReadOnlySpan zeroSpan1 = new ReadOnlySpan(array1, 5, 0);
+ ReadOnlySpan zeroSpan2 = new ReadOnlySpan(array2, 3, 0);
+
+ // Zero-length spans should be equal (both empty, content-based)
+ Assert.IsTrue(zeroSpan1 == zeroSpan2, "Zero-length spans should be equal");
+ Assert.IsFalse(zeroSpan1 != zeroSpan2, "Zero-length spans should not be not-equal");
+
+ // Zero-length span from same array with different offset
+ ReadOnlySpan zeroSpan3 = new ReadOnlySpan(array1, 7, 0);
+ Assert.IsTrue(zeroSpan1 == zeroSpan3, "Zero-length spans should be equal regardless of offset");
+
+ // Compare with Empty
+ ReadOnlySpan emptySpan = ReadOnlySpan.Empty;
+ Assert.IsTrue(zeroSpan1 == emptySpan, "Zero-length span should equal Empty span");
+ }
+
+ [TestMethod]
+ public void EqualityDifferentLengthsTests()
+ {
+ byte[] array1 = new byte[6] { 1, 2, 3, 4, 5, 6 };
+ byte[] array2 = new byte[4] { 1, 2, 3, 4 };
+
+ ReadOnlySpan span1 = new ReadOnlySpan(array1);
+ ReadOnlySpan span2 = new ReadOnlySpan(array2);
+
+ // Spans with different lengths should not be equal
+ Assert.IsFalse(span1 == span2, "ReadOnlySpans with different lengths should not be equal");
+ Assert.IsTrue(span1 != span2, "ReadOnlySpans with different lengths should be not-equal");
+
+ // Test with partial spans that have same initial content but different lengths
+ byte[] array3 = new byte[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ ReadOnlySpan span3 = new ReadOnlySpan(array3, 0, 4);
+ ReadOnlySpan span4 = new ReadOnlySpan(array3, 0, 6);
+
+ Assert.IsFalse(span3 == span4, "Partial spans with different lengths should not be equal");
+ }
+
+ [TestMethod]
+ public void EqualityPartialContentMatchTests()
+ {
+ // Test equality when only part of the content matches
+ byte[] array1 = new byte[5] { 1, 2, 3, 4, 5 };
+ byte[] array2 = new byte[5] { 1, 2, 99, 4, 5 };
+
+ ReadOnlySpan span1 = new ReadOnlySpan(array1);
+ ReadOnlySpan span2 = new ReadOnlySpan(array2);
+
+ // Should not be equal (one element differs)
+ Assert.IsFalse(span1 == span2, "ReadOnlySpans with partial content match should not be equal");
+ Assert.IsTrue(span1 != span2, "ReadOnlySpans with partial content match should be not-equal");
+
+ // Test with matching prefix
+ ReadOnlySpan span1Prefix = span1.Slice(0, 2);
+ ReadOnlySpan span2Prefix = span2.Slice(0, 2);
+ Assert.IsTrue(span1Prefix == span2Prefix, "ReadOnlySpans with matching prefix should be equal");
+
+ // Test with matching suffix
+ ReadOnlySpan span1Suffix = span1.Slice(3, 2);
+ ReadOnlySpan span2Suffix = span2.Slice(3, 2);
+ Assert.IsTrue(span1Suffix == span2Suffix, "ReadOnlySpans with matching suffix should be equal");
+ }
+ }
+}
diff --git a/Tests/NFUnitTestTypes/UnitTestsSpanByte.cs b/Tests/NFUnitTestTypes/UnitTestsSpanByte.cs
index e4ffde13..1c577578 100644
--- a/Tests/NFUnitTestTypes/UnitTestsSpanByte.cs
+++ b/Tests/NFUnitTestTypes/UnitTestsSpanByte.cs
@@ -13,13 +13,15 @@ public class UnitTestsSpanByte
public void EmptySpanTests()
{
// Empty span
- SpanByte span = SpanByte.Empty;
+ Span span = Span.Empty;
// Create a destination span larger
- SpanByte destination = new byte[1];
+ Span destination = new Span(new byte[1]);
+
span.CopyTo(destination);
// Now also empty
- destination = SpanByte.Empty;
+ destination = Span.Empty;
+
span.CopyTo(destination);
}
@@ -27,56 +29,270 @@ public void EmptySpanTests()
public void RaisingExceptionsOfAllKindsTests()
{
// Should raise an exception on creation
- Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { SpanByte span = new SpanByte(null, 1, 2); }, "ArgumentOutOfRangeException when array is null, start is 1 and length is 2");
- Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { SpanByte span = new SpanByte(new byte[1], 1, 2); }, "ArgumentOutOfRangeException when array is new byte[1], start is 1 and length is 2");
- Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { SpanByte span = new SpanByte(new byte[1], 0, 2); }, "ArgumentOutOfRangeException when array is new byte[1], start is 0 and length is 2");
- Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { SpanByte span = new SpanByte(new byte[1], 2, 0); }, "ArgumentOutOfRangeException when array is new byte[1], start is 2 and length is 0");
+ Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { Span span = new Span(null, 1, 2); }, "ArgumentOutOfRangeException when array is null, start is 1 and length is 2");
+ Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { Span span = new Span