Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ Read this before interpreting unexpected, missing, or truncated captured values,
- A value's `Value` string is not always its plain `ToString()`. A materialized collection (`List<T>`, arrays, dictionaries, ...) previews as a shallow JSON array/object instead of the default type-name text. A custom struct/class whose declared type does not override `ToString()` previews the same way — a shallow JSON object of its fields — so you do not need to add a temporary `ToString()` override just to see its contents. A type that does override `ToString()` keeps using that result unchanged. Either kind of preview is capped by depth, element count, and length like any other captured value; the element-count cap (default 10) and the preview's character budget both scale with `enable-pause-point --max-preview-elements` (1–1000). Raising it scales the character budget proportionally, so each element keeps the same ~100-character share it has at the default — plenty for numeric or boolean cells, but individually long elements can still be clipped by the scaled budget. The enable response echoes the effective `MaxPreviewElements`.
- A captured `Collision2D` is previewed as `{"Collider":{"Name":...,"UnityObjectPath":...},"OtherCollider":{...},"RelativeVelocity":...,"ContactCount":...}` — read `UnityObjectPath` to identify both colliding objects without an extra `execute-dynamic-code` round-trip. Each of `Collider` / `OtherCollider` is either that object form or the string `"(none)"` when the collider is null or destroyed.
- A multidimensional array (`int[,]`, `int[,,]`, ...) previews as `{"Shape":"Int32[2,3]","TotalElements":6,"PreviewedElements":6,"ElementOrder":"row-major (last dimension fastest)","Elements":[...]}` instead of a bare JSON array, since `Elements` flattens every rank in row-major order (last dimension fastest) and would otherwise look like an empty or 1D collection; when `--max-preview-elements` cuts the list, the preview also includes `"ElementsTruncated":true` and a smaller `PreviewedElements`. A `T[]` or jagged `T[][]` array is unaffected and still previews as a plain JSON array.
- `CapturedVariablesTruncated=true` means at least one value was clipped (value-length cap, collection preview element cap, or preview depth cap) or the variable-count cap stopped enumeration; clipped values are still present up to the cap.
- `CapturedVariablesTruncated=true` means at least one value was clipped (value-length cap or collection preview element cap) or the variable-count cap stopped enumeration; clipped values are still present up to the cap.
- `TruncatedVariableCount` is the exact number of variables that were dropped whole by the variable-count cap or whose value preview was clipped (`Truncated: true` on the entry). `TruncatedVariableNames` lists that union in capture order, at most 20 names; the count stays exact when more than 20 were affected. The invariant is `CapturedVariablesTruncated == (TruncatedVariableCount > 0)` and `TruncatedVariableCount >= TruncatedVariableNames.Length`.

## Unity Object Values

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ Read this before interpreting unexpected, missing, or truncated captured values,
- A value's `Value` string is not always its plain `ToString()`. A materialized collection (`List<T>`, arrays, dictionaries, ...) previews as a shallow JSON array/object instead of the default type-name text. A custom struct/class whose declared type does not override `ToString()` previews the same way — a shallow JSON object of its fields — so you do not need to add a temporary `ToString()` override just to see its contents. A type that does override `ToString()` keeps using that result unchanged. Either kind of preview is capped by depth, element count, and length like any other captured value; the element-count cap (default 10) and the preview's character budget both scale with `enable-pause-point --max-preview-elements` (1–1000). Raising it scales the character budget proportionally, so each element keeps the same ~100-character share it has at the default — plenty for numeric or boolean cells, but individually long elements can still be clipped by the scaled budget. The enable response echoes the effective `MaxPreviewElements`.
- A captured `Collision2D` is previewed as `{"Collider":{"Name":...,"UnityObjectPath":...},"OtherCollider":{...},"RelativeVelocity":...,"ContactCount":...}` — read `UnityObjectPath` to identify both colliding objects without an extra `execute-dynamic-code` round-trip. Each of `Collider` / `OtherCollider` is either that object form or the string `"(none)"` when the collider is null or destroyed.
- A multidimensional array (`int[,]`, `int[,,]`, ...) previews as `{"Shape":"Int32[2,3]","TotalElements":6,"PreviewedElements":6,"ElementOrder":"row-major (last dimension fastest)","Elements":[...]}` instead of a bare JSON array, since `Elements` flattens every rank in row-major order (last dimension fastest) and would otherwise look like an empty or 1D collection; when `--max-preview-elements` cuts the list, the preview also includes `"ElementsTruncated":true` and a smaller `PreviewedElements`. A `T[]` or jagged `T[][]` array is unaffected and still previews as a plain JSON array.
- `CapturedVariablesTruncated=true` means at least one value was clipped (value-length cap, collection preview element cap, or preview depth cap) or the variable-count cap stopped enumeration; clipped values are still present up to the cap.
- `CapturedVariablesTruncated=true` means at least one value was clipped (value-length cap or collection preview element cap) or the variable-count cap stopped enumeration; clipped values are still present up to the cap.
- `TruncatedVariableCount` is the exact number of variables that were dropped whole by the variable-count cap or whose value preview was clipped (`Truncated: true` on the entry). `TruncatedVariableNames` lists that union in capture order, at most 20 names; the count stays exact when more than 20 were affected. The invariant is `CapturedVariablesTruncated == (TruncatedVariableCount > 0)` and `TruncatedVariableCount >= TruncatedVariableNames.Length`.

