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
5 changes: 2 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ Common commands:
- `task test:integration` (integration tests only)

- Filter tests:
- By class: `--filter "FullyQualifiedName~ClassName"`
- By method: `--filter "FullyQualifiedName~MethodName"`
- By display name pattern: `--filter "DisplayName~Pattern"`
- By class: `task test:unit filter=MyTestClass`
- By method: `task test:integration filter=MyMethodName`

- Coverage and reports (preferred via Task):
- `task coverage`, `task coverage:unit`, `task coverage:integration`
Expand Down
8 changes: 6 additions & 2 deletions DEVELOPER.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,12 @@ In Windows, run from following commands from the appropriate Visual Studio Comma
task test

# Run specific test suites
task test:unit # Unit tests only
task test:integration # Integration tests only
task test:unit
task test:integration

# Run specific test classes or methods
task test:unit filter=MyTestClass
task test:integration filter=MyMethodName
Comment thread
currantw marked this conversation as resolved.

# Run tests with coverage and generate reports
task coverage # All tests with coverage
Expand Down
22 changes: 17 additions & 5 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ vars:
UNIT_TEST_PROJECT: tests/Valkey.Glide.UnitTests/Valkey.Glide.UnitTests.csproj
INTEGRATION_TEST_PROJECT: tests/Valkey.Glide.IntegrationTests/Valkey.Glide.IntegrationTests.csproj

# Build options
# Build the solution or a specific target.
# Usage: `task build target=lib`
TARGET: '{{.target | default "all"}}'
TARGET_PATH: '{{if eq .TARGET "lib"}}sources/Valkey.Glide/{{end}}'
BUILD_TARGET: '{{if eq .TARGET "lib"}}sources/Valkey.Glide/{{end}}'

# Filter tests by class or method name.
# Usage: `task test:unit filter=MyTestClass`
FILTER: '{{.filter | default ""}}'
TEST_FILTER: "{{if .FILTER}}--filter FullyQualifiedName~{{.FILTER}}{{end}}"
Comment thread
currantw marked this conversation as resolved.

tasks:
clean:
Expand All @@ -31,13 +37,19 @@ tasks:
desc: Run unit tests only
deps: [clean]
cmds:
- dotnet test {{.UNIT_TEST_PROJECT}} --configuration Release --verbosity normal
- dotnet test {{.UNIT_TEST_PROJECT}}
--configuration Release
--verbosity normal
{{.TEST_FILTER}}

test:integration:
desc: Run integration tests only
deps: [clean]
cmds:
- dotnet test {{.INTEGRATION_TEST_PROJECT}} --configuration Release --verbosity normal
- dotnet test {{.INTEGRATION_TEST_PROJECT}}
--configuration Release
--verbosity normal
{{.TEST_FILTER}}

test:coverage:unit:
desc: Run unit tests with coverage collection
Expand Down Expand Up @@ -146,7 +158,7 @@ tasks:

build:
desc: Build the solution
cmd: dotnet build {{.TARGET_PATH}} --configuration Release
cmd: dotnet build {{.BUILD_TARGET}} --configuration Release

test:
desc: Build and run all tests
Expand Down
16 changes: 8 additions & 8 deletions sources/Valkey.Glide/Abstract/ValkeyServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,13 @@ public async Task<string> LolwutAsync(CommandFlags flags = CommandFlags.None)

