зеркало из https://github.com/aspnet/Routing.git
Родитель
0cf972cc43
Коммит
7209cab5e9
|
@ -0,0 +1,134 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public class JumpTableMultipleEntryBenchmark
|
||||
{
|
||||
private string[] _strings;
|
||||
private PathSegment[] _segments;
|
||||
|
||||
private JumpTable _linearSearch;
|
||||
private JumpTable _dictionary;
|
||||
|
||||
// All factors of 100 to support sampling
|
||||
[Params(2, 5, 10, 25, 50, 100)]
|
||||
public int Count;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_strings = GetStrings(100);
|
||||
_segments = new PathSegment[100];
|
||||
|
||||
for (var i = 0; i < _strings.Length; i++)
|
||||
{
|
||||
_segments[i] = new PathSegment(0, _strings[i].Length);
|
||||
}
|
||||
|
||||
var samples = new int[Count];
|
||||
for (var i = 0; i < samples.Length; i++)
|
||||
{
|
||||
samples[i] = i * (_strings.Length / Count);
|
||||
}
|
||||
|
||||
var entries = new List<(string text, int _)>();
|
||||
for (var i = 0; i < samples.Length; i++)
|
||||
{
|
||||
entries.Add((_strings[samples[i]], i));
|
||||
}
|
||||
|
||||
_linearSearch = new LinearSearchJumpTable(0, -1, entries.ToArray());
|
||||
_dictionary = new DictionaryJumpTable(0, -1, entries.ToArray());
|
||||
}
|
||||
|
||||
// This baseline is similar to SingleEntryJumpTable. We just want
|
||||
// something stable to compare against.
|
||||
[Benchmark(Baseline = true, OperationsPerInvoke = 100)]
|
||||
public int Baseline()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
var @string = strings[i];
|
||||
var segment = segments[i];
|
||||
|
||||
destination = segment.Length == 0 ? -1 :
|
||||
segment.Length != @string.Length ? 1 :
|
||||
string.Compare(
|
||||
@string,
|
||||
segment.Start,
|
||||
@string,
|
||||
0,
|
||||
@string.Length,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = 100)]
|
||||
public int LinearSearch()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
destination = _linearSearch.GetDestination(strings[i], segments[i]);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = 100)]
|
||||
public int Dictionary()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
destination = _dictionary.GetDestination(strings[i], segments[i]);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
private static string[] GetStrings(int count)
|
||||
{
|
||||
var strings = new string[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var guid = Guid.NewGuid().ToString();
|
||||
|
||||
// Between 5 and 36 characters
|
||||
var text = guid.Substring(0, Math.Max(5, Math.Min(count, 36)));
|
||||
if (char.IsDigit(text[0]))
|
||||
{
|
||||
// Convert first character to a letter.
|
||||
text = ((char)(text[0] + ('G' - '0'))) + text.Substring(1);
|
||||
}
|
||||
|
||||
if (i % 2 == 0)
|
||||
{
|
||||
// Lowercase half of them
|
||||
text = text.ToLowerInvariant();
|
||||
}
|
||||
|
||||
strings[i] = text;
|
||||
}
|
||||
|
||||
return strings;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public class JumpTableSingleEntryBenchmark
|
||||
{
|
||||
private JumpTable _table;
|
||||
private string[] _strings;
|
||||
private PathSegment[] _segments;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_table = new SingleEntryJumpTable(0, -1, "hello-world", 1);
|
||||
_strings = new string[]
|
||||
{
|
||||
"index/foo/2",
|
||||
"index/hello-world1/2",
|
||||
"index/hello-world/2",
|
||||
"index//2",
|
||||
"index/hillo-goodbye/2",
|
||||
};
|
||||
_segments = new PathSegment[]
|
||||
{
|
||||
new PathSegment(6, 3),
|
||||
new PathSegment(6, 12),
|
||||
new PathSegment(6, 11),
|
||||
new PathSegment(6, 0),
|
||||
new PathSegment(6, 13),
|
||||
};
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true, OperationsPerInvoke = 5)]
|
||||
public int Baseline()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
var @string = strings[i];
|
||||
var segment = segments[i];
|
||||
|
||||
destination = segment.Length == 0 ? -1 :
|
||||
segment.Length != 11 ? 1 :
|
||||
string.Compare(
|
||||
@string,
|
||||
segment.Start,
|
||||
"hello-world",
|
||||
0,
|
||||
segment.Length,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = 5)]
|
||||
public int Implementation()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
destination = _table.GetDestination(strings[i], segments[i]);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public class JumpTableZeroEntryBenchmark
|
||||
{
|
||||
private JumpTable _table;
|
||||
private string[] _strings;
|
||||
private PathSegment[] _segments;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_table = new ZeroEntryJumpTable(0, -1);
|
||||
_strings = new string[]
|
||||
{
|
||||
"index/foo/2",
|
||||
"index/hello-world1/2",
|
||||
"index/hello-world/2",
|
||||
"index//2",
|
||||
"index/hillo-goodbye/2",
|
||||
};
|
||||
_segments = new PathSegment[]
|
||||
{
|
||||
new PathSegment(6, 3),
|
||||
new PathSegment(6, 12),
|
||||
new PathSegment(6, 11),
|
||||
new PathSegment(6, 0),
|
||||
new PathSegment(6, 13),
|
||||
};
|
||||
}
|
||||
|
||||
[Benchmark(Baseline=true, OperationsPerInvoke = 5)]
|
||||
public int Baseline()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
destination = segments[i].Length == 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = 5)]
|
||||
public int Implementation()
|
||||
{
|
||||
var strings = _strings;
|
||||
var segments = _segments;
|
||||
|
||||
var destination = 0;
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
destination = _table.GetDestination(strings[i], segments[i]);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class DictionaryJumpTable : JumpTable
|
||||
{
|
||||
private readonly int _defaultDestination;
|
||||
private readonly int _exitDestination;
|
||||
private readonly Dictionary<string, int> _dictionary;
|
||||
|
||||
public DictionaryJumpTable(
|
||||
int defaultDestination,
|
||||
int exitDestination,
|
||||
(string text, int destination)[] entries)
|
||||
{
|
||||
_defaultDestination = defaultDestination;
|
||||
_exitDestination = exitDestination;
|
||||
|
||||
_dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
for (var i = 0; i < entries.Length; i++)
|
||||
{
|
||||
_dictionary.Add(entries[i].text, entries[i].destination);
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetDestination(string path, PathSegment segment)
|
||||
{
|
||||
if (segment.Length == 0)
|
||||
{
|
||||
return _exitDestination;
|
||||
}
|
||||
|
||||
var text = path.Substring(segment.Start, segment.Length);
|
||||
if (_dictionary.TryGetValue(text, out var destination))
|
||||
{
|
||||
return destination;
|
||||
}
|
||||
|
||||
return _defaultDestination;
|
||||
}
|
||||
|
||||
public override string DebuggerToString()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("{ ");
|
||||
|
||||
builder.Append(string.Join(", ", _dictionary.Select(kvp => $"{kvp.Key}: {kvp.Value}")));
|
||||
|
||||
builder.Append("$+: ");
|
||||
builder.Append(_defaultDestination);
|
||||
builder.Append(", ");
|
||||
|
||||
builder.Append("$0: ");
|
||||
builder.Append(_defaultDestination);
|
||||
|
||||
builder.Append(" }");
|
||||
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
internal abstract class JumpTable
|
||||
{
|
||||
public abstract int GetDestination(string path, PathSegment segment);
|
||||
|
||||
public virtual string DebuggerToString()
|
||||
{
|
||||
return GetType().Name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class JumpTableBuilder
|
||||
{
|
||||
public static readonly int InvalidDestination = -1;
|
||||
|
||||
private readonly List<(string text, int destination)> _entries = new List<(string text, int destination)>();
|
||||
|
||||
// The destination state when none of the text entries match.
|
||||
public int DefaultDestination { get; set; } = InvalidDestination;
|
||||
|
||||
// The destination state for a zero-length segment. This is a special
|
||||
// case because parameters don't match a zero-length segment.
|
||||
public int ExitDestination { get; set; } = InvalidDestination;
|
||||
|
||||
public void AddEntry(string text, int destination)
|
||||
{
|
||||
_entries.Add((text, destination));
|
||||
}
|
||||
|
||||
public JumpTable Build()
|
||||
{
|
||||
if (DefaultDestination == InvalidDestination)
|
||||
{
|
||||
var message = $"{nameof(DefaultDestination)} is not set. Please report this as a bug.";
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
if (ExitDestination == InvalidDestination)
|
||||
{
|
||||
var message = $"{nameof(ExitDestination)} is not set. Please report this as a bug.";
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
// The JumpTable implementation is chosen based on the number of entries. Right
|
||||
// now this is simple and minimal.
|
||||
if (_entries.Count == 0)
|
||||
{
|
||||
return new ZeroEntryJumpTable(DefaultDestination, ExitDestination);
|
||||
}
|
||||
|
||||
if (_entries.Count == 1)
|
||||
{
|
||||
var entry = _entries[0];
|
||||
return new SingleEntryJumpTable(DefaultDestination, ExitDestination, entry.text, entry.destination);
|
||||
}
|
||||
|
||||
if (_entries.Count < 10)
|
||||
{
|
||||
return new LinearSearchJumpTable(DefaultDestination, ExitDestination, _entries.ToArray());
|
||||
}
|
||||
|
||||
return new DictionaryJumpTable(DefaultDestination, ExitDestination, _entries.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class LinearSearchJumpTable : JumpTable
|
||||
{
|
||||
private readonly int _defaultDestination;
|
||||
private readonly int _exitDestination;
|
||||
private readonly (string text, int destination)[] _entries;
|
||||
|
||||
public LinearSearchJumpTable(
|
||||
int defaultDestination,
|
||||
int exitDestination,
|
||||
(string text, int destination)[] entries)
|
||||
{
|
||||
_defaultDestination = defaultDestination;
|
||||
_exitDestination = exitDestination;
|
||||
_entries = entries;
|
||||
}
|
||||
|
||||
public override int GetDestination(string path, PathSegment segment)
|
||||
{
|
||||
if (segment.Length == 0)
|
||||
{
|
||||
return _exitDestination;
|
||||
}
|
||||
|
||||
var entries = _entries;
|
||||
for (var i = 0; i < entries.Length; i++)
|
||||
{
|
||||
var text = entries[i].text;
|
||||
if (segment.Length == text.Length &&
|
||||
string.Compare(
|
||||
path,
|
||||
segment.Start,
|
||||
text,
|
||||
0,
|
||||
segment.Length,
|
||||
StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return entries[i].destination;
|
||||
}
|
||||
}
|
||||
|
||||
return _defaultDestination;
|
||||
}
|
||||
|
||||
public override string DebuggerToString()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("{ ");
|
||||
|
||||
builder.Append(string.Join(", ", _entries.Select(e => $"{e.text}: {e.destination}")));
|
||||
|
||||
builder.Append("$+: ");
|
||||
builder.Append(_defaultDestination);
|
||||
builder.Append(", ");
|
||||
|
||||
builder.Append("$0: ");
|
||||
builder.Append(_defaultDestination);
|
||||
|
||||
builder.Append(" }");
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class SingleEntryJumpTable : JumpTable
|
||||
{
|
||||
private readonly int _defaultDestination;
|
||||
private readonly int _exitDestination;
|
||||
private readonly string _text;
|
||||
private readonly int _destination;
|
||||
|
||||
public SingleEntryJumpTable(
|
||||
int defaultDestination,
|
||||
int exitDestination,
|
||||
string text,
|
||||
int destination)
|
||||
{
|
||||
_defaultDestination = defaultDestination;
|
||||
_exitDestination = exitDestination;
|
||||
_text = text;
|
||||
_destination = destination;
|
||||
}
|
||||
|
||||
public override int GetDestination(string path, PathSegment segment)
|
||||
{
|
||||
if (segment.Length == 0)
|
||||
{
|
||||
return _exitDestination;
|
||||
}
|
||||
|
||||
if (segment.Length == _text.Length &&
|
||||
string.Compare(
|
||||
path,
|
||||
segment.Start,
|
||||
_text,
|
||||
0,
|
||||
segment.Length,
|
||||
StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return _destination;
|
||||
}
|
||||
|
||||
return _defaultDestination;
|
||||
}
|
||||
|
||||
public override string DebuggerToString()
|
||||
{
|
||||
return $"{{ {_text}: {_destination}, $+: {_defaultDestination}, $0: {_exitDestination} }}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
internal class ZeroEntryJumpTable : JumpTable
|
||||
{
|
||||
private readonly int _defaultDestination;
|
||||
private readonly int _exitDestination;
|
||||
|
||||
public ZeroEntryJumpTable(int defaultDestination, int exitDestination)
|
||||
{
|
||||
_defaultDestination = defaultDestination;
|
||||
_exitDestination = exitDestination;
|
||||
}
|
||||
|
||||
public unsafe override int GetDestination(string path, PathSegment segment)
|
||||
{
|
||||
return segment.Length == 0 ? _exitDestination : _defaultDestination;
|
||||
}
|
||||
|
||||
public override string DebuggerToString()
|
||||
{
|
||||
return $"{{ $+: {_defaultDestination}, $0: {_exitDestination} }}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
current = states[current].Transitions.GetDestination(buffer, i, path);
|
||||
current = states[current].Transitions.GetDestination(path, buffer[i]);
|
||||
}
|
||||
|
||||
var matches = new List<(Endpoint, RouteValueDictionary)>();
|
||||
|
@ -88,64 +88,5 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
public Endpoint Endpoint;
|
||||
public string[] Parameters;
|
||||
}
|
||||
|
||||
public abstract class JumpTable
|
||||
{
|
||||
public unsafe abstract int GetDestination(PathSegment* segments, int depth, string path);
|
||||
}
|
||||
|
||||
public class JumpTableBuilder
|
||||
{
|
||||
private readonly List<(string text, int destination)> _entries = new List<(string text, int destination)>();
|
||||
|
||||
public int Depth { get; set; }
|
||||
|
||||
public int Exit { get; set; }
|
||||
|
||||
public void AddEntry(string text, int destination)
|
||||
{
|
||||
_entries.Add((text, destination));
|
||||
}
|
||||
|
||||
public JumpTable Build()
|
||||
{
|
||||
return new SimpleJumpTable(Depth, Exit, _entries.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
private class SimpleJumpTable : JumpTable
|
||||
{
|
||||
private readonly (string text, int destination)[] _entries;
|
||||
private readonly int _depth;
|
||||
private readonly int _exit;
|
||||
|
||||
public SimpleJumpTable(int depth, int exit, (string text, int destination)[] entries)
|
||||
{
|
||||
_depth = depth;
|
||||
_exit = exit;
|
||||
_entries = entries;
|
||||
}
|
||||
|
||||
public unsafe override int GetDestination(PathSegment* segments, int depth, string path)
|
||||
{
|
||||
for (var i = 0; i < _entries.Length; i++)
|
||||
{
|
||||
var segment = segments[depth];
|
||||
if (segment.Length == _entries[i].text.Length &&
|
||||
string.Compare(
|
||||
path,
|
||||
segment.Start,
|
||||
_entries[i].text,
|
||||
0,
|
||||
segment.Length,
|
||||
StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return _entries[i].destination;
|
||||
}
|
||||
}
|
||||
|
||||
return _exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,13 +109,18 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
|
||||
var exit = states.Count;
|
||||
states.Add(new State() { IsAccepting = false, Matches = Array.Empty<Candidate>(), });
|
||||
tables.Add(new JumpTableBuilder() { Exit = exit, });
|
||||
tables.Add(new JumpTableBuilder() { DefaultDestination = exit, ExitDestination = exit, });
|
||||
|
||||
for (var i = 0; i < tables.Count; i++)
|
||||
{
|
||||
if (tables[i].Exit == -1)
|
||||
if (tables[i].DefaultDestination == JumpTableBuilder.InvalidDestination)
|
||||
{
|
||||
tables[i].Exit = exit;
|
||||
tables[i].DefaultDestination = exit;
|
||||
}
|
||||
|
||||
if (tables[i].ExitDestination == JumpTableBuilder.InvalidDestination)
|
||||
{
|
||||
tables[i].ExitDestination = exit;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,7 +163,7 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
IsAccepting = node.Matches.Count > 0,
|
||||
});
|
||||
|
||||
var table = new JumpTableBuilder() { Depth = node.Depth, };
|
||||
var table = new JumpTableBuilder();
|
||||
tables.Add(table);
|
||||
|
||||
foreach (var kvp in node.Literals)
|
||||
|
@ -172,13 +177,13 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
|||
table.AddEntry(kvp.Key, transition);
|
||||
}
|
||||
|
||||
var exitIndex = -1;
|
||||
var defaultIndex = -1;
|
||||
if (node.Literals.TryGetValue("*", out var exit))
|
||||
{
|
||||
exitIndex = AddNode(exit, states, tables);
|
||||
defaultIndex = AddNode(exit, states, tables);
|
||||
}
|
||||
|
||||
table.Exit = exitIndex;
|
||||
table.DefaultDestination = defaultIndex;
|
||||
return index;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public class DictionaryJumpTableTest : MultipleEntryJumpTableTest
|
||||
{
|
||||
internal override JumpTable CreateTable(
|
||||
int defaultDestination,
|
||||
int exitDestination,
|
||||
params (string text, int destination)[] entries)
|
||||
{
|
||||
return new DictionaryJumpTable(defaultDestination, exitDestination, entries);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public class LinearSearchJumpTableTest : MultipleEntryJumpTableTest
|
||||
{
|
||||
internal override JumpTable CreateTable(
|
||||
int defaultDestination,
|
||||
int existDestination,
|
||||
params (string text, int destination)[] entries)
|
||||
{
|
||||
return new LinearSearchJumpTable(defaultDestination, existDestination, entries);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public abstract class MultipleEntryJumpTableTest
|
||||
{
|
||||
internal abstract JumpTable CreateTable(
|
||||
int defaultDestination,
|
||||
int exitDestination,
|
||||
params (string text, int destination)[] entries);
|
||||
|
||||
[Fact]
|
||||
public void GetDestination_ZeroLengthSegment_JumpsToExit()
|
||||
{
|
||||
// Arrange
|
||||
var table = CreateTable(0, 1, ("text", 2));
|
||||
|
||||
// Act
|
||||
var result = table.GetDestination("ignored", new PathSegment(0, 0));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDestination_NonMatchingSegment_JumpsToDefault()
|
||||
{
|
||||
// Arrange
|
||||
var table = CreateTable(0, 1, ("text", 2));
|
||||
|
||||
// Act
|
||||
var result = table.GetDestination("text", new PathSegment(1, 2));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDestination_SegmentMatchingText_JumpsToDestination()
|
||||
{
|
||||
// Arrange
|
||||
var table = CreateTable(0, 1, ("text", 2));
|
||||
|
||||
// Act
|
||||
var result = table.GetDestination("some-text", new PathSegment(5, 4));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDestination_SegmentMatchingTextIgnoreCase_JumpsToDestination()
|
||||
{
|
||||
// Arrange
|
||||
var table = CreateTable(0, 1, ("text", 2));
|
||||
|
||||
// Act
|
||||
var result = table.GetDestination("some-tExt", new PathSegment(5, 4));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDestination_SegmentMatchingTextIgnoreCase_MultipleEntries()
|
||||
{
|
||||
// Arrange
|
||||
var table = CreateTable(0, 1, ("tezt", 2), ("text", 3));
|
||||
|
||||
// Act
|
||||
var result = table.GetDestination("some-tExt", new PathSegment(5, 4));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public class SingleEntryJumpTableTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetDestination_ZeroLengthSegment_JumpsToExit()
|
||||
{
|
||||
// Arrange
|
||||
var table = new SingleEntryJumpTable(0, 1, "text", 2);
|
||||
|
||||
// Act
|
||||
var result = table.GetDestination("ignored", new PathSegment(0, 0));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDestination_NonMatchingSegment_JumpsToDefault()
|
||||
{
|
||||
// Arrange
|
||||
var table = new SingleEntryJumpTable(0, 1, "text", 2);
|
||||
|
||||
// Act
|
||||
var result = table.GetDestination("text", new PathSegment(1, 2));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDestination_SegmentMatchingText_JumpsToDestination()
|
||||
{
|
||||
// Arrange
|
||||
var table = new SingleEntryJumpTable(0, 1, "text", 2);
|
||||
|
||||
// Act
|
||||
var result = table.GetDestination("some-text", new PathSegment(5, 4));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDestination_SegmentMatchingTextIgnoreCase_JumpsToDestination()
|
||||
{
|
||||
// Arrange
|
||||
var table = new SingleEntryJumpTable(0, 1, "text", 2);
|
||||
|
||||
// Act
|
||||
var result = table.GetDestination("some-tExt", new PathSegment(5, 4));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||
{
|
||||
public class ZeroEntryJumpTableTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetDestination_ZeroLengthSegment_JumpsToExit()
|
||||
{
|
||||
// Arrange
|
||||
var table = new ZeroEntryJumpTable(0, 1);
|
||||
|
||||
// Act
|
||||
var result = table.GetDestination("ignored", new PathSegment(0, 0));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDestination_SegmentWithLength_JumpsToDefault()
|
||||
{
|
||||
// Arrange
|
||||
var table = new ZeroEntryJumpTable(0, 1);
|
||||
|
||||
// Act
|
||||
var result = table.GetDestination("ignored", new PathSegment(0, 1));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, result);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче