[vlc-commits] [Git][videolan/vlc][master] 3 commits: macosx: Stop media-library callbacks before cold-count tests
Steve Lhomme (@robUx4)
gitlab at videolan.org
Tue Nov 3 08:04:04 UTC 2026
Steve Lhomme pushed to branch master at VideoLAN / VLC
Commits:
3d436805 by Claudio Cambra at 2026-11-03T07:42:24+00:00
macosx: Stop media-library callbacks before cold-count tests
Signed-off-by: Claudio Cambra <developer at claudiocambra.com>
- - - - -
9b62470c by Claudio Cambra at 2026-11-03T07:42:24+00:00
macosx: Check stable identities in media-library deletion tests
Signed-off-by: Claudio Cambra <developer at claudiocambra.com>
- - - - -
5c8ca8c4 by Claudio Cambra at 2026-11-03T07:42:24+00:00
macosx: Run media-library fixture SQL in a separate process
Signed-off-by: Claudio Cambra <developer at claudiocambra.com>
- - - - -
5 changed files:
- modules/gui/macosx/tests/Makefile.am
- modules/gui/macosx/tests/VLCLibraryDataTypesIntegrationTestSupport.m
- + modules/gui/macosx/tests/VLCLibraryFixtureSQL.m
- modules/gui/macosx/tests/VLCLibraryModelConcurrencyTest.m
- modules/gui/macosx/tests/VLCLibraryModelIntegrationTest.m
Changes:
=====================================
modules/gui/macosx/tests/Makefile.am
=====================================
@@ -5,7 +5,7 @@ if ENABLE_MACOSX_XCTESTS
macosx_gui_xctest_frameworks_dir = $(shell $(XCRUN) --sdk macosx --show-sdk-platform-path)/Developer/Library/Frameworks
macosx_gui_xctest_bundles = VLCMacOSXTests.xctest
-check_PROGRAMS += VLCMacOSXTests
+check_PROGRAMS += VLCMacOSXTests VLCLibraryFixtureSQL
TESTS += $(macosx_gui_xctest_bundles)
VLCMacOSXTests_SOURCES = \
@@ -105,20 +105,27 @@ VLCMacOSXTests_LDFLAGS = \
VLCMacOSXTests_LDADD = \
$(top_builddir)/lib/libvlc.la \
- $(top_builddir)/src/libvlccore.la \
- -lsqlite3
+ $(top_builddir)/src/libvlccore.la
+
+VLCLibraryFixtureSQL_SOURCES = gui/macosx/tests/VLCLibraryFixtureSQL.m
+VLCLibraryFixtureSQL_OBJCFLAGS = $(AM_OBJCFLAGS) -fobjc-arc -fobjc-exceptions -fmodules
+VLCLibraryFixtureSQL_LDFLAGS = -Wl,-framework,Foundation
+VLCLibraryFixtureSQL_LDADD = -lsqlite3
VLCMacOSXTests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(OBJCLD) \
$(VLCMacOSXTests_OBJCFLAGS) $(OBJCFLAGS) \
$(VLCMacOSXTests_LDFLAGS) $(LDFLAGS) -o $@
-VLCMacOSXTests.xctest: VLCMacOSXTests$(EXEEXT) $(srcdir)/gui/macosx/tests/VLCMacOSXTests-Info.plist
+VLCMacOSXTests.xctest: VLCMacOSXTests$(EXEEXT) VLCLibraryFixtureSQL$(EXEEXT) $(srcdir)/gui/macosx/tests/VLCMacOSXTests-Info.plist
$(AM_V_GEN)rm -rf $@
$(AM_V_at)$(MKDIR_P) $@/Contents/MacOS
$(AM_V_at)$(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install \
$(INSTALL_PROGRAM) VLCMacOSXTests$(EXEEXT) \
$(abs_builddir)/$@/Contents/MacOS/VLCMacOSXTests
+ $(AM_V_at)$(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install \
+ $(INSTALL_PROGRAM) VLCLibraryFixtureSQL$(EXEEXT) \
+ $(abs_builddir)/$@/Contents/MacOS/VLCLibraryFixtureSQL
$(AM_V_at)$(INSTALL_DATA) $(srcdir)/gui/macosx/tests/VLCMacOSXTests-Info.plist $@/Contents/Info.plist
EXTRA_DIST += gui/macosx/tests/VLCMacOSXTests-Info.plist
=====================================
modules/gui/macosx/tests/VLCLibraryDataTypesIntegrationTestSupport.m
=====================================
@@ -16,9 +16,10 @@
#import <vlc/libvlc.h>
#include <string.h>
-#include <ctype.h>
+#include <stdlib.h>
#include <dispatch/dispatch.h>
-#include <sqlite3.h>
+#include <signal.h>
+#include <fcntl.h>
#include <unistd.h>
#import "VLCLibraryDataTypesIntegrationTestSupport.h"
@@ -52,62 +53,97 @@ static int64_t sGroupID;
static BOOL sFactoryFixturesPrepared;
static NSString *sUserDataPath;
-// Match medialibrary 0.13.2's sqlite::Connection::collateFilename, including
-// its leading-number comparison and length-bounded case-insensitive suffix.
-// Adapted from SqliteConnection.cpp.
-static int VLCLibraryDataTypesIntegrationCompareFilenames(void * __unused context,
- int lhsSize,
- const void *lhs,
- int rhsSize,
- const void *rhs)
+// The media-library plugin has its own SQLite copy. Run fixture SQL in another
+// process so SQLite's process-wide file locks keep the two clients coordinated.
+static NSDictionary *VLCLibraryDataTypesIntegrationSQLStatement(NSString *sql,
+ NSArray *parameters,
+ NSUInteger minimumChanges)
{
- const char *left = lhs;
- const char *right = rhs;
- int size = MIN(lhsSize, rhsSize);
- if (size == 0)
- return lhsSize - rhsSize;
- if (!isdigit((unsigned char)*left) || !isdigit((unsigned char)*right))
- return sqlite3_strnicmp(left, right, size);
-
- int leftNumber = 0;
- while (lhsSize > 0 && isdigit((unsigned char)*left)) {
- leftNumber = leftNumber * 10 + (*left++ - '0');
- lhsSize--;
- }
- int rightNumber = 0;
- while (rhsSize > 0 && isdigit((unsigned char)*right)) {
- rightNumber = rightNumber * 10 + (*right++ - '0');
- rhsSize--;
- }
- if (leftNumber != rightNumber)
- return leftNumber - rightNumber;
- return sqlite3_strnicmp(left, right, MIN(lhsSize, rhsSize));
+ return @{ @"sql": sql, @"parameters": parameters, @"minimumChanges": @(minimumChanges) };
}
-static BOOL VLCLibraryDataTypesIntegrationOpenDatabase(sqlite3 **database)
+static BOOL VLCLibraryDataTypesIntegrationExecuteStatements(NSArray<NSDictionary *> *statements)
{
- NSString * const path = [sUserDataPath stringByAppendingPathComponent:@"ml/ml.db"];
- BOOL success = sqlite3_open_v2(path.fileSystemRepresentation,
- database,
- SQLITE_OPEN_READWRITE,
- NULL) == SQLITE_OK;
- if (success) {
- success = sqlite3_busy_timeout(*database, 5000) == SQLITE_OK &&
- sqlite3_create_collation_v2(*database,
- "FILENAME",
- SQLITE_UTF8,
- NULL,
- VLCLibraryDataTypesIntegrationCompareFilenames,
- NULL) == SQLITE_OK &&
- sqlite3_exec(*database, "PRAGMA foreign_keys = ON", NULL, NULL, NULL) == SQLITE_OK &&
- sqlite3_exec(*database, "PRAGMA recursive_triggers = ON", NULL, NULL, NULL) == SQLITE_OK;
- }
- if (!success && *database != NULL) {
- NSLog(@"Could not open fixture database: %s", sqlite3_errmsg(*database));
- sqlite3_close(*database);
- *database = NULL;
- }
- return success;
+ NSString * const databasePath = [sUserDataPath stringByAppendingPathComponent:@"ml/ml.db"];
+ NSString * const helperPath = [[[[NSBundle bundleForClass:VLCInputItem.class] executablePath]
+ stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"VLCLibraryFixtureSQL"];
+ NSError *error = nil;
+ NSData * const request = [NSJSONSerialization dataWithJSONObject:
+ @{ @"database": databasePath, @"statements": statements }
+ options:0 error:&error];
+ if (request == nil) {
+ NSLog(@"Could not encode fixture SQL request: %@", error);
+ return NO;
+ }
+
+ NSTask * const task = [[NSTask alloc] init];
+ task.executableURL = [NSURL fileURLWithPath:helperPath];
+ NSPipe * const input = NSPipe.pipe;
+ NSPipe * const output = NSPipe.pipe;
+ task.standardInput = input;
+ task.standardOutput = output;
+ // A child killed at the deadline must not send SIGPIPE to the test runner.
+ if (fcntl(input.fileHandleForWriting.fileDescriptor, F_SETNOSIGPIPE, 1) < 0) {
+ [input.fileHandleForWriting closeFile];
+ [output.fileHandleForReading closeFile];
+ NSLog(@"Could not configure fixture SQL input pipe");
+ return NO;
+ }
+ // The child reports one small JSON result; diagnostics inherit stderr.
+ dispatch_semaphore_t const finished = dispatch_semaphore_create(0);
+ task.terminationHandler = ^(NSTask * __unused terminatedTask) {
+ dispatch_semaphore_signal(finished);
+ };
+ if (![task launchAndReturnError:&error]) {
+ [input.fileHandleForWriting closeFile];
+ [output.fileHandleForReading closeFile];
+ NSLog(@"Could not launch fixture SQL helper: %@", error);
+ return NO;
+ }
+
+ dispatch_group_t const io = dispatch_group_create();
+ dispatch_queue_t const queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0);
+ __block NSString *writeError = nil;
+ __block NSString *readError = nil;
+ __block NSData *responseData = nil;
+ dispatch_group_async(io, queue, ^{
+ @try {
+ [input.fileHandleForWriting writeData:request];
+ } @catch (NSException *exception) {
+ writeError = exception.reason;
+ } @finally {
+ [input.fileHandleForWriting closeFile];
+ }
+ });
+ dispatch_group_async(io, queue, ^{
+ @try {
+ responseData = [output.fileHandleForReading readDataToEndOfFile];
+ } @catch (NSException *exception) {
+ readError = exception.reason;
+ } @finally {
+ [output.fileHandleForReading closeFile];
+ }
+ });
+ const BOOL timedOut = dispatch_semaphore_wait(finished,
+ dispatch_time(DISPATCH_TIME_NOW, 15 * NSEC_PER_SEC)) != 0;
+ if (timedOut && task.running)
+ kill(task.processIdentifier, SIGKILL);
+ // Reap the child and finish pipe I/O before the fixture database is removed.
+ [task waitUntilExit];
+ dispatch_group_wait(io, DISPATCH_TIME_FOREVER);
+ if (timedOut || writeError != nil || readError != nil) {
+ NSLog(@"Fixture SQL transport failed: %@", timedOut
+ ? @"helper exceeded its 15-second deadline" : writeError ?: readError);
+ return NO;
+ }
+ id const response = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
+ if (task.terminationStatus != EXIT_SUCCESS ||
+ ![response isKindOfClass:NSDictionary.class] || ![response[@"success"] boolValue]) {
+ NSLog(@"Fixture SQL helper failed (exit %d): %@", task.terminationStatus,
+ [response isKindOfClass:NSDictionary.class] ? response[@"error"] : error);
+ return NO;
+ }
+ return YES;
}
typedef struct
@@ -137,82 +173,28 @@ static BOOL VLCLibraryDataTypesIntegrationMediaMatchesMRL(const vlc_ml_media_t *
static BOOL VLCLibraryDataTypesIntegrationSetMovieMetadata(int64_t mediaID)
{
- /* The VLC medialibrary bridge exposes movie fields as read-only. Seed the
- * persisted media and movie rows so the wrapper conversion is tested with
- * a real video-typed movie and non-empty metadata. */
- sqlite3 *database = NULL;
- if (!VLCLibraryDataTypesIntegrationOpenDatabase(&database))
- return NO;
-
- sqlite3_stmt *statement = NULL;
- BOOL success = sqlite3_prepare_v2(database,
- "UPDATE Media SET type = ?, subtype = ? "
- "WHERE id_media = ?",
- -1,
- &statement,
- NULL) == SQLITE_OK;
- if (success) {
- sqlite3_bind_int(statement, 1, VLC_ML_MEDIA_TYPE_VIDEO);
- sqlite3_bind_int(statement, 2, VLC_ML_MEDIA_SUBTYPE_MOVIE);
- sqlite3_bind_int64(statement, 3, mediaID);
- success = sqlite3_step(statement) == SQLITE_DONE;
- }
- sqlite3_finalize(statement);
-
- if (success) {
- success = sqlite3_prepare_v2(database,
- "DELETE FROM Movie WHERE media_id = ?",
- -1,
- &statement,
- NULL) == SQLITE_OK;
- }
- if (success) {
- sqlite3_bind_int64(statement, 1, mediaID);
- success = sqlite3_step(statement) == SQLITE_DONE;
- }
- sqlite3_finalize(statement);
-
- if (success) {
- success = sqlite3_prepare_v2(database,
- "INSERT INTO Movie (media_id, summary, imdb_id) "
- "VALUES (?, ?, ?)",
- -1,
- &statement,
- NULL) == SQLITE_OK;
- }
- if (success) {
- sqlite3_bind_int64(statement, 1, mediaID);
- sqlite3_bind_text(statement, 2, "Factory movie summary", -1, SQLITE_STATIC);
- sqlite3_bind_text(statement, 3, "tt1234567", -1, SQLITE_STATIC);
- success = sqlite3_step(statement) == SQLITE_DONE;
- }
- sqlite3_finalize(statement);
- sqlite3_close(database);
- return success;
+ // The VLC bridge exposes movie fields as read-only. Seed the related rows
+ // atomically so media-library readers cannot observe a partly converted movie.
+ return VLCLibraryDataTypesIntegrationExecuteStatements(@[
+ VLCLibraryDataTypesIntegrationSQLStatement(
+ @"UPDATE Media SET type = ?1, subtype = ?2 WHERE id_media = ?3",
+ @[ @(VLC_ML_MEDIA_TYPE_VIDEO), @(VLC_ML_MEDIA_SUBTYPE_MOVIE), @(mediaID) ], 1),
+ VLCLibraryDataTypesIntegrationSQLStatement(
+ @"DELETE FROM Movie WHERE media_id = ?1", @[ @(mediaID) ], 0),
+ VLCLibraryDataTypesIntegrationSQLStatement(
+ @"INSERT INTO Movie (media_id, summary, imdb_id) VALUES (?1, ?2, ?3)",
+ @[ @(mediaID), @"Factory movie summary", @"tt1234567" ], 1),
+ ]);
}
static BOOL VLCLibraryDataTypesIntegrationSetMediaType(int64_t mediaID,
int type)
{
- sqlite3 *database = NULL;
- if (!VLCLibraryDataTypesIntegrationOpenDatabase(&database))
- return NO;
-
- sqlite3_stmt *statement = NULL;
- BOOL success = sqlite3_prepare_v2(database,
- "UPDATE Media SET type = ?, import_type = 0, "
- "is_present = 1 WHERE id_media = ?",
- -1,
- &statement,
- NULL) == SQLITE_OK;
- if (success) {
- sqlite3_bind_int(statement, 1, type);
- sqlite3_bind_int64(statement, 2, mediaID);
- success = sqlite3_step(statement) == SQLITE_DONE && sqlite3_changes(database) == 1;
- }
- sqlite3_finalize(statement);
- sqlite3_close(database);
- return success;
+ return VLCLibraryDataTypesIntegrationExecuteStatements(@[
+ VLCLibraryDataTypesIntegrationSQLStatement(
+ @"UPDATE Media SET type = ?2, import_type = 0, is_present = 1 WHERE id_media = ?1",
+ @[ @(mediaID), @(type) ], 1),
+ ]);
}
static void VLCLibraryDataTypesIntegrationMediaLibraryEvent(void *data,
@@ -888,24 +870,10 @@ static BOOL VLCLibraryDataTypesIntegrationExecuteEntityStatement(const char *sql
int64_t libraryID,
id value)
{
- sqlite3 *database = NULL;
- if (!VLCLibraryDataTypesIntegrationOpenDatabase(&database))
- return NO;
- sqlite3_stmt *statement = NULL;
- BOOL success = sqlite3_prepare_v2(database, sql, -1, &statement, NULL) == SQLITE_OK;
- if (success) {
- sqlite3_bind_int64(statement, 1, libraryID);
- if ([value isKindOfClass:NSNumber.class])
- sqlite3_bind_int64(statement, 2, [value longLongValue]);
- else if ([value isKindOfClass:NSString.class])
- sqlite3_bind_text(statement, 2, [value UTF8String], -1, SQLITE_TRANSIENT);
- success = sqlite3_step(statement) == SQLITE_DONE && sqlite3_changes(database) > 0;
- }
- if (!success)
- NSLog(@"Could not modify fixture entity %lld: %s", libraryID, sqlite3_errmsg(database));
- sqlite3_finalize(statement);
- sqlite3_close(database);
- return success;
+ NSArray * const parameters = value == nil ? @[ @(libraryID) ] : @[ @(libraryID), value ];
+ return VLCLibraryDataTypesIntegrationExecuteStatements(@[
+ VLCLibraryDataTypesIntegrationSQLStatement([NSString stringWithUTF8String:sql], parameters, 1),
+ ]);
}
int64_t VLCLibraryDataTypesIntegrationCreateExternalMediaOfType(const char *mrl, vlc_ml_media_type_t type)
@@ -1057,37 +1025,16 @@ BOOL VLCLibraryDataTypesIntegrationMoveMediaToAlbum(int64_t mediaID, int64_t alb
BOOL VLCLibraryDataTypesIntegrationMoveMediaToArtist(int64_t mediaID, int64_t artistID)
{
- sqlite3 *database = NULL;
- if (!VLCLibraryDataTypesIntegrationOpenDatabase(&database))
- return NO;
- BOOL success = sqlite3_exec(database, "BEGIN IMMEDIATE", NULL, NULL, NULL) == SQLITE_OK;
- // The relation-table delete/insert triggers maintain both artists' real
- // track counts. Keep the media's primary artist and relation consistent.
- const char * const statements[] = {
- "DELETE FROM MediaArtistRelation WHERE media_id = ?1",
- "UPDATE Media SET artist_id = ?2 WHERE id_media = ?1",
- "INSERT INTO MediaArtistRelation (media_id, artist_id) VALUES (?1, ?2)",
- };
- for (NSUInteger index = 0; success && index < sizeof(statements) / sizeof(statements[0]); index++) {
- sqlite3_stmt *statement = NULL;
- success = sqlite3_prepare_v2(database, statements[index], -1, &statement, NULL) == SQLITE_OK;
- if (success) {
- success = sqlite3_bind_int64(statement, 1, mediaID) == SQLITE_OK;
- if (success && sqlite3_bind_parameter_count(statement) > 1)
- success = sqlite3_bind_int64(statement, 2, artistID) == SQLITE_OK;
- if (success)
- success = sqlite3_step(statement) == SQLITE_DONE && sqlite3_changes(database) > 0;
- }
- sqlite3_finalize(statement);
- }
- if (success)
- success = sqlite3_exec(database, "COMMIT", NULL, NULL, NULL) == SQLITE_OK;
- if (!success) {
- NSLog(@"Could not move fixture media %lld to artist %lld: %s", mediaID, artistID, sqlite3_errmsg(database));
- sqlite3_exec(database, "ROLLBACK", NULL, NULL, NULL);
- }
- sqlite3_close(database);
- return success;
+ // Keep the primary artist and trigger-maintained relation counts consistent.
+ return VLCLibraryDataTypesIntegrationExecuteStatements(@[
+ VLCLibraryDataTypesIntegrationSQLStatement(
+ @"DELETE FROM MediaArtistRelation WHERE media_id = ?1", @[ @(mediaID) ], 1),
+ VLCLibraryDataTypesIntegrationSQLStatement(
+ @"UPDATE Media SET artist_id = ?2 WHERE id_media = ?1", @[ @(mediaID), @(artistID) ], 1),
+ VLCLibraryDataTypesIntegrationSQLStatement(
+ @"INSERT INTO MediaArtistRelation (media_id, artist_id) VALUES (?1, ?2)",
+ @[ @(mediaID), @(artistID) ], 1),
+ ]);
}
BOOL VLCLibraryDataTypesIntegrationSetMovieSummary(int64_t mediaID, NSString *summary)
=====================================
modules/gui/macosx/tests/VLCLibraryFixtureSQL.m
=====================================
@@ -0,0 +1,161 @@
+/****************************************************************************
+ * VLCLibraryFixtureSQL.m: isolated SQL mutations for medialibrary tests
+ ****************************************************************************
+ * Copyright (C) 2026 VLC authors and VideoLAN
+ *
+ * Authors: Claudio Cambra <developer at claudiocambra.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any later
+ * version.
+ *****************************************************************************/
+
+#import <Foundation/Foundation.h>
+
+#include <ctype.h>
+#include <sqlite3.h>
+#include <stdlib.h>
+
+// Match medialibrary's FILENAME collation, including leading-number comparison.
+static int CompareFilenames(void * __unused context, int lhsSize, const void *lhs,
+ int rhsSize, const void *rhs)
+{
+ const char *left = lhs;
+ const char *right = rhs;
+ int size = MIN(lhsSize, rhsSize);
+ if (size == 0)
+ return lhsSize - rhsSize;
+ if (!isdigit((unsigned char)*left) || !isdigit((unsigned char)*right))
+ return sqlite3_strnicmp(left, right, size);
+
+ int leftNumber = 0;
+ while (lhsSize > 0 && isdigit((unsigned char)*left)) {
+ leftNumber = leftNumber * 10 + (*left++ - '0');
+ lhsSize--;
+ }
+ int rightNumber = 0;
+ while (rhsSize > 0 && isdigit((unsigned char)*right)) {
+ rightNumber = rightNumber * 10 + (*right++ - '0');
+ rhsSize--;
+ }
+ if (leftNumber != rightNumber)
+ return leftNumber - rightNumber;
+ return sqlite3_strnicmp(left, right, MIN(lhsSize, rhsSize));
+}
+
+static BOOL ExecuteStatement(sqlite3 *database, NSDictionary *request, NSString **error)
+{
+ NSString * const sql = request[@"sql"];
+ NSArray * const parameters = request[@"parameters"];
+ NSNumber * const minimumChanges = request[@"minimumChanges"];
+ if (![sql isKindOfClass:NSString.class] ||
+ ![parameters isKindOfClass:NSArray.class] ||
+ ![minimumChanges isKindOfClass:NSNumber.class]) {
+ *error = @"Invalid fixture SQL statement request";
+ return NO;
+ }
+ sqlite3_stmt *statement = NULL;
+ BOOL success = sqlite3_prepare_v2(database, sql.UTF8String, -1, &statement, NULL) == SQLITE_OK;
+ if (success && (NSUInteger)sqlite3_bind_parameter_count(statement) != parameters.count) {
+ *error = @"Fixture SQL parameter count does not match the statement";
+ success = NO;
+ }
+ for (NSUInteger index = 0; success && index < parameters.count; index++) {
+ id const value = parameters[index];
+ int result;
+ if ([value isKindOfClass:NSNumber.class])
+ result = sqlite3_bind_int64(statement, (int)index + 1, [value longLongValue]);
+ else if ([value isKindOfClass:NSString.class])
+ result = sqlite3_bind_text(statement, (int)index + 1, [value UTF8String], -1, SQLITE_TRANSIENT);
+ else if (value == NSNull.null)
+ result = sqlite3_bind_null(statement, (int)index + 1);
+ else {
+ *error = @"Unsupported fixture SQL parameter type";
+ success = NO;
+ break;
+ }
+ success = result == SQLITE_OK;
+ }
+ if (success)
+ success = sqlite3_step(statement) == SQLITE_DONE;
+ if (success && sqlite3_changes(database) < minimumChanges.longLongValue) {
+ *error = [NSString stringWithFormat:@"Fixture SQL changed %d rows, expected at least %@",
+ sqlite3_changes(database), minimumChanges];
+ success = NO;
+ }
+ if (!success && *error == nil)
+ *error = [NSString stringWithUTF8String:sqlite3_errmsg(database)];
+ sqlite3_finalize(statement);
+ return success;
+}
+
+static BOOL ExecuteRequest(NSDictionary *request, NSString **error)
+{
+ NSString * const path = request[@"database"];
+ NSArray * const statements = request[@"statements"];
+ if (![path isKindOfClass:NSString.class] || path.length == 0 ||
+ ![statements isKindOfClass:NSArray.class] || statements.count == 0) {
+ *error = @"Invalid fixture SQL request";
+ return NO;
+ }
+ sqlite3 *database = NULL;
+ BOOL success = sqlite3_open_v2(path.fileSystemRepresentation, &database,
+ SQLITE_OPEN_READWRITE, NULL) == SQLITE_OK;
+ if (success) {
+ success = sqlite3_busy_timeout(database, 5000) == SQLITE_OK &&
+ sqlite3_create_collation_v2(database, "FILENAME", SQLITE_UTF8, NULL,
+ CompareFilenames, NULL) == SQLITE_OK &&
+ sqlite3_exec(database, "PRAGMA foreign_keys = ON", NULL, NULL, NULL) == SQLITE_OK &&
+ sqlite3_exec(database, "PRAGMA recursive_triggers = ON", NULL, NULL, NULL) == SQLITE_OK &&
+ sqlite3_exec(database, "BEGIN IMMEDIATE", NULL, NULL, NULL) == SQLITE_OK;
+ }
+ for (NSUInteger index = 0; success && index < statements.count; index++) {
+ id const statement = statements[index];
+ if (![statement isKindOfClass:NSDictionary.class]) {
+ *error = @"Invalid fixture SQL statement request";
+ success = NO;
+ } else {
+ success = ExecuteStatement(database, statement, error);
+ }
+ if (!success)
+ *error = [NSString stringWithFormat:@"Statement %lu: %@", (unsigned long)index + 1, *error];
+ }
+ if (success)
+ success = sqlite3_exec(database, "COMMIT", NULL, NULL, NULL) == SQLITE_OK;
+ if (!success) {
+ if (*error == nil)
+ *error = database == NULL ? @"Could not open fixture database" :
+ [NSString stringWithUTF8String:sqlite3_errmsg(database)];
+ if (database != NULL)
+ sqlite3_exec(database, "ROLLBACK", NULL, NULL, NULL);
+ }
+ if (database != NULL) {
+ const int result = sqlite3_close(database);
+ if (success && result != SQLITE_OK) {
+ *error = @"Could not close fixture database";
+ success = NO;
+ }
+ }
+ return success;
+}
+
+int main(void)
+{
+ @autoreleasepool {
+ NSError *parseError = nil;
+ NSData * const data = [NSFileHandle.fileHandleWithStandardInput readDataToEndOfFile];
+ id const request = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
+ NSString *error = nil;
+ BOOL success = NO;
+ if ([request isKindOfClass:NSDictionary.class])
+ success = ExecuteRequest(request, &error);
+ else
+ error = parseError.localizedDescription ?: @"Invalid fixture SQL request";
+ NSDictionary * const response = success ? @{ @"success": @YES } :
+ @{ @"success": @NO, @"error": error ?: @"Fixture SQL operation failed" };
+ NSData * const responseData = [NSJSONSerialization dataWithJSONObject:response options:0 error:NULL];
+ [NSFileHandle.fileHandleWithStandardOutput writeData:responseData];
+ return success ? EXIT_SUCCESS : EXIT_FAILURE;
+ }
+}
=====================================
modules/gui/macosx/tests/VLCLibraryModelConcurrencyTest.m
=====================================
@@ -1916,6 +1916,22 @@ typedef NS_ENUM(NSUInteger, VLCLibraryModelOverlapChange) {
NSNotificationName const recentDeletion = audio ? VLCLibraryModelRecentAudioMediaItemDeleted : VLCLibraryModelRecentsMediaItemDeleted;
[changes addObject:[self expectChangesNamed:mainDeletion IDs:@[ @(pendingID) ]]];
[changes addObject:[self expectChangesNamed:recentDeletion IDs:@[ @(pendingID) ]]];
+ // Controlled event delivery keeps these seeded snapshots stable until deletion.
+ NSArray * const deletedItems = @[
+ [self itemWithID:pendingID inItems:self.fixture.cachedItems],
+ [self itemWithID:pendingID inItems:[self recentItemsForKind:kind]],
+ ];
+ NSArray<NSNotificationName> * const deletionNames = @[ mainDeletion, recentDeletion ];
+ [deletionNames enumerateObjectsUsingBlock:^(NSNotificationName name, NSUInteger index, BOOL * __unused stop) {
+ id const cachedItem = deletedItems[index];
+ [self observeNotification:name
+ object:nil
+ queue:NSOperationQueue.mainQueue
+ usingBlock:^(NSNotification *notification) {
+ if ([notification.object libraryID] == pendingID)
+ XCTAssertEqual(notification.object, cachedItem);
+ }];
+ }];
}
XCTestExpectation * const queueHeld = [self expectationWithDescription:@"Shared media-cache queue held"];
[model holdWritesFollowingNextCacheReadWithHandler:^{
=====================================
modules/gui/macosx/tests/VLCLibraryModelIntegrationTest.m
=====================================
@@ -1124,7 +1124,14 @@
counter:(size_t (^)(void))counter
loader:(NSArray *(^)(void))loader
{
+ // Stop media-library callbacks after lazy construction and drain submitted work.
+ (void)self.model;
[self stopModelCallback];
+ XCTestExpectation * const settled = [self expectationWithDescription:@"Media-library callbacks settled before filtered count query"];
+ [self.model finishPendingQueriesWithCompletion:^{
+ [settled fulfill];
+ }];
+ [self waitForExpectations:@[ settled ] timeout:10.];
const struct {
SEL getter;
__unsafe_unretained NSNotificationName notification;
@@ -1377,7 +1384,10 @@
[notification.object libraryID] != libraryID)
return NO;
XCTAssertTrue(NSThread.isMainThread);
- XCTAssertEqual(notification.object, previous);
+ VLCMediaLibraryMediaItem * const deletedItem = notification.object;
+ // Media-library callbacks may replace the snapshot before deletion arrives.
+ XCTAssertEqual(deletedItem.libraryID, previous.libraryID);
+ XCTAssertEqual(deletedItem.mediaType, type);
NSArray * const cache = audio
? self.model.listOfRecentAudioMedia
: self.model.listOfRecentMedia;
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/9697b3130204ca1782b08f8c5bfdfd1390aab811...5c8ca8c49a606d4a59553b9f77dfdf78da241037
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/9697b3130204ca1782b08f8c5bfdfd1390aab811...5c8ca8c49a606d4a59553b9f77dfdf78da241037
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help
More information about the vlc-commits
mailing list