public async Task<bool> ScriptExistsAsync(string script, CommandFlags flags = CommandFlags.None)
{
GuardClauses.ThrowIfCommandFlags(flags);

if (string.IsNullOrEmpty(script))
{
throw new ArgumentException("Script cannot be null or empty", nameof(script));
}

GuardClauses.ThrowIfCommandFlags(flags);

// Calculate SHA1 hash of the script
using Script scriptObj = new(script);
string hash = scriptObj.Hash;
Expand All @@ -181,13 +181,13 @@ public async Task<bool> ScriptExistsAsync(string script, CommandFlags flags = Co

public async Task<bool> ScriptExistsAsync(byte[] sha1, CommandFlags flags = CommandFlags.None)
{
GuardClauses.ThrowIfCommandFlags(flags);

if (sha1 == null || sha1.Length == 0)
{
throw new ArgumentException("SHA1 hash cannot be null or empty", nameof(sha1));
}

GuardClauses.ThrowIfCommandFlags(flags);

// Convert byte array to hex string
string hash = BitConverter.ToString(sha1).Replace("-", "").ToLowerInvariant();

Expand All @@ -198,13 +198,13 @@ public async Task<bool> ScriptExistsAsync(byte[] sha1, CommandFlags flags = Comm

public async Task<byte[]> ScriptLoadAsync(string script, CommandFlags flags = CommandFlags.None)
{
GuardClauses.ThrowIfCommandFlags(flags);

if (string.IsNullOrEmpty(script))
{
throw new ArgumentException("Script cannot be null or empty", nameof(script));
}

GuardClauses.ThrowIfCommandFlags(flags);

// Use custom command to call SCRIPT LOAD
ValkeyResult result = await ExecuteAsync("SCRIPT", ["LOAD", script], flags);
string? hashString = (string?)result;
Expand All @@ -220,13 +220,13 @@ public async Task<byte[]> ScriptLoadAsync(string script, CommandFlags flags = Co

public async Task<LoadedLuaScript> ScriptLoadAsync(LuaScript script, CommandFlags flags = CommandFlags.None)
{
GuardClauses.ThrowIfCommandFlags(flags);

if (script == null)
{
throw new ArgumentNullException(nameof(script));
}

GuardClauses.ThrowIfCommandFlags(flags);

// Load the executable script
byte[] hash = await ScriptLoadAsync(script.ExecutableScript, flags);
return new LoadedLuaScript(script, hash, script.ExecutableScript);
Expand Down
59 changes: 18 additions & 41 deletions tests/Valkey.Glide.IntegrationTests/ClientSideCacheTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,10 @@ public class ClientSideCacheTests
{
#region Helpers

private static async Task<GlideClient> CreateStandaloneClientWithCache(ClientSideCacheConfig cache)
{
var config = TestConfiguration.DefaultClientConfig()
.WithClientSideCache(cache)
.Build();
return await GlideClient.CreateClient(config);
}

private static async Task<GlideClusterClient> CreateClusterClientWithCache(ClientSideCacheConfig cache)
{
var config = TestConfiguration.DefaultClusterClientConfig()
.WithClientSideCache(cache)
.Build();
return await GlideClusterClient.CreateClient(config);
}

private static async Task<BaseClient> CreateClientWithCache(ClientSideCacheConfig cache, bool clusterMode)
{
if (clusterMode)
{
return await CreateClusterClientWithCache(cache);
}

return await CreateStandaloneClientWithCache(cache);
}
=> clusterMode
? await GlideClusterClient.CreateClient(TestConfiguration.DefaultClusterClientConfig().WithClientSideCache(cache).Build())
: await GlideClient.CreateClient(TestConfiguration.DefaultClientConfig().WithClientSideCache(cache).Build());

#endregion

Expand Down Expand Up @@ -112,11 +91,11 @@ public async Task WithoutMetrics_MetricCallsFail(bool clusterMode)
Assert.Equal("value", value.ToString());

// Metrics that require EnableMetrics should fail
_ = await Assert.ThrowsAsync<Errors.RequestException>(() => client.GetCacheHitRateAsync());
_ = await Assert.ThrowsAsync<Errors.RequestException>(() => client.GetCacheMissRateAsync());
_ = await Assert.ThrowsAsync<Errors.RequestException>(() => client.GetCacheEvictionsAsync());
_ = await Assert.ThrowsAsync<Errors.RequestException>(() => client.GetCacheExpirationsAsync());
_ = await Assert.ThrowsAsync<Errors.RequestException>(() => client.GetCacheTotalLookupsAsync());
_ = await Assert.ThrowsAsync<Errors.RequestException>(client.GetCacheHitRateAsync);
_ = await Assert.ThrowsAsync<Errors.RequestException>(client.GetCacheMissRateAsync);
_ = await Assert.ThrowsAsync<Errors.RequestException>(client.GetCacheEvictionsAsync);
_ = await Assert.ThrowsAsync<Errors.RequestException>(client.GetCacheExpirationsAsync);
_ = await Assert.ThrowsAsync<Errors.RequestException>(client.GetCacheTotalLookupsAsync);

// Entry count should still work (doesn't require metrics)
long entryCount = await client.GetCacheEntryCountAsync();
Expand All @@ -132,12 +111,12 @@ public async Task WithoutMetrics_MetricCallsFail(bool clusterMode)
public async Task NoCacheConfigured_AllMetricsFail(BaseClient client)
{
// Without cache configured, all metric calls should fail
_ = await Assert.ThrowsAsync<Errors.RequestException>(() => client.GetCacheHitRateAsync());
_ = await Assert.ThrowsAsync<Errors.RequestException>(() => client.GetCacheMissRateAsync());
_ = await Assert.ThrowsAsync<Errors.RequestException>(() => client.GetCacheEntryCountAsync());
_ = await Assert.ThrowsAsync<Errors.RequestException>(() => client.GetCacheEvictionsAsync());
_ = await Assert.ThrowsAsync<Errors.RequestException>(() => client.GetCacheExpirationsAsync());
_ = await Assert.ThrowsAsync<Errors.RequestException>(() => client.GetCacheTotalLookupsAsync());
_ = await Assert.ThrowsAsync<Errors.RequestException>(client.GetCacheHitRateAsync);
Comment thread
currantw marked this conversation as resolved.
_ = await Assert.ThrowsAsync<Errors.RequestException>(client.GetCacheMissRateAsync);
_ = await Assert.ThrowsAsync<Errors.RequestException>(client.GetCacheEntryCountAsync);
_ = await Assert.ThrowsAsync<Errors.RequestException>(client.GetCacheEvictionsAsync);
_ = await Assert.ThrowsAsync<Errors.RequestException>(client.GetCacheExpirationsAsync);
_ = await Assert.ThrowsAsync<Errors.RequestException>(client.GetCacheTotalLookupsAsync);
}

#endregion
Expand Down Expand Up @@ -273,9 +252,8 @@ public async Task EvictionPolicyLRU(bool clusterMode)
Assert.Equal(largeValue, value.ToString());
}

// Verify 2 evictions occurred
long evictions = await client.GetCacheEvictionsAsync();
Assert.Equal(2, evictions);
// Verify at least 2 evictions occurred (possibly more due to internal overhead)
Assert.True(await client.GetCacheEvictionsAsync() >= 2);
Comment thread
currantw marked this conversation as resolved.

// Verify cache is working (hit rate > 0)
double hitRate = await client.GetCacheHitRateAsync();
Expand Down Expand Up @@ -356,9 +334,8 @@ public async Task EvictionPolicyLFU(bool clusterMode)
entryCount = await client.GetCacheEntryCountAsync();
Assert.Equal(3, entryCount);

// Verify 1 eviction occurred
long evictions = await client.GetCacheEvictionsAsync();
Assert.Equal(1, evictions);
// Verify at least 1 eviction occurred (possibly more due to internal overhead)
Assert.True(await client.GetCacheEvictionsAsync() >= 1);

// Check that key1 (highest frequency) is still cached
double oldHitRate = await client.GetCacheHitRateAsync();
Expand Down
15 changes: 10 additions & 5 deletions tests/Valkey.Glide.IntegrationTests/ServerModules/FtInfoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,9 @@ public async Task InfoLocalAsync_TextFieldAttributes(BaseClient client)
await SkipUtils.IfSearchModuleNotLoaded(client);

var index = Guid.NewGuid().ToString();
var prefix = $"{index}:";
var field = new Ft.CreateTextField("$.title", "title") { NoStem = true, WithSuffixTrie = false };
await Ft.CreateAsync(client, index, field);
await Ft.CreateAsync(client, index, field, new Ft.CreateOptions { Prefixes = [prefix] });
Comment thread
currantw marked this conversation as resolved.

var info = await Ft.InfoLocalAsync(client, index);
var attr = Assert.IsType<Ft.InfoTextField>(Assert.Single(info.Attributes));
Expand All @@ -122,8 +123,9 @@ public async Task InfoLocalAsync_TagFieldAttributes(BaseClient client)
await SkipUtils.IfSearchModuleNotLoaded(client);

var index = Guid.NewGuid().ToString();
var prefix = $"{index}:";
var field = new Ft.CreateTagField("category") { Separator = '|', CaseSensitive = true };
await Ft.CreateAsync(client, index, field);
await Ft.CreateAsync(client, index, field, new Ft.CreateOptions { Prefixes = [prefix] });

var info = await Ft.InfoLocalAsync(client, index);
var attr = Assert.IsType<Ft.InfoTagField>(Assert.Single(info.Attributes));
Expand All @@ -144,8 +146,9 @@ public async Task InfoLocalAsync_NumericFieldAttributes(BaseClient client)
await SkipUtils.IfSearchModuleNotLoaded(client);

var index = Guid.NewGuid().ToString();
var prefix = $"{index}:";
var field = new Ft.CreateNumericField("price");
await Ft.CreateAsync(client, index, field);
await Ft.CreateAsync(client, index, field, new Ft.CreateOptions { Prefixes = [prefix] });

var info = await Ft.InfoLocalAsync(client, index);
var attr = Assert.IsType<Ft.InfoNumericField>(Assert.Single(info.Attributes));
Expand All @@ -162,13 +165,14 @@ public async Task InfoLocalAsync_VectorFieldFlatAttributes(BaseClient client)
await SkipUtils.IfSearchModuleNotLoaded(client);

var index = Guid.NewGuid().ToString();
var prefix = $"{index}:";
var field = new Ft.CreateVectorFieldFlat
{
Identifier = "embedding",
Dimensions = 128,
DistanceMetric = Ft.DistanceMetric.Cosine,
};
await Ft.CreateAsync(client, index, field);
await Ft.CreateAsync(client, index, field, new Ft.CreateOptions { Prefixes = [prefix] });

var info = await Ft.InfoLocalAsync(client, index);
var attr = Assert.IsType<Ft.InfoVectorFieldFlat>(Assert.Single(info.Attributes));
Expand All @@ -190,6 +194,7 @@ public async Task InfoLocalAsync_VectorFieldHnswAttributes(BaseClient client)
await SkipUtils.IfSearchModuleNotLoaded(client);

var index = Guid.NewGuid().ToString();
var prefix = $"{index}:";
var field = new Ft.CreateVectorFieldHnsw
{
Identifier = "vec",
Expand All @@ -199,7 +204,7 @@ public async Task InfoLocalAsync_VectorFieldHnswAttributes(BaseClient client)
VectorsExaminedOnConstruction = 200,
VectorsExaminedOnRuntime = 10,
};
await Ft.CreateAsync(client, index, field);
await Ft.CreateAsync(client, index, field, new Ft.CreateOptions { Prefixes = [prefix] });

var info = await Ft.InfoLocalAsync(client, index);
var attr = Assert.IsType<Ft.InfoVectorFieldHnsw>(Assert.Single(info.Attributes));
Expand Down
Loading
Loading