## Unity Object Values

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
using System;
using System.Collections.Generic;

using NUnit.Framework;

using UnityEngine;
using UnityEngine.TestTools;

using io.github.hatayama.UnityCliLoop.FirstPartyTools;
using io.github.hatayama.UnityCliLoop.Runtime;

namespace io.github.hatayama.UnityCliLoop.Tests.Editor
{
/// <summary>
/// Verifies count-cap drops and preview-clipped entries share one truncation aggregate.
/// </summary>
[TestFixture]
public sealed class SourcePausePointTruncationAggregateTests
{
/// <summary>
/// What: preview clipping alone puts that variable name on the aggregate with count 1.
/// </summary>
[Test]
public void CaptureFrame_WhenOnlyPreviewIsClipped_ReportsThatNameAndCountOne()
{
string longValue = new string('a', SourcePausePointConstants.MaxCapturedVariableValueLength + 10);
object[] locals = { "longText", longValue, "hp", 42 };

(UloopPausePointCapturedVariableFrame frame, List<UloopCapturedVariable> variables, bool truncated) =
SourcePausePointCapture.CaptureFrame(null, Array.Empty<object>(), locals);

Assert.That(truncated, Is.True);
Assert.That(frame.Truncated, Is.True);
Assert.That(frame.TruncatedVariableCount, Is.EqualTo(1));
Assert.That(frame.TruncatedVariableNames, Is.EqualTo(new[] { "longText" }));
Assert.That(variables.Find(variable => variable.Name == "longText").Truncated, Is.True);
Assert.That(variables.Find(variable => variable.Name == "hp").Truncated, Is.False);
}

/// <summary>
/// What: count-cap overflow alone reports count 25 and local50 through local69 in order.
/// </summary>
[Test]
public void CaptureFrame_WhenOnlyCountCapDropsVariables_ReportsTwentyNamesAndExactCount()
{
object[] locals = new object[150];
for (int index = 0; index < 75; index++)
{
locals[index * 2] = $"local{index}";
locals[index * 2 + 1] = index;
}

(UloopPausePointCapturedVariableFrame frame, _, bool truncated) =
SourcePausePointCapture.CaptureFrame(null, Array.Empty<object>(), locals);

Assert.That(truncated, Is.True);
Assert.That(frame.TruncatedVariableCount, Is.EqualTo(25));
Assert.That(
frame.TruncatedVariableNames,
Is.EqualTo(new[]
{
"local50",
"local51",
"local52",
"local53",
"local54",
"local55",
"local56",
"local57",
"local58",
"local59",
"local60",
"local61",
"local62",
"local63",
"local64",
"local65",
"local66",
"local67",
"local68",
"local69"
}));
}

/// <summary>
/// What: preview clipping plus count-cap drops unions names in capture order.
/// </summary>
[Test]
public void CaptureFrame_WhenPreviewClipAndCountCapCombine_UnionsNamesInCaptureOrder()
{
object[] locals = new object[106];
locals[0] = "longText";
locals[1] = new string('a', SourcePausePointConstants.MaxCapturedVariableValueLength + 10);
for (int index = 1; index < 53; index++)
{
locals[index * 2] = $"local{index}";
locals[index * 2 + 1] = index;
}

(UloopPausePointCapturedVariableFrame frame, _, bool truncated) =
SourcePausePointCapture.CaptureFrame(null, Array.Empty<object>(), locals);

Assert.That(truncated, Is.True);
Assert.That(frame.TruncatedVariableCount, Is.EqualTo(4));
Assert.That(
frame.TruncatedVariableNames,
Is.EqualTo(new[] { "longText", "local50", "local51", "local52" }));
}

/// <summary>
/// What: no clipping and no count-cap drop leaves the aggregate empty.
/// </summary>
[Test]
public void CaptureFrame_WhenNothingIsTruncated_ReportsEmptyAggregate()
{
object[] locals = { "speed", 5, "damage", 3 };

(UloopPausePointCapturedVariableFrame frame, _, bool truncated) =
SourcePausePointCapture.CaptureFrame(null, Array.Empty<object>(), locals);

Assert.That(truncated, Is.False);
Assert.That(frame.Truncated, Is.False);
Assert.That(frame.TruncatedVariableCount, Is.EqualTo(0));
Assert.That(frame.TruncatedVariableNames, Is.Empty);
}

/// <summary>
/// What: a clipped value with an empty name still increments the aggregate count.
/// </summary>
[Test]
public void CaptureFrame_WhenClippedValueHasEmptyName_CountsItWithoutReportingAName()
{
string longValue = new string('a', SourcePausePointConstants.MaxCapturedVariableValueLength + 10);
object[] locals = { "", longValue };

LogAssert.Expect(LogType.Assert, "name must not be null or empty");

(UloopPausePointCapturedVariableFrame frame, _, bool truncated) =
SourcePausePointCapture.CaptureFrame(null, Array.Empty<object>(), locals);

Assert.That(truncated, Is.True);
Assert.That(frame.Truncated, Is.True);
Assert.That(frame.TruncatedVariableCount, Is.EqualTo(1));
Assert.That(frame.TruncatedVariableNames, Is.Empty);
}

/// <summary>
/// What: more than 20 preview clips keep the first 20 names and the exact total count.
/// </summary>
[Test]
public void CaptureFrame_WhenPreviewClipsExceedNameCap_ReportsFirstTwentyNamesAndExactCount()
{
string longValue = new string('a', SourcePausePointConstants.MaxCapturedVariableValueLength + 10);
object[] locals = new object[42];
for (int index = 0; index < 21; index++)
{
locals[index * 2] = $"clip{index}";
locals[index * 2 + 1] = longValue;
}

(UloopPausePointCapturedVariableFrame frame, _, bool truncated) =
SourcePausePointCapture.CaptureFrame(null, Array.Empty<object>(), locals);

Assert.That(truncated, Is.True);
Assert.That(frame.TruncatedVariableCount, Is.EqualTo(21));
Assert.That(
frame.TruncatedVariableNames,
Is.EqualTo(new[]
{
"clip0",
"clip1",
"clip2",
"clip3",
"clip4",
"clip5",
"clip6",
"clip7",
"clip8",
"clip9",
"clip10",
"clip11",
"clip12",
"clip13",
"clip14",
"clip15",
"clip16",
"clip17",
"clip18",
"clip19"
}));
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ Read this before interpreting unexpected, missing, or truncated captured values,
- A value's `Value` string is not always its plain `ToString()`. A materialized collection (`List<T>`, arrays, dictionaries, ...) previews as a shallow JSON array/object instead of the default type-name text. A custom struct/class whose declared type does not override `ToString()` previews the same way — a shallow JSON object of its fields — so you do not need to add a temporary `ToString()` override just to see its contents. A type that does override `ToString()` keeps using that result unchanged. Either kind of preview is capped by depth, element count, and length like any other captured value; the element-count cap (default 10) and the preview's character budget both scale with `enable-pause-point --max-preview-elements` (1–1000). Raising it scales the character budget proportionally, so each element keeps the same ~100-character share it has at the default — plenty for numeric or boolean cells, but individually long elements can still be clipped by the scaled budget. The enable response echoes the effective `MaxPreviewElements`.
- A captured `Collision2D` is previewed as `{"Collider":{"Name":...,"UnityObjectPath":...},"OtherCollider":{...},"RelativeVelocity":...,"ContactCount":...}` — read `UnityObjectPath` to identify both colliding objects without an extra `execute-dynamic-code` round-trip. Each of `Collider` / `OtherCollider` is either that object form or the string `"(none)"` when the collider is null or destroyed.
- A multidimensional array (`int[,]`, `int[,,]`, ...) previews as `{"Shape":"Int32[2,3]","TotalElements":6,"PreviewedElements":6,"ElementOrder":"row-major (last dimension fastest)","Elements":[...]}` instead of a bare JSON array, since `Elements` flattens every rank in row-major order (last dimension fastest) and would otherwise look like an empty or 1D collection; when `--max-preview-elements` cuts the list, the preview also includes `"ElementsTruncated":true` and a smaller `PreviewedElements`. A `T[]` or jagged `T[][]` array is unaffected and still previews as a plain JSON array.
- `CapturedVariablesTruncated=true` means at least one value was clipped (value-length cap, collection preview element cap, or preview depth cap) or the variable-count cap stopped enumeration; clipped values are still present up to the cap.
- `CapturedVariablesTruncated=true` means at least one value was clipped (value-length cap or collection preview element cap) or the variable-count cap stopped enumeration; clipped values are still present up to the cap.
- `TruncatedVariableCount` is the exact number of variables that were dropped whole by the variable-count cap or whose value preview was clipped (`Truncated: true` on the entry). `TruncatedVariableNames` lists that union in capture order, at most 20 names; the count stays exact when more than 20 were affected. The invariant is `CapturedVariablesTruncated == (TruncatedVariableCount > 0)` and `TruncatedVariableCount >= TruncatedVariableNames.Length`.

## Unity Object Values

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ internal static (UloopPausePointCapturedVariableFrame Frame, List<UloopCapturedV
instance, parameterNamesAndValues, localNamesAndValues);
(List<UloopCapturedVariable> variables, bool truncated) =
SourcePausePointVariableFormatter.FormatFrame(frame, maxPreviewElements);
return (frame, variables, truncated);
frame = SourcePausePointTruncationAggregate.Merge(frame, variables);
return (frame, variables, truncated || frame.Truncated);
}
}
}
Loading
Loading