|
@@ -6,116 +6,116 @@ import { execSync } from 'child_process';
|
|
|
import { fileURLToPath } from 'url';
|
|
|
|
|
|
// Logging functions following formatters/logging/README.md style
|
|
|
-function log_title(title: string): void {
|
|
|
- console.error(`\x1b[1m${title}\x1b[0m`);
|
|
|
+function log_title (title: string): void {
|
|
|
+ console.error(`\x1b[1m${title}\x1b[0m`);
|
|
|
}
|
|
|
|
|
|
-function log_action(action: string): void {
|
|
|
- process.stderr.write(` ${action}... `);
|
|
|
+function log_action (action: string): void {
|
|
|
+ process.stderr.write(` ${action}... `);
|
|
|
}
|
|
|
|
|
|
-function log_ok(): void {
|
|
|
- console.error('\x1b[32m✓\x1b[0m');
|
|
|
+function log_ok (): void {
|
|
|
+ console.error('\x1b[32m✓\x1b[0m');
|
|
|
}
|
|
|
|
|
|
-function log_fail(): void {
|
|
|
- console.error('\x1b[31m✗\x1b[0m');
|
|
|
+function log_fail (): void {
|
|
|
+ console.error('\x1b[31m✗\x1b[0m');
|
|
|
}
|
|
|
|
|
|
-function log_detail(detail: string): void {
|
|
|
- console.error(`\x1b[90m${detail}\x1b[0m`);
|
|
|
+function log_detail (detail: string): void {
|
|
|
+ console.error(`\x1b[90m${detail}\x1b[0m`);
|
|
|
}
|
|
|
|
|
|
-function log_summary(summary: string): void {
|
|
|
- console.error(`\x1b[1m${summary}\x1b[0m`);
|
|
|
+function log_summary (summary: string): void {
|
|
|
+ console.error(`\x1b[1m${summary}\x1b[0m`);
|
|
|
}
|
|
|
|
|
|
-function cleanOutputDirectory(): void {
|
|
|
- const outputDir = join(SPINE_ROOT, 'tests', 'output');
|
|
|
-
|
|
|
- log_action('Cleaning output directory');
|
|
|
- try {
|
|
|
- if (existsSync(outputDir)) {
|
|
|
- rmSync(outputDir, { recursive: true, force: true });
|
|
|
- }
|
|
|
- mkdirSync(outputDir, { recursive: true });
|
|
|
- log_ok();
|
|
|
- } catch (error: any) {
|
|
|
- log_fail();
|
|
|
- log_detail(`Failed to clean output directory: ${error.message}`);
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
+function cleanOutputDirectory (): void {
|
|
|
+ const outputDir = join(SPINE_ROOT, 'tests', 'output');
|
|
|
+
|
|
|
+ log_action('Cleaning output directory');
|
|
|
+ try {
|
|
|
+ if (existsSync(outputDir)) {
|
|
|
+ rmSync(outputDir, { recursive: true, force: true });
|
|
|
+ }
|
|
|
+ mkdirSync(outputDir, { recursive: true });
|
|
|
+ log_ok();
|
|
|
+ } catch (error: any) {
|
|
|
+ log_fail();
|
|
|
+ log_detail(`Failed to clean output directory: ${error.message}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-function getNewestFileTime(directory: string, pattern: string): number {
|
|
|
- try {
|
|
|
- const files = execSync(`find "${directory}" -name "${pattern}" -type f`, { encoding: 'utf8' })
|
|
|
- .trim()
|
|
|
- .split('\n')
|
|
|
- .filter(f => f);
|
|
|
-
|
|
|
- if (files.length === 0) return 0;
|
|
|
-
|
|
|
- return Math.max(...files.map(file => {
|
|
|
- try {
|
|
|
- return statSync(file).mtime.getTime();
|
|
|
- } catch {
|
|
|
- return 0;
|
|
|
- }
|
|
|
- }));
|
|
|
- } catch {
|
|
|
- return 0;
|
|
|
- }
|
|
|
+function getNewestFileTime (directory: string, pattern: string): number {
|
|
|
+ try {
|
|
|
+ const files = execSync(`find "${directory}" -name "${pattern}" -type f`, { encoding: 'utf8' })
|
|
|
+ .trim()
|
|
|
+ .split('\n')
|
|
|
+ .filter(f => f);
|
|
|
+
|
|
|
+ if (files.length === 0) return 0;
|
|
|
+
|
|
|
+ return Math.max(...files.map(file => {
|
|
|
+ try {
|
|
|
+ return statSync(file).mtime.getTime();
|
|
|
+ } catch {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }));
|
|
|
+ } catch {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-function needsJavaBuild(): boolean {
|
|
|
- const javaDir = join(SPINE_ROOT, 'spine-libgdx');
|
|
|
- const testDir = join(javaDir, 'spine-libgdx-tests');
|
|
|
- const buildDir = join(testDir, 'build', 'libs');
|
|
|
-
|
|
|
- try {
|
|
|
- // Check if fat jar exists (specifically look for the headless test jar)
|
|
|
- const fatJarFiles = execSync(`ls ${buildDir}/spine-headless-test-*.jar 2>/dev/null || true`, { encoding: 'utf8' }).trim();
|
|
|
- if (!fatJarFiles) return true;
|
|
|
-
|
|
|
- // Get jar modification time
|
|
|
- const jarTime = statSync(fatJarFiles.split('\n')[0]).mtime.getTime();
|
|
|
-
|
|
|
- // Check Java source files
|
|
|
- const javaSourceTime = getNewestFileTime(join(SPINE_ROOT, 'spine-libgdx'), '*.java');
|
|
|
-
|
|
|
- // Check Gradle files
|
|
|
- const gradleTime = getNewestFileTime(javaDir, 'build.gradle*');
|
|
|
-
|
|
|
- return javaSourceTime > jarTime || gradleTime > jarTime;
|
|
|
- } catch {
|
|
|
- return true;
|
|
|
- }
|
|
|
+function needsJavaBuild (): boolean {
|
|
|
+ const javaDir = join(SPINE_ROOT, 'spine-libgdx');
|
|
|
+ const testDir = join(javaDir, 'spine-libgdx-tests');
|
|
|
+ const buildDir = join(testDir, 'build', 'libs');
|
|
|
+
|
|
|
+ try {
|
|
|
+ // Check if fat jar exists (specifically look for the headless test jar)
|
|
|
+ const fatJarFiles = execSync(`ls ${buildDir}/spine-headless-test-*.jar 2>/dev/null || true`, { encoding: 'utf8' }).trim();
|
|
|
+ if (!fatJarFiles) return true;
|
|
|
+
|
|
|
+ // Get jar modification time
|
|
|
+ const jarTime = statSync(fatJarFiles.split('\n')[0]).mtime.getTime();
|
|
|
+
|
|
|
+ // Check Java source files
|
|
|
+ const javaSourceTime = getNewestFileTime(join(SPINE_ROOT, 'spine-libgdx'), '*.java');
|
|
|
+
|
|
|
+ // Check Gradle files
|
|
|
+ const gradleTime = getNewestFileTime(javaDir, 'build.gradle*');
|
|
|
+
|
|
|
+ return javaSourceTime > jarTime || gradleTime > jarTime;
|
|
|
+ } catch {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-function needsCppBuild(): boolean {
|
|
|
- const cppDir = join(SPINE_ROOT, 'spine-cpp');
|
|
|
- const buildDir = join(cppDir, 'build');
|
|
|
- const headlessTest = join(buildDir, 'headless-test');
|
|
|
-
|
|
|
- try {
|
|
|
- // Check if executable exists
|
|
|
- if (!existsSync(headlessTest)) return true;
|
|
|
-
|
|
|
- // Get executable modification time
|
|
|
- const execTime = statSync(headlessTest).mtime.getTime();
|
|
|
-
|
|
|
- // Check C++ source files
|
|
|
- const cppSourceTime = getNewestFileTime(join(SPINE_ROOT, 'spine-cpp'), '*.cpp');
|
|
|
- const hppSourceTime = getNewestFileTime(join(SPINE_ROOT, 'spine-cpp'), '*.h');
|
|
|
- const cmakeTime = getNewestFileTime(cppDir, 'CMakeLists.txt');
|
|
|
-
|
|
|
- const newestSourceTime = Math.max(cppSourceTime, hppSourceTime, cmakeTime);
|
|
|
-
|
|
|
- return newestSourceTime > execTime;
|
|
|
- } catch {
|
|
|
- return true;
|
|
|
- }
|
|
|
+function needsCppBuild (): boolean {
|
|
|
+ const cppDir = join(SPINE_ROOT, 'spine-cpp');
|
|
|
+ const buildDir = join(cppDir, 'build');
|
|
|
+ const headlessTest = join(buildDir, 'headless-test');
|
|
|
+
|
|
|
+ try {
|
|
|
+ // Check if executable exists
|
|
|
+ if (!existsSync(headlessTest)) return true;
|
|
|
+
|
|
|
+ // Get executable modification time
|
|
|
+ const execTime = statSync(headlessTest).mtime.getTime();
|
|
|
+
|
|
|
+ // Check C++ source files
|
|
|
+ const cppSourceTime = getNewestFileTime(join(SPINE_ROOT, 'spine-cpp'), '*.cpp');
|
|
|
+ const hppSourceTime = getNewestFileTime(join(SPINE_ROOT, 'spine-cpp'), '*.h');
|
|
|
+ const cmakeTime = getNewestFileTime(cppDir, 'CMakeLists.txt');
|
|
|
+
|
|
|
+ const newestSourceTime = Math.max(cppSourceTime, hppSourceTime, cmakeTime);
|
|
|
+
|
|
|
+ return newestSourceTime > execTime;
|
|
|
+ } catch {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -123,514 +123,504 @@ const __dirname = dirname(__filename);
|
|
|
const SPINE_ROOT = resolve(__dirname, '../..');
|
|
|
|
|
|
interface TestArgs {
|
|
|
- language: string;
|
|
|
- skeletonPath: string;
|
|
|
- atlasPath: string;
|
|
|
- animationName?: string;
|
|
|
+ language: string;
|
|
|
+ skeletonPath: string;
|
|
|
+ atlasPath: string;
|
|
|
+ animationName?: string;
|
|
|
}
|
|
|
|
|
|
interface SkeletonFiles {
|
|
|
- jsonPath: string;
|
|
|
- skelPath: string;
|
|
|
- atlasPath: string;
|
|
|
+ jsonPath: string;
|
|
|
+ skelPath: string;
|
|
|
+ atlasPath: string;
|
|
|
}
|
|
|
|
|
|
-function findSkeletonFiles(skeletonName: string): SkeletonFiles {
|
|
|
- const examplesDir = join(SPINE_ROOT, 'examples', skeletonName);
|
|
|
- const exportDir = join(examplesDir, 'export');
|
|
|
-
|
|
|
- if (!existsSync(exportDir)) {
|
|
|
- log_detail(`Export directory not found: ${exportDir}`);
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
-
|
|
|
- // Try to find skeleton files (pro first, then ess)
|
|
|
- let jsonPath: string | null = null;
|
|
|
- let skelPath: string | null = null;
|
|
|
-
|
|
|
- const proJson = join(exportDir, `${skeletonName}-pro.json`);
|
|
|
- const proSkel = join(exportDir, `${skeletonName}-pro.skel`);
|
|
|
- const essJson = join(exportDir, `${skeletonName}-ess.json`);
|
|
|
- const essSkel = join(exportDir, `${skeletonName}-ess.skel`);
|
|
|
-
|
|
|
- if (existsSync(proJson) && existsSync(proSkel)) {
|
|
|
- jsonPath = proJson;
|
|
|
- skelPath = proSkel;
|
|
|
- } else if (existsSync(essJson) && existsSync(essSkel)) {
|
|
|
- jsonPath = essJson;
|
|
|
- skelPath = essSkel;
|
|
|
- } else {
|
|
|
- log_detail(`Could not find matching .json and .skel files for ${skeletonName}`);
|
|
|
- log_detail(`Tried: ${proJson}, ${proSkel}, ${essJson}, ${essSkel}`);
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
-
|
|
|
- // Try to find atlas file (pma first, then regular)
|
|
|
- let atlasPath: string | null = null;
|
|
|
- const pmaAtlas = join(exportDir, `${skeletonName}-pma.atlas`);
|
|
|
- const regularAtlas = join(exportDir, `${skeletonName}.atlas`);
|
|
|
-
|
|
|
- if (existsSync(pmaAtlas)) {
|
|
|
- atlasPath = pmaAtlas;
|
|
|
- } else if (existsSync(regularAtlas)) {
|
|
|
- atlasPath = regularAtlas;
|
|
|
- } else {
|
|
|
- log_detail(`Could not find atlas file for ${skeletonName}`);
|
|
|
- log_detail(`Tried: ${pmaAtlas}, ${regularAtlas}`);
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
-
|
|
|
- return {
|
|
|
- jsonPath,
|
|
|
- skelPath,
|
|
|
- atlasPath
|
|
|
- };
|
|
|
+function findSkeletonFiles (skeletonName: string): SkeletonFiles {
|
|
|
+ const examplesDir = join(SPINE_ROOT, 'examples', skeletonName);
|
|
|
+ const exportDir = join(examplesDir, 'export');
|
|
|
+
|
|
|
+ if (!existsSync(exportDir)) {
|
|
|
+ log_detail(`Export directory not found: ${exportDir}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Try to find skeleton files (pro first, then ess)
|
|
|
+ let jsonPath: string | null = null;
|
|
|
+ let skelPath: string | null = null;
|
|
|
+
|
|
|
+ const proJson = join(exportDir, `${skeletonName}-pro.json`);
|
|
|
+ const proSkel = join(exportDir, `${skeletonName}-pro.skel`);
|
|
|
+ const essJson = join(exportDir, `${skeletonName}-ess.json`);
|
|
|
+ const essSkel = join(exportDir, `${skeletonName}-ess.skel`);
|
|
|
+
|
|
|
+ if (existsSync(proJson) && existsSync(proSkel)) {
|
|
|
+ jsonPath = proJson;
|
|
|
+ skelPath = proSkel;
|
|
|
+ } else if (existsSync(essJson) && existsSync(essSkel)) {
|
|
|
+ jsonPath = essJson;
|
|
|
+ skelPath = essSkel;
|
|
|
+ } else {
|
|
|
+ log_detail(`Could not find matching .json and .skel files for ${skeletonName}`);
|
|
|
+ log_detail(`Tried: ${proJson}, ${proSkel}, ${essJson}, ${essSkel}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Try to find atlas file (pma first, then regular)
|
|
|
+ let atlasPath: string | null = null;
|
|
|
+ const pmaAtlas = join(exportDir, `${skeletonName}-pma.atlas`);
|
|
|
+ const regularAtlas = join(exportDir, `${skeletonName}.atlas`);
|
|
|
+
|
|
|
+ if (existsSync(pmaAtlas)) {
|
|
|
+ atlasPath = pmaAtlas;
|
|
|
+ } else if (existsSync(regularAtlas)) {
|
|
|
+ atlasPath = regularAtlas;
|
|
|
+ } else {
|
|
|
+ log_detail(`Could not find atlas file for ${skeletonName}`);
|
|
|
+ log_detail(`Tried: ${pmaAtlas}, ${regularAtlas}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ jsonPath,
|
|
|
+ skelPath,
|
|
|
+ atlasPath
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
-function validateArgs(): { language: string; files?: SkeletonFiles; skeletonPath?: string; atlasPath?: string; animationName?: string; fixFloats?: boolean } {
|
|
|
- const args = process.argv.slice(2);
|
|
|
-
|
|
|
- if (args.length < 2) {
|
|
|
- log_detail('Usage: headless-test-runner <target-language> -s <skeleton-name> [animation-name] [-f]');
|
|
|
- log_detail(' headless-test-runner <target-language> <skeleton-path> <atlas-path> [animation-name] [-f]');
|
|
|
- log_detail('Target languages: cpp');
|
|
|
- log_detail('Flags: -f (fix floats and propertyIds to match Java values)');
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
-
|
|
|
- // Check for -f flag
|
|
|
- const fixFloats = args.includes('-f');
|
|
|
- const filteredArgs = args.filter(arg => arg !== '-f');
|
|
|
-
|
|
|
- const [language, ...restArgs] = filteredArgs;
|
|
|
-
|
|
|
- if (!['cpp'].includes(language)) {
|
|
|
- log_detail(`Invalid target language: ${language}. Must be cpp`);
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
-
|
|
|
- // Check if using -s flag
|
|
|
- if (restArgs[0] === '-s') {
|
|
|
- if (restArgs.length < 2) {
|
|
|
- log_detail('Usage: headless-test-runner <target-language> -s <skeleton-name> [animation-name]');
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
-
|
|
|
- const [, skeletonName, animationName] = restArgs;
|
|
|
- const files = findSkeletonFiles(skeletonName);
|
|
|
-
|
|
|
- return {
|
|
|
- language,
|
|
|
- files,
|
|
|
- animationName,
|
|
|
- fixFloats
|
|
|
- };
|
|
|
- } else {
|
|
|
- // Explicit paths mode
|
|
|
- if (restArgs.length < 2) {
|
|
|
- log_detail('Usage: headless-test-runner <target-language> <skeleton-path> <atlas-path> [animation-name]');
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
-
|
|
|
- const [skeletonPath, atlasPath, animationName] = restArgs;
|
|
|
- const resolvedSkeletonPath = resolve(skeletonPath);
|
|
|
- const resolvedAtlasPath = resolve(atlasPath);
|
|
|
-
|
|
|
- if (!existsSync(resolvedSkeletonPath)) {
|
|
|
- log_detail(`Skeleton file not found: ${resolvedSkeletonPath}`);
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
-
|
|
|
- if (!existsSync(resolvedAtlasPath)) {
|
|
|
- log_detail(`Atlas file not found: ${resolvedAtlasPath}`);
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
-
|
|
|
- return {
|
|
|
- language,
|
|
|
- skeletonPath: resolvedSkeletonPath,
|
|
|
- atlasPath: resolvedAtlasPath,
|
|
|
- animationName,
|
|
|
- fixFloats
|
|
|
- };
|
|
|
- }
|
|
|
+function validateArgs (): { language: string; files?: SkeletonFiles; skeletonPath?: string; atlasPath?: string; animationName?: string; fixFloats?: boolean } {
|
|
|
+ const args = process.argv.slice(2);
|
|
|
+
|
|
|
+ if (args.length < 2) {
|
|
|
+ log_detail('Usage: headless-test-runner <target-language> -s <skeleton-name> [animation-name] [-f]');
|
|
|
+ log_detail(' headless-test-runner <target-language> <skeleton-path> <atlas-path> [animation-name] [-f]');
|
|
|
+ log_detail('Target languages: cpp');
|
|
|
+ log_detail('Flags: -f (fix floats and propertyIds to match Java values)');
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check for -f flag
|
|
|
+ const fixFloats = args.includes('-f');
|
|
|
+ const filteredArgs = args.filter(arg => arg !== '-f');
|
|
|
+
|
|
|
+ const [language, ...restArgs] = filteredArgs;
|
|
|
+
|
|
|
+ if (!['cpp'].includes(language)) {
|
|
|
+ log_detail(`Invalid target language: ${language}. Must be cpp`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if using -s flag
|
|
|
+ if (restArgs[0] === '-s') {
|
|
|
+ if (restArgs.length < 2) {
|
|
|
+ log_detail('Usage: headless-test-runner <target-language> -s <skeleton-name> [animation-name]');
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ const [, skeletonName, animationName] = restArgs;
|
|
|
+ const files = findSkeletonFiles(skeletonName);
|
|
|
+
|
|
|
+ return {
|
|
|
+ language,
|
|
|
+ files,
|
|
|
+ animationName,
|
|
|
+ fixFloats
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ // Explicit paths mode
|
|
|
+ if (restArgs.length < 2) {
|
|
|
+ log_detail('Usage: headless-test-runner <target-language> <skeleton-path> <atlas-path> [animation-name]');
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ const [skeletonPath, atlasPath, animationName] = restArgs;
|
|
|
+ const resolvedSkeletonPath = resolve(skeletonPath);
|
|
|
+ const resolvedAtlasPath = resolve(atlasPath);
|
|
|
+
|
|
|
+ if (!existsSync(resolvedSkeletonPath)) {
|
|
|
+ log_detail(`Skeleton file not found: ${resolvedSkeletonPath}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!existsSync(resolvedAtlasPath)) {
|
|
|
+ log_detail(`Atlas file not found: ${resolvedAtlasPath}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ language,
|
|
|
+ skeletonPath: resolvedSkeletonPath,
|
|
|
+ atlasPath: resolvedAtlasPath,
|
|
|
+ animationName,
|
|
|
+ fixFloats
|
|
|
+ };
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-function executeJava(args: TestArgs): string {
|
|
|
- const javaDir = join(SPINE_ROOT, 'spine-libgdx');
|
|
|
- const testDir = join(javaDir, 'spine-libgdx-tests');
|
|
|
-
|
|
|
- if (!existsSync(testDir)) {
|
|
|
- log_detail(`Java test directory not found: ${testDir}`);
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
-
|
|
|
- // Check if we need to build
|
|
|
- if (needsJavaBuild()) {
|
|
|
- // Check if we have a gradle wrapper or gradle
|
|
|
- const hasGradlew = existsSync(join(javaDir, 'gradlew'));
|
|
|
- const gradleCmd = hasGradlew ? './gradlew' : 'gradle';
|
|
|
-
|
|
|
- // Build the fat jar
|
|
|
- log_action('Building Java HeadlessTest fat jar');
|
|
|
- try {
|
|
|
- execSync(`${gradleCmd} :spine-libgdx-tests:fatJar`, {
|
|
|
- cwd: javaDir,
|
|
|
- stdio: ['inherit', 'pipe', 'inherit']
|
|
|
- });
|
|
|
- log_ok();
|
|
|
- } catch (error: any) {
|
|
|
- log_fail();
|
|
|
- log_detail(`Java build failed: ${error.message}`);
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Find the fat jar file
|
|
|
- const buildDir = join(testDir, 'build', 'libs');
|
|
|
- const fatJarFiles = execSync(`ls ${buildDir}/spine-headless-test-*.jar`, { encoding: 'utf8' }).trim().split('\n');
|
|
|
-
|
|
|
- if (fatJarFiles.length === 0) {
|
|
|
- log_detail('No fat jar files found in build/libs directory');
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
-
|
|
|
- const jarFile = fatJarFiles[0]; // Use the first fat jar file found
|
|
|
-
|
|
|
- // Run the HeadlessTest from jar
|
|
|
- const testArgs = [args.skeletonPath, args.atlasPath];
|
|
|
- if (args.animationName) {
|
|
|
- testArgs.push(args.animationName);
|
|
|
- }
|
|
|
-
|
|
|
- log_action('Running Java HeadlessTest');
|
|
|
- try {
|
|
|
- const output = execSync(`java -cp "${jarFile}" com.esotericsoftware.spine.HeadlessTest ${testArgs.join(' ')}`, {
|
|
|
- encoding: 'utf8',
|
|
|
- maxBuffer: 50 * 1024 * 1024 // 50MB buffer for large output
|
|
|
- });
|
|
|
- log_ok();
|
|
|
- return output;
|
|
|
- } catch (error: any) {
|
|
|
- log_fail();
|
|
|
- log_detail(`Java execution failed: ${error.message}`);
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
+function executeJava (args: TestArgs): string {
|
|
|
+ const javaDir = join(SPINE_ROOT, 'spine-libgdx');
|
|
|
+ const testDir = join(javaDir, 'spine-libgdx-tests');
|
|
|
+
|
|
|
+ if (!existsSync(testDir)) {
|
|
|
+ log_detail(`Java test directory not found: ${testDir}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if we need to build
|
|
|
+ if (needsJavaBuild()) {
|
|
|
+ // Check if we have a gradle wrapper or gradle
|
|
|
+ const hasGradlew = existsSync(join(javaDir, 'gradlew'));
|
|
|
+ const gradleCmd = hasGradlew ? './gradlew' : 'gradle';
|
|
|
+
|
|
|
+ // Build the fat jar
|
|
|
+ log_action('Building Java HeadlessTest fat jar');
|
|
|
+ try {
|
|
|
+ execSync(`${gradleCmd} :spine-libgdx-tests:fatJar`, {
|
|
|
+ cwd: javaDir,
|
|
|
+ stdio: ['inherit', 'pipe', 'inherit']
|
|
|
+ });
|
|
|
+ log_ok();
|
|
|
+ } catch (error: any) {
|
|
|
+ log_fail();
|
|
|
+ log_detail(`Java build failed: ${error.message}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Find the fat jar file
|
|
|
+ const buildDir = join(testDir, 'build', 'libs');
|
|
|
+ const fatJarFiles = execSync(`ls ${buildDir}/spine-headless-test-*.jar`, { encoding: 'utf8' }).trim().split('\n');
|
|
|
+
|
|
|
+ if (fatJarFiles.length === 0) {
|
|
|
+ log_detail('No fat jar files found in build/libs directory');
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ const jarFile = fatJarFiles[0]; // Use the first fat jar file found
|
|
|
+
|
|
|
+ // Run the HeadlessTest from jar
|
|
|
+ const testArgs = [args.skeletonPath, args.atlasPath];
|
|
|
+ if (args.animationName) {
|
|
|
+ testArgs.push(args.animationName);
|
|
|
+ }
|
|
|
+
|
|
|
+ log_action('Running Java HeadlessTest');
|
|
|
+ try {
|
|
|
+ const output = execSync(`java -cp "${jarFile}" com.esotericsoftware.spine.HeadlessTest ${testArgs.join(' ')}`, {
|
|
|
+ encoding: 'utf8',
|
|
|
+ maxBuffer: 50 * 1024 * 1024 // 50MB buffer for large output
|
|
|
+ });
|
|
|
+ log_ok();
|
|
|
+ return output;
|
|
|
+ } catch (error: any) {
|
|
|
+ log_fail();
|
|
|
+ log_detail(`Java execution failed: ${error.message}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-function executeCpp(args: TestArgs): string {
|
|
|
- const cppDir = join(SPINE_ROOT, 'spine-cpp');
|
|
|
- const testsDir = join(cppDir, 'tests');
|
|
|
-
|
|
|
- if (!existsSync(testsDir)) {
|
|
|
- log_detail(`C++ tests directory not found: ${testsDir}`);
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
-
|
|
|
- // Check if we need to build
|
|
|
- if (needsCppBuild()) {
|
|
|
- // Build using build.sh
|
|
|
- log_action('Building C++ tests');
|
|
|
- try {
|
|
|
- execSync('./build.sh clean release', {
|
|
|
- cwd: cppDir,
|
|
|
- stdio: ['inherit', 'pipe', 'inherit']
|
|
|
- });
|
|
|
- log_ok();
|
|
|
- } catch (error: any) {
|
|
|
- log_fail();
|
|
|
- log_detail(`C++ build failed: ${error.message}`);
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Now run the headless test directly
|
|
|
- const testArgs = [args.skeletonPath, args.atlasPath];
|
|
|
- if (args.animationName) {
|
|
|
- testArgs.push(args.animationName);
|
|
|
- }
|
|
|
-
|
|
|
- const buildDir = join(cppDir, 'build');
|
|
|
- const headlessTest = join(buildDir, 'headless-test');
|
|
|
-
|
|
|
- if (!existsSync(headlessTest)) {
|
|
|
- log_detail(`C++ headless-test executable not found: ${headlessTest}`);
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
-
|
|
|
- log_action('Running C++ HeadlessTest');
|
|
|
- try {
|
|
|
- const output = execSync(`${headlessTest} ${testArgs.join(' ')}`, {
|
|
|
- encoding: 'utf8',
|
|
|
- maxBuffer: 50 * 1024 * 1024 // 50MB buffer for large output
|
|
|
- });
|
|
|
- log_ok();
|
|
|
- return output;
|
|
|
- } catch (error: any) {
|
|
|
- log_fail();
|
|
|
- log_detail(`C++ execution failed: ${error.message}`);
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
+function executeCpp (args: TestArgs): string {
|
|
|
+ const cppDir = join(SPINE_ROOT, 'spine-cpp');
|
|
|
+ const testsDir = join(cppDir, 'tests');
|
|
|
+
|
|
|
+ if (!existsSync(testsDir)) {
|
|
|
+ log_detail(`C++ tests directory not found: ${testsDir}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if we need to build
|
|
|
+ if (needsCppBuild()) {
|
|
|
+ // Build using build.sh
|
|
|
+ log_action('Building C++ tests');
|
|
|
+ try {
|
|
|
+ execSync('./build.sh clean release', {
|
|
|
+ cwd: cppDir,
|
|
|
+ stdio: ['inherit', 'pipe', 'inherit']
|
|
|
+ });
|
|
|
+ log_ok();
|
|
|
+ } catch (error: any) {
|
|
|
+ log_fail();
|
|
|
+ log_detail(`C++ build failed: ${error.message}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Now run the headless test directly
|
|
|
+ const testArgs = [args.skeletonPath, args.atlasPath];
|
|
|
+ if (args.animationName) {
|
|
|
+ testArgs.push(args.animationName);
|
|
|
+ }
|
|
|
+
|
|
|
+ const buildDir = join(cppDir, 'build');
|
|
|
+ const headlessTest = join(buildDir, 'headless-test');
|
|
|
+
|
|
|
+ if (!existsSync(headlessTest)) {
|
|
|
+ log_detail(`C++ headless-test executable not found: ${headlessTest}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ log_action('Running C++ HeadlessTest');
|
|
|
+ try {
|
|
|
+ const output = execSync(`${headlessTest} ${testArgs.join(' ')}`, {
|
|
|
+ encoding: 'utf8',
|
|
|
+ maxBuffer: 50 * 1024 * 1024 // 50MB buffer for large output
|
|
|
+ });
|
|
|
+ log_ok();
|
|
|
+ return output;
|
|
|
+ } catch (error: any) {
|
|
|
+ log_fail();
|
|
|
+ log_detail(`C++ execution failed: ${error.message}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-function parseOutput(output: string): { skeletonData: any, skeletonState: any, animationState?: any } {
|
|
|
- // Split output into sections
|
|
|
- const sections = output.split(/=== [A-Z ]+? ===/);
|
|
|
-
|
|
|
- if (sections.length < 3) {
|
|
|
- log_detail(`Expected at least 2 sections in output, got: ${sections.length - 1}`);
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- const result: { skeletonData: any, skeletonState: any, animationState?: any } = {
|
|
|
- skeletonData: JSON.parse(sections[1].trim()),
|
|
|
- skeletonState: JSON.parse(sections[2].trim())
|
|
|
- };
|
|
|
-
|
|
|
- // Animation state is optional (only present if animation was loaded)
|
|
|
- if (sections.length >= 4 && sections[3].trim()) {
|
|
|
- result.animationState = JSON.parse(sections[3].trim());
|
|
|
- }
|
|
|
-
|
|
|
- return result;
|
|
|
- } catch (error) {
|
|
|
- log_detail(`Failed to parse JSON output: ${error}`);
|
|
|
- log_detail(`Section lengths: ${sections.map(s => s.length)}`);
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
+function parseOutput (output: string): { skeletonData: any, skeletonState: any, animationState?: any } {
|
|
|
+ // Split output into sections
|
|
|
+ const sections = output.split(/=== [A-Z ]+? ===/);
|
|
|
+
|
|
|
+ if (sections.length < 3) {
|
|
|
+ log_detail(`Expected at least 2 sections in output, got: ${sections.length - 1}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const result: { skeletonData: any, skeletonState: any, animationState?: any } = {
|
|
|
+ skeletonData: JSON.parse(sections[1].trim()),
|
|
|
+ skeletonState: JSON.parse(sections[2].trim())
|
|
|
+ };
|
|
|
+
|
|
|
+ // Animation state is optional (only present if animation was loaded)
|
|
|
+ if (sections.length >= 4 && sections[3].trim()) {
|
|
|
+ result.animationState = JSON.parse(sections[3].trim());
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ } catch (error) {
|
|
|
+ log_detail(`Failed to parse JSON output: ${error}`);
|
|
|
+ log_detail(`Section lengths: ${sections.map(s => s.length)}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-function fixFloatsAndPropertyIds(target: any, reference: any, path: string = '', targetLanguage: string = 'cpp'): any {
|
|
|
- // Handle null values
|
|
|
- if (reference === null || target === null) return target;
|
|
|
-
|
|
|
- // Handle circular references
|
|
|
- if (reference === '<circular>' || target === '<circular>') return target;
|
|
|
-
|
|
|
- // Handle arrays
|
|
|
- if (Array.isArray(reference) && Array.isArray(target)) {
|
|
|
- return target.map((item: any, index: number) => {
|
|
|
- if (index < reference.length) {
|
|
|
- return fixFloatsAndPropertyIds(item, reference[index], `${path}[${index}]`, targetLanguage);
|
|
|
- }
|
|
|
- return item;
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- // Handle objects
|
|
|
- if (typeof reference === 'object' && typeof target === 'object') {
|
|
|
- const result: any = {};
|
|
|
-
|
|
|
- // Copy all target properties
|
|
|
- for (const key in target) {
|
|
|
- if (target.hasOwnProperty(key)) {
|
|
|
- if (reference.hasOwnProperty(key)) {
|
|
|
- // Special cases where we always use Java value
|
|
|
- if (key === 'propertyIds') {
|
|
|
- // PropertyIds are encoded differently across all languages
|
|
|
- result[key] = reference[key];
|
|
|
- } else if (targetLanguage === 'cpp' && (
|
|
|
- (reference[key] === null && target[key] === '') ||
|
|
|
- (reference[key] === '' && target[key] === null) ||
|
|
|
- (key === 'bones' && reference[key] === null && Array.isArray(target[key]) && target[key].length === 0)
|
|
|
- )) {
|
|
|
- // C++ specific fixes: null vs empty string, null vs empty array for bones
|
|
|
- result[key] = reference[key];
|
|
|
- } else {
|
|
|
- result[key] = fixFloatsAndPropertyIds(target[key], reference[key], `${path}.${key}`, targetLanguage);
|
|
|
- }
|
|
|
- } else {
|
|
|
- result[key] = target[key];
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Add any missing properties from reference (shouldn't happen, but defensive)
|
|
|
- for (const key in reference) {
|
|
|
- if (reference.hasOwnProperty(key) && !result.hasOwnProperty(key)) {
|
|
|
- result[key] = reference[key];
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return result;
|
|
|
- }
|
|
|
-
|
|
|
- // Handle primitive values
|
|
|
- if (typeof reference === 'number' && typeof target === 'number') {
|
|
|
- // Check if both are floats and within epsilon
|
|
|
- const diff = Math.abs(reference - target);
|
|
|
- if (diff <= 0.001 && diff > 0) {
|
|
|
- return reference; // Use Java value
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- return target;
|
|
|
+function fixFloatsAndPropertyIds (target: any, reference: any, path: string = '', targetLanguage: string = 'cpp'): any {
|
|
|
+ // Handle null values
|
|
|
+ if (reference === null || target === null) return target;
|
|
|
+
|
|
|
+ // Handle circular references
|
|
|
+ if (reference === '<circular>' || target === '<circular>') return target;
|
|
|
+
|
|
|
+ // Handle arrays
|
|
|
+ if (Array.isArray(reference) && Array.isArray(target)) {
|
|
|
+ return target.map((item: any, index: number) => {
|
|
|
+ if (index < reference.length) {
|
|
|
+ return fixFloatsAndPropertyIds(item, reference[index], `${path}[${index}]`, targetLanguage);
|
|
|
+ }
|
|
|
+ return item;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle objects
|
|
|
+ if (typeof reference === 'object' && typeof target === 'object') {
|
|
|
+ const result: any = {};
|
|
|
+
|
|
|
+ // Copy all target properties
|
|
|
+ for (const key in target) {
|
|
|
+ if (target.hasOwnProperty(key)) {
|
|
|
+ if (reference.hasOwnProperty(key)) {
|
|
|
+ // Special cases where we always use Java value
|
|
|
+ if (key === 'propertyIds') {
|
|
|
+ // PropertyIds are encoded differently across all languages
|
|
|
+ result[key] = reference[key];
|
|
|
+ } else if (targetLanguage === 'cpp' && (
|
|
|
+ (reference[key] === null && target[key] === '') ||
|
|
|
+ (reference[key] === '' && target[key] === null) ||
|
|
|
+ (key === 'bones' && reference[key] === null && Array.isArray(target[key]) && target[key].length === 0)
|
|
|
+ )) {
|
|
|
+ // C++ specific fixes: null vs empty string, null vs empty array for bones
|
|
|
+ result[key] = reference[key];
|
|
|
+ } else {
|
|
|
+ result[key] = fixFloatsAndPropertyIds(target[key], reference[key], `${path}.${key}`, targetLanguage);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ result[key] = target[key];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add any missing properties from reference (shouldn't happen, but defensive)
|
|
|
+ for (const key in reference) {
|
|
|
+ if (reference.hasOwnProperty(key) && !result.hasOwnProperty(key)) {
|
|
|
+ result[key] = reference[key];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle primitive values
|
|
|
+ if (typeof reference === 'number' && typeof target === 'number') {
|
|
|
+ // Check if both are floats and within epsilon
|
|
|
+ const diff = Math.abs(reference - target);
|
|
|
+ if (diff <= 0.001 && diff > 0) {
|
|
|
+ return reference; // Use Java value
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ return target;
|
|
|
}
|
|
|
|
|
|
-function saveJsonFiles(args: TestArgs, parsed: any, javaParsed?: any, fixFloats?: boolean): void {
|
|
|
- // Ensure output directory exists
|
|
|
- const outputDir = join(SPINE_ROOT, 'tests', 'output');
|
|
|
- if (!existsSync(outputDir)) {
|
|
|
- mkdirSync(outputDir, { recursive: true });
|
|
|
- }
|
|
|
-
|
|
|
- // Determine file format from skeleton path
|
|
|
- const format = args.skeletonPath.endsWith('.json') ? 'json' : 'skel';
|
|
|
-
|
|
|
- // Apply float fixing if enabled and we have Java reference data
|
|
|
- let outputData = parsed;
|
|
|
- if (fixFloats && javaParsed && args.language !== 'java') {
|
|
|
- log_action('Fixing floats and propertyIds to match Java values');
|
|
|
- outputData = {
|
|
|
- skeletonData: fixFloatsAndPropertyIds(parsed.skeletonData, javaParsed.skeletonData, '', args.language),
|
|
|
- skeletonState: fixFloatsAndPropertyIds(parsed.skeletonState, javaParsed.skeletonState, '', args.language),
|
|
|
- animationState: parsed.animationState && javaParsed.animationState ?
|
|
|
- fixFloatsAndPropertyIds(parsed.animationState, javaParsed.animationState, '', args.language) : parsed.animationState
|
|
|
- };
|
|
|
- log_ok();
|
|
|
- }
|
|
|
-
|
|
|
- // Save files with language and format in filename
|
|
|
- writeFileSync(join(outputDir, `skeleton-data-${args.language}-${format}.json`), JSON.stringify(outputData.skeletonData, null, 2));
|
|
|
- writeFileSync(join(outputDir, `skeleton-state-${args.language}-${format}.json`), JSON.stringify(outputData.skeletonState, null, 2));
|
|
|
-
|
|
|
- // Only save animation state if it exists
|
|
|
- if (outputData.animationState) {
|
|
|
- writeFileSync(join(outputDir, `animation-state-${args.language}-${format}.json`), JSON.stringify(outputData.animationState, null, 2));
|
|
|
- }
|
|
|
+function saveJsonFiles (args: TestArgs, parsed: any, javaParsed?: any, fixFloats?: boolean): void {
|
|
|
+ // Ensure output directory exists
|
|
|
+ const outputDir = join(SPINE_ROOT, 'tests', 'output');
|
|
|
+ if (!existsSync(outputDir)) {
|
|
|
+ mkdirSync(outputDir, { recursive: true });
|
|
|
+ }
|
|
|
+
|
|
|
+ // Determine file format from skeleton path
|
|
|
+ const format = args.skeletonPath.endsWith('.json') ? 'json' : 'skel';
|
|
|
+
|
|
|
+ // Apply float fixing if enabled and we have Java reference data
|
|
|
+ let outputData = parsed;
|
|
|
+ if (fixFloats && javaParsed && args.language !== 'java') {
|
|
|
+ log_action('Fixing floats and propertyIds to match Java values');
|
|
|
+ outputData = {
|
|
|
+ skeletonData: fixFloatsAndPropertyIds(parsed.skeletonData, javaParsed.skeletonData, '', args.language),
|
|
|
+ skeletonState: fixFloatsAndPropertyIds(parsed.skeletonState, javaParsed.skeletonState, '', args.language),
|
|
|
+ animationState: parsed.animationState && javaParsed.animationState ?
|
|
|
+ fixFloatsAndPropertyIds(parsed.animationState, javaParsed.animationState, '', args.language) : parsed.animationState
|
|
|
+ };
|
|
|
+ log_ok();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Save files with language and format in filename
|
|
|
+ writeFileSync(join(outputDir, `skeleton-data-${args.language}-${format}.json`), JSON.stringify(outputData.skeletonData, null, 2));
|
|
|
+ writeFileSync(join(outputDir, `skeleton-state-${args.language}-${format}.json`), JSON.stringify(outputData.skeletonState, null, 2));
|
|
|
+
|
|
|
+ // Only save animation state if it exists
|
|
|
+ if (outputData.animationState) {
|
|
|
+ writeFileSync(join(outputDir, `animation-state-${args.language}-${format}.json`), JSON.stringify(outputData.animationState, null, 2));
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-function runTestsForFiles(language: string, skeletonPath: string, atlasPath: string, animationName?: string, fixFloats?: boolean): void {
|
|
|
- const testArgs: TestArgs = {
|
|
|
- language,
|
|
|
- skeletonPath,
|
|
|
- atlasPath,
|
|
|
- animationName
|
|
|
- };
|
|
|
-
|
|
|
- const fileType = skeletonPath.endsWith('.json') ? 'JSON' : 'BINARY';
|
|
|
- const fileName = skeletonPath.split('/').pop() || skeletonPath;
|
|
|
-
|
|
|
- log_detail(`Testing ${fileType}: ${fileName}`);
|
|
|
-
|
|
|
- // Always run Java first (reference implementation)
|
|
|
- const javaOutput = executeJava(testArgs);
|
|
|
- const javaParsed = parseOutput(javaOutput);
|
|
|
- saveJsonFiles({ ...testArgs, language: 'java' }, javaParsed);
|
|
|
-
|
|
|
- // Run target language
|
|
|
- let targetOutput: string;
|
|
|
- if (language === 'cpp') {
|
|
|
- targetOutput = executeCpp(testArgs);
|
|
|
- } else {
|
|
|
- log_detail(`Unsupported target language: ${language}`);
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
-
|
|
|
- const targetParsed = parseOutput(targetOutput);
|
|
|
- saveJsonFiles(testArgs, targetParsed, javaParsed, fixFloats);
|
|
|
+function runTestsForFiles (language: string, skeletonPath: string, atlasPath: string, animationName?: string, fixFloats?: boolean): void {
|
|
|
+ const testArgs: TestArgs = {
|
|
|
+ language,
|
|
|
+ skeletonPath,
|
|
|
+ atlasPath,
|
|
|
+ animationName
|
|
|
+ };
|
|
|
+
|
|
|
+ const fileType = skeletonPath.endsWith('.json') ? 'JSON' : 'BINARY';
|
|
|
+ const fileName = skeletonPath.split('/').pop() || skeletonPath;
|
|
|
+
|
|
|
+ log_detail(`Testing ${fileType}: ${fileName}`);
|
|
|
+
|
|
|
+ // Always run Java first (reference implementation)
|
|
|
+ const javaOutput = executeJava(testArgs);
|
|
|
+ const javaParsed = parseOutput(javaOutput);
|
|
|
+ saveJsonFiles({ ...testArgs, language: 'java' }, javaParsed);
|
|
|
+
|
|
|
+ // Run target language
|
|
|
+ let targetOutput: string;
|
|
|
+ if (language === 'cpp') {
|
|
|
+ targetOutput = executeCpp(testArgs);
|
|
|
+ } else {
|
|
|
+ log_detail(`Unsupported target language: ${language}`);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ const targetParsed = parseOutput(targetOutput);
|
|
|
+ saveJsonFiles(testArgs, targetParsed, javaParsed, fixFloats);
|
|
|
}
|
|
|
|
|
|
-function verifyOutputsMatch(): void {
|
|
|
- const outputDir = join(SPINE_ROOT, 'tests', 'output');
|
|
|
- const outputFiles = [
|
|
|
- 'skeleton-data-java-json.json',
|
|
|
- 'skeleton-data-cpp-json.json',
|
|
|
- 'skeleton-state-java-json.json',
|
|
|
- 'skeleton-state-cpp-json.json',
|
|
|
- 'skeleton-data-java-skel.json',
|
|
|
- 'skeleton-data-cpp-skel.json',
|
|
|
- 'skeleton-state-java-skel.json',
|
|
|
- 'skeleton-state-cpp-skel.json'
|
|
|
- ];
|
|
|
-
|
|
|
- // Check if all files exist
|
|
|
- const missingFiles = outputFiles.filter(file => !existsSync(join(outputDir, file)));
|
|
|
- if (missingFiles.length > 0) {
|
|
|
- log_detail(`Skipping diff check - missing files: ${missingFiles.join(', ')}`);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- log_action('Verifying Java and C++ outputs match');
|
|
|
-
|
|
|
- const comparisons = [
|
|
|
- ['skeleton-data-java-json.json', 'skeleton-data-cpp-json.json']
|
|
|
- // TODO: Add binary file comparison once C++ binary parsing is fixed
|
|
|
- // ['skeleton-data-java-skel.json', 'skeleton-data-cpp-skel.json']
|
|
|
- ];
|
|
|
-
|
|
|
- let allMatch = true;
|
|
|
-
|
|
|
- for (const [javaFile, cppFile] of comparisons) {
|
|
|
- try {
|
|
|
- const javaContent = execSync(`cat "${join(outputDir, javaFile)}"`, { encoding: 'utf8' });
|
|
|
- const cppContent = execSync(`cat "${join(outputDir, cppFile)}"`, { encoding: 'utf8' });
|
|
|
-
|
|
|
- if (javaContent !== cppContent) {
|
|
|
- allMatch = false;
|
|
|
- console.error(`\n❌ Files differ: ${javaFile} vs ${cppFile}`);
|
|
|
-
|
|
|
- // Show diff using system diff command
|
|
|
- try {
|
|
|
- execSync(`diff "${join(outputDir, javaFile)}" "${join(outputDir, cppFile)}"`, {
|
|
|
- stdio: 'inherit',
|
|
|
- cwd: outputDir
|
|
|
- });
|
|
|
- } catch {
|
|
|
- // diff exits with code 1 when files differ, which is expected
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (error: any) {
|
|
|
- allMatch = false;
|
|
|
- console.error(`\n❌ Error comparing ${javaFile} vs ${cppFile}: ${error.message}`);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (allMatch) {
|
|
|
- log_ok();
|
|
|
- } else {
|
|
|
- log_fail();
|
|
|
- console.error('\n❌ Java and C++ outputs do not match');
|
|
|
- process.exit(1);
|
|
|
- }
|
|
|
+function verifyOutputsMatch (): void {
|
|
|
+ const outputDir = join(SPINE_ROOT, 'tests', 'output');
|
|
|
+ const outputFiles = [
|
|
|
+ 'skeleton-data-java-json.json',
|
|
|
+ 'skeleton-data-cpp-json.json',
|
|
|
+ 'skeleton-state-java-json.json',
|
|
|
+ 'skeleton-state-cpp-json.json',
|
|
|
+ 'skeleton-data-java-skel.json',
|
|
|
+ 'skeleton-data-cpp-skel.json',
|
|
|
+ 'skeleton-state-java-skel.json',
|
|
|
+ 'skeleton-state-cpp-skel.json'
|
|
|
+ ];
|
|
|
+
|
|
|
+ // Check if all files exist
|
|
|
+ const missingFiles = outputFiles.filter(file => !existsSync(join(outputDir, file)));
|
|
|
+ if (missingFiles.length > 0) {
|
|
|
+ log_detail(`Skipping diff check - missing files: ${missingFiles.join(', ')}`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ log_action('Verifying Java and C++ outputs match');
|
|
|
+
|
|
|
+ const comparisons = [
|
|
|
+ ['skeleton-data-java-json.json', 'skeleton-data-cpp-json.json']
|
|
|
+ // TODO: Add binary file comparison once C++ binary parsing is fixed
|
|
|
+ // ['skeleton-data-java-skel.json', 'skeleton-data-cpp-skel.json']
|
|
|
+ ];
|
|
|
+
|
|
|
+ let allMatch = true;
|
|
|
+
|
|
|
+ for (const [javaFile, cppFile] of comparisons) {
|
|
|
+ try {
|
|
|
+ const javaContent = execSync(`cat "${join(outputDir, javaFile)}"`, { encoding: 'utf8' });
|
|
|
+ const cppContent = execSync(`cat "${join(outputDir, cppFile)}"`, { encoding: 'utf8' });
|
|
|
+
|
|
|
+ if (javaContent !== cppContent) {
|
|
|
+ allMatch = false;
|
|
|
+ console.error(`\n❌ Files differ: ${javaFile} vs ${cppFile}`);
|
|
|
+ }
|
|
|
+ } catch (error: any) {
|
|
|
+ allMatch = false;
|
|
|
+ console.error(`\n❌ Error comparing ${javaFile} vs ${cppFile}: ${error.message}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (allMatch) {
|
|
|
+ log_ok();
|
|
|
+ } else {
|
|
|
+ log_fail();
|
|
|
+ console.error('\n❌ Java and C++ outputs do not match');
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-function main(): void {
|
|
|
- const args = validateArgs();
|
|
|
-
|
|
|
- log_title('Spine Runtime Test');
|
|
|
-
|
|
|
- // Clean output directory first
|
|
|
- cleanOutputDirectory();
|
|
|
-
|
|
|
- if (args.files) {
|
|
|
- // Auto-discovery mode: run tests for both JSON and binary files
|
|
|
- const jsonFile = args.files.jsonPath.split('/').pop() || args.files.jsonPath;
|
|
|
- const skelFile = args.files.skelPath.split('/').pop() || args.files.skelPath;
|
|
|
- const atlasFile = args.files.atlasPath.split('/').pop() || args.files.atlasPath;
|
|
|
-
|
|
|
- log_detail(`Files: ${jsonFile}, ${skelFile}, ${atlasFile}`);
|
|
|
-
|
|
|
- runTestsForFiles(args.language, args.files.jsonPath, args.files.atlasPath, args.animationName, args.fixFloats);
|
|
|
- runTestsForFiles(args.language, args.files.skelPath, args.files.atlasPath, args.animationName, args.fixFloats);
|
|
|
-
|
|
|
- log_summary('✓ All tests completed');
|
|
|
- log_detail(`JSON files saved to: ${join(SPINE_ROOT, 'tests', 'output')}`);
|
|
|
- } else {
|
|
|
- // Explicit paths mode: run test for single file
|
|
|
- runTestsForFiles(args.language, args.skeletonPath!, args.atlasPath!, args.animationName, args.fixFloats);
|
|
|
- log_summary('✓ Test completed');
|
|
|
- log_detail(`JSON files saved to: ${join(SPINE_ROOT, 'tests', 'output')}`);
|
|
|
- }
|
|
|
-
|
|
|
- // Verify outputs match if we're testing C++
|
|
|
- if (args.language === 'cpp') {
|
|
|
- verifyOutputsMatch();
|
|
|
- }
|
|
|
+function main (): void {
|
|
|
+ const args = validateArgs();
|
|
|
+
|
|
|
+ log_title('Spine Runtime Test');
|
|
|
+
|
|
|
+ // Clean output directory first
|
|
|
+ cleanOutputDirectory();
|
|
|
+
|
|
|
+ if (args.files) {
|
|
|
+ // Auto-discovery mode: run tests for both JSON and binary files
|
|
|
+ const jsonFile = args.files.jsonPath.split('/').pop() || args.files.jsonPath;
|
|
|
+ const skelFile = args.files.skelPath.split('/').pop() || args.files.skelPath;
|
|
|
+ const atlasFile = args.files.atlasPath.split('/').pop() || args.files.atlasPath;
|
|
|
+
|
|
|
+ log_detail(`Files: ${jsonFile}, ${skelFile}, ${atlasFile}`);
|
|
|
+
|
|
|
+ runTestsForFiles(args.language, args.files.jsonPath, args.files.atlasPath, args.animationName, args.fixFloats);
|
|
|
+ runTestsForFiles(args.language, args.files.skelPath, args.files.atlasPath, args.animationName, args.fixFloats);
|
|
|
+
|
|
|
+ log_summary('✓ All tests completed');
|
|
|
+ log_detail(`JSON files saved to: ${join(SPINE_ROOT, 'tests', 'output')}`);
|
|
|
+ } else {
|
|
|
+ // Explicit paths mode: run test for single file
|
|
|
+ runTestsForFiles(args.language, args.skeletonPath!, args.atlasPath!, args.animationName, args.fixFloats);
|
|
|
+ log_summary('✓ Test completed');
|
|
|
+ log_detail(`JSON files saved to: ${join(SPINE_ROOT, 'tests', 'output')}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify outputs match if we're testing C++
|
|
|
+ if (args.language === 'cpp') {
|
|
|
+ verifyOutputsMatch();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
- main();
|
|
|
+ main();
|
|
|
}
|