Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add helper class to watch for beatmap metadata changes #37

Merged
merged 8 commits into from
Mar 17, 2025
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
24 changes: 24 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"version": 1,
"isRoot": true,
"tools": {
"jetbrains.resharper.globaltools": {
"version": "2023.3.3",
"commands": [
"jb"
]
},
"nvika": {
"version": "4.0.0",
"commands": [
"nvika"
]
},
"codefilesanity": {
"version": "0.0.36",
"commands": [
"CodeFileSanity"
]
}
}
}
71 changes: 48 additions & 23 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,36 +1,61 @@
name: .NET Core

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
on: [push, pull_request]
name: Continuous Integration
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
unit-tests:
inspect-code:
name: Code Quality
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install .NET 8.0.x
uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.0.x"

services:
redis:
image: redis
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
- name: Restore Tools
run: dotnet tool restore

- name: Restore Packages
run: dotnet restore

- name: CodeFileSanity
run: |
# TODO: Add ignore filters and GitHub Workflow Command Reporting in CFS. That way we don't have to do this workaround.
# FIXME: Suppress warnings from templates project
exit_code=0
while read -r line; do
if [[ ! -z "$line" ]]; then
echo "::error::$line"
exit_code=1
fi
done <<< $(dotnet codefilesanity)
exit $exit_code

- name: InspectCode
run: dotnet jb inspectcode $(pwd)/osu.Server.QueueProcessor.sln --build --output="inspectcodereport.xml" --caches-home="inspectcode" --verbosity=WARN

- name: NVika
run: dotnet nvika parsereport "${{github.workspace}}/inspectcodereport.xml" --treatwarningsaserrors

test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install .NET 8.0.x
uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.0.x"
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore

- name: Docker compose
run: docker compose up -d

- name: Test
run: dotnet test --no-restore --verbosity normal
run: dotnet test
66 changes: 66 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
version: '3.9'

x-env: &x-env
DB_CONNECTION_STRING: Server=db;Database=osu;Uid=osuweb;
DB_HOST: db
DB_USERNAME: 'root'
APP_ENV: 'local'
GITHUB_TOKEN: "${GITHUB_TOKEN}"
BROADCAST_DRIVER: redis
CACHE_DRIVER: redis
NOTIFICATION_REDIS_HOST: redis
REDIS_HOST: redis
SESSION_DRIVER: redis
MYSQL_DATABASE: 'osu'
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
MYSQL_ROOT_HOST: '%'

services:
# just a placeholder service to ensure we wait for migrator to complete successfully.
ready_for_use:
image: hello-world:latest
depends_on:
migrator:
condition: service_completed_successfully

migrator:
image: pppy/osu-web:latest-dev
command: ['artisan', 'db:setup']
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
environment:
<<: *x-env

db:
image: mysql/mysql-server:8.0
environment:
<<: *x-env
volumes:
- database:/var/lib/mysql
ports:
- "${MYSQL_EXTERNAL_PORT:-3306}:3306"
command: --default-authentication-plugin=mysql_native_password
healthcheck:
# important to use 127.0.0.1 instead of localhost as mysql starts twice.
# the first time it listens on sockets but isn't actually ready
# see https://github.com/docker-library/mysql/issues/663
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1"]
interval: 1s
timeout: 60s
start_period: 60s

redis:
image: redis:latest
ports:
- "${REDIS_EXTERNAL_PORT:-6379}:6379"
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 1s
timeout: 60s
start_period: 60s

volumes:
database:
7 changes: 7 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"sdk": {
"version": "8.0.100",
"rollForward": "latestFeature",
"allowPrerelease": false
}
}
93 changes: 93 additions & 0 deletions osu.Server.QueueProcessor.Tests/BeatmapStatusWatcherTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Threading;
using System.Threading.Tasks;
using Dapper;
using Xunit;

namespace osu.Server.QueueProcessor.Tests
{
public class BeatmapStatusWatcherTests
{
/// <summary>
/// Checking that processing an empty queue works as expected.
/// </summary>
[Fact]
public async Task TestBasic()
{
var cts = new CancellationTokenSource(10000);

TaskCompletionSource<BeatmapUpdates> tcs = new TaskCompletionSource<BeatmapUpdates>();
using var db = await DatabaseAccess.GetConnectionAsync(cts.Token);

// just a safety measure for now to ensure we don't hit production. since i was running on production until now.
// will throw if not on test database.
if (db.QueryFirstOrDefault<int?>("SELECT `count` FROM `osu_counts` WHERE `name` = 'is_production'") != null)
throw new InvalidOperationException("You are trying to do something very silly.");

await db.ExecuteAsync("TRUNCATE TABLE `bss_process_queue`");

using var poller = await BeatmapStatusWatcher.StartPollingAsync(updates => { tcs.SetResult(updates); }, pollMilliseconds: 100);

await db.ExecuteAsync("INSERT INTO `bss_process_queue` (beatmapset_id) VALUES (1)");

var updates = await tcs.Task.WaitAsync(cts.Token);

Assert.Equal(new[] { 1 }, updates.BeatmapSetIDs);
Assert.Equal(1, updates.LastProcessedQueueID);

tcs = new TaskCompletionSource<BeatmapUpdates>();

await db.ExecuteAsync("INSERT INTO `bss_process_queue` (beatmapset_id) VALUES (2), (3)");

updates = await tcs.Task.WaitAsync(cts.Token);

Assert.Equal(new[] { 2, 3 }, updates.BeatmapSetIDs);
Assert.Equal(3, updates.LastProcessedQueueID);
}

/// <summary>
/// Checking that processing an empty queue works as expected.
/// </summary>
[Fact]
public async Task TestLimit()
{
var cts = new CancellationTokenSource(10000);

TaskCompletionSource<BeatmapUpdates> tcs = new TaskCompletionSource<BeatmapUpdates>();
using var db = await DatabaseAccess.GetConnectionAsync(cts.Token);

// just a safety measure for now to ensure we don't hit production. since i was running on production until now.
// will throw if not on test database.
if (db.QueryFirstOrDefault<int?>("SELECT `count` FROM `osu_counts` WHERE `name` = 'is_production'") != null)
throw new InvalidOperationException("You are trying to do something very silly.");

await db.ExecuteAsync("TRUNCATE TABLE `bss_process_queue`");

using var poller = await BeatmapStatusWatcher.StartPollingAsync(updates => { tcs.SetResult(updates); }, limit: 1, pollMilliseconds: 100);

await db.ExecuteAsync("INSERT INTO `bss_process_queue` (beatmapset_id) VALUES (1)");

var updates = await tcs.Task.WaitAsync(cts.Token);
tcs = new TaskCompletionSource<BeatmapUpdates>();

Assert.Equal(new[] { 1 }, updates.BeatmapSetIDs);
Assert.Equal(1, updates.LastProcessedQueueID);

await db.ExecuteAsync("INSERT INTO `bss_process_queue` (beatmapset_id) VALUES (2), (3)");

updates = await tcs.Task.WaitAsync(cts.Token);
tcs = new TaskCompletionSource<BeatmapUpdates>();

Assert.Equal(new[] { 2 }, updates.BeatmapSetIDs);
Assert.Equal(2, updates.LastProcessedQueueID);

updates = await tcs.Task.WaitAsync(cts.Token);

Assert.Equal(new[] { 3 }, updates.BeatmapSetIDs);
Assert.Equal(3, updates.LastProcessedQueueID);
}
}
}
Loading
Loading