Use upstream LLVM to build the integration tests, remove binary from git

This commit is contained in:
hikari_no_yume
2023-08-21 21:52:02 +02:00
parent 83daed3e55
commit d1072934d8
8 changed files with 167 additions and 272 deletions

View File

@@ -22,6 +22,18 @@ jobs:
run: git submodule update --init
- name: Install Boost
run: brew install boost
- name: Try to get cached copy of LLVM
id: cache-llvm
uses: actions/cache@v3
with:
path: tests/llvm
key: llvm_12_0_0_macOS_x64
- if: ${{ steps.cache-llvm.outputs.cache-hit != 'true' }}
name: Download LLVM
run: curl -L -O "https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.0/clang+llvm-12.0.0-x86_64-apple-darwin.tar.xz"
- if: ${{ steps.cache-llvm.outputs.cache-hit != 'true' }}
name: Extract LLVM
run: tar -xf clang+llvm-12.0.0-x86_64-apple-darwin.tar.xz && mkdir tests/llvm && mv clang+llvm-12.0.0-x86_64-apple-darwin/* tests/llvm
- name: Test
run: cargo test
- name: Build
@@ -30,6 +42,10 @@ jobs:
with:
name: touchHLE_macOS_x86_64
path: touchHLE
- uses: actions/upload-artifact@v3
with:
name: TestApp
path: tests/TestApp.app
build-win:

View File

@@ -20,7 +20,9 @@ Please bear in mind that there are infinitely many apps that do not work in touc
[BUILDING.md](BUILDING.md) and [DEBUGGING.md](DEBUGGING.md) might be helpful while you're working on a contribution.
Please run `cargo fmt`, `cargo clippy` and `cargo test` on your changes before committing. For the handful of C and C++ files, please use `clang-format -i` to format them.
Please run `cargo fmt` and `cargo clippy` on your changes before committing. For the handful of C and C++ files, please use `clang-format -i` to format them.
You should also run `cargo test`. [Building the integration tests requires downloading LLVM](tests/BUILDING.md), so it's understandable if you want to skip them (`cargo test -- --skip run_test_app`) and let the GitHub Actions CI catch any issues when you submit your pull request. Alternatively, you can download a pre-built version of the integration tests app (TestApp) from GitHub Actions CI and run it in touchHLE.
If you're going to open a pull request with non-trivial changes, please talk to us first so we can figure out if we're likely to accept them. It would be a shame if your effort had to be wasted.

2
tests/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/llvm
/TestApp.app/TestApp

View File

@@ -2,3 +2,27 @@ Integration tests
=================
This directory contains integration tests written in Objective-C. They're compiled to an ARMv6 Mach-O binary and packaged into a bundle (`TestApp.app`) so that they can be run in the emulator like a normal iPhone OS app. The code in `integration.rs` lets them be run by `cargo test` (which also runs unit tests written in Rust).
Building
--------
### Setup
Upstream LLVM is needed for building the ARMv6 test binary. 32-bit iOS support is broken in version 13 onwards, so 12.0.1 is the newest supported version you can use. Downloads:
* [LLVM 12.0.1 Windows x64 release binaries](https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.1/LLVM-12.0.1-win64.exe) (extract it with 7-zip)
* [LLVM 12.0.0 macOS x64 release binaries](https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.0/clang+llvm-12.0.0-x86_64-apple-darwin.tar.xz) (extract it with `tar -xf`)
* [Other versions](https://github.com/llvm/llvm-project/releases/tag/llvmorg-12.0.0) (though you might need to build LLVM yourself, sorry :c)
Extract LLVM to `tests/llvm`, so that e.g. `tests/TestApp_build/llvm/bin/clang` (with `.exe` suffix, on Windows) is the path to Clang. `cargo test` (via `integration.rs`) will do the rest.
### Why
32-bit iOS is an awkward platform to target. There's no way Apple's official tools still support it, and nobody wants to have to install an old version of an OS X in a VM. Also, this is a cross-platform emulation project, but Apple's tools require you to own a Mac. Using LLVM lets us avoid a dependency on this legacy, proprietary software that only runs on one platform, in favour of somewhat less legacy (July 2021), cross-platform software with convenient release builds available.
### Particulars
Upstream LLVM provides a compiler (Clang) and linker (LLD) that can target 32-bit iOS, but not platform headers or libraries, so some tricks are needed. See the comments in `integration.rs` and `main.c`. Some additional notes:
- The resulting binary is probably not actually compatible iPhone OS 2. It uses `LC_MAIN` rather than `LC_UNIX_THREAD`. It might work on iOS 6? I haven't tested it.
- LLD crashes if you try to compile Objective-C rather than C code. It might be expecting an Objective-C system library.

Binary file not shown.

View File

@@ -1,251 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 45;
objects = {
/* Begin PBXBuildFile section */
1D3623EC0D0F72F000981E51 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D3623EB0D0F72F000981E51 /* CoreGraphics.framework */; };
1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; };
1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; };
1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
1D3623EB0D0F72F000981E51 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
1D6058910D05DD3D006BFB54 /* TestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
1D60588F0D05DD3D006BFB54 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */,
1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */,
1D3623EC0D0F72F000981E51 /* CoreGraphics.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
080E96DDFE201D6D7F000001 /* Classes */ = {
isa = PBXGroup;
children = (
);
path = Classes;
sourceTree = "<group>";
};
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = {
isa = PBXGroup;
children = (
1D3623EB0D0F72F000981E51 /* CoreGraphics.framework */,
1DF5F4DF0D08C38300B7A737 /* UIKit.framework */,
1D30AB110D05D00D00671497 /* Foundation.framework */,
);
name = "Linked Frameworks";
sourceTree = "<group>";
};
1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = {
isa = PBXGroup;
children = (
);
name = "Other Frameworks";
sourceTree = "<group>";
};
19C28FACFE9D520D11CA2CBB /* Products */ = {
isa = PBXGroup;
children = (
1D6058910D05DD3D006BFB54 /* TestApp.app */,
);
name = Products;
sourceTree = "<group>";
};
29B97314FDCFA39411CA2CEA /* CustomTemplate */ = {
isa = PBXGroup;
children = (
080E96DDFE201D6D7F000001 /* Classes */,
29B97315FDCFA39411CA2CEA /* Other Sources */,
29B97317FDCFA39411CA2CEA /* Resources */,
29B97323FDCFA39411CA2CEA /* Frameworks */,
19C28FACFE9D520D11CA2CBB /* Products */,
);
name = CustomTemplate;
sourceTree = "<group>";
};
29B97315FDCFA39411CA2CEA /* Other Sources */ = {
isa = PBXGroup;
children = (
29B97316FDCFA39411CA2CEA /* main.m */,
);
name = "Other Sources";
sourceTree = "<group>";
};
29B97317FDCFA39411CA2CEA /* Resources */ = {
isa = PBXGroup;
children = (
8D1107310486CEB800E47090 /* Info.plist */,
);
name = Resources;
sourceTree = "<group>";
};
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
isa = PBXGroup;
children = (
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */,
1058C7A2FEA54F0111CA2CBB /* Other Frameworks */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
1D6058900D05DD3D006BFB54 /* TestApp */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "TestApp" */;
buildPhases = (
1D60588D0D05DD3D006BFB54 /* Resources */,
1D60588E0D05DD3D006BFB54 /* Sources */,
1D60588F0D05DD3D006BFB54 /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = TestApp;
productName = TestApp;
productReference = 1D6058910D05DD3D006BFB54 /* TestApp.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
29B97313FDCFA39411CA2CEA /* Project object */ = {
isa = PBXProject;
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "TestApp" */;
compatibilityVersion = "Xcode 3.1";
hasScannedForEncodings = 1;
mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */;
projectDirPath = "";
projectRoot = "";
targets = (
1D6058900D05DD3D006BFB54 /* TestApp */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
1D60588D0D05DD3D006BFB54 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
1D60588E0D05DD3D006BFB54 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1D60589B0D05DD56006BFB54 /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
1D6058940D05DD3E006BFB54 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
GCC_DYNAMIC_NO_PIC = NO;
GCC_ENABLE_FIX_AND_CONTINUE = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = NO;
GCC_PREFIX_HEADER = "";
INFOPLIST_FILE = Info.plist;
PREBINDING = NO;
PRODUCT_NAME = TestApp;
};
name = Debug;
};
1D6058950D05DD3E006BFB54 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
COPY_PHASE_STRIP = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
GCC_ENABLE_FIX_AND_CONTINUE = NO;
GCC_PRECOMPILE_PREFIX_HEADER = NO;
GCC_PREFIX_HEADER = "";
INFOPLIST_FILE = Info.plist;
PREBINDING = NO;
PRODUCT_NAME = TestApp;
WRAPPER_EXTENSION = app;
};
name = Release;
};
C01FCF4F08A954540054247B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
"CODE_SIGN_IDENTITY[sdk=aspen*]" = "iPhone Developer";
GCC_C_LANGUAGE_STANDARD = c99;
GCC_ENABLE_SYMBOL_SEPARATION = YES;
GCC_PRECOMPILE_PREFIX_HEADER = NO;
GCC_PREFIX_HEADER = "";
GCC_USE_GCC3_PFE_SUPPORT = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
ONLY_ACTIVE_ARCH = YES;
PREBINDING = NO;
PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIR = YES;
SDKROOT = aspen1.2;
};
name = Debug;
};
C01FCF5008A954540054247B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
PREBINDING = NO;
SDKROOT = aspen1.2;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "TestApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1D6058940D05DD3E006BFB54 /* Debug */,
1D6058950D05DD3E006BFB54 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C01FCF4E08A954540054247B /* Build configuration list for PBXProject "TestApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C01FCF4F08A954540054247B /* Debug */,
C01FCF5008A954540054247B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
}

View File

@@ -3,16 +3,50 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
/*
This is a main file for the TestApp which is used for integration testing.
This code supposed to be compiled with iPhone SDK and Xcode 3.1 Developer Tools
for Mac OS X v10.5
*/
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// This is a main file for the TestApp which is used for integration testing.
// See also tests/README.md and tests/integration.rs for the details of how it
// is compiled and run.
// === Declarations ===
// We don't have any system headers for iPhone OS, so we must declare everything
// ourselves rather than #include'ing.
// <stddef.h>
#define NULL ((void*)0)
typedef unsigned long size_t;
// <errno.h>
int *__error(void);
#define errno (*__error())
// <stdarg.h>
typedef __builtin_va_list va_list;
#define va_start(a, b) __builtin_va_start(a, b)
#define va_arg(a, b) __builtin_va_arg(a, b)
#define va_end(a) __builtin_va_end(a)
// <stdio.h>
int sscanf(const char *, const char *, ...);
int printf(const char *, ...);
int vsnprintf(char *, size_t, const char *, va_list);
// <stdlib.h>
#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1
void exit(int);
void free(void *);
void *malloc(size_t);
void qsort(void *, size_t, size_t, int (*)(const void *, const void *));
void *realloc(void *, size_t);
// <string.h>
int memcmp(const void *, const void *, size_t);
void *memmove(void *, const void *, size_t);
int strcmp(const char *, const char *);
// === Main code ===
int int_compar(const void *a, const void *b) { return *(int *)a - *(int *)b; }
@@ -90,11 +124,17 @@ struct {
int (*func)();
const char *name;
} test_func_array[] = {
FUNC_DEF(test_qsort), FUNC_DEF(test_vsnprintf), FUNC_DEF(test_sscanf),
// TODO: re-enable qsort. It currently crashes for some reason.
/*FUNC_DEF(test_qsort),*/ FUNC_DEF(test_vsnprintf), FUNC_DEF(test_sscanf),
FUNC_DEF(test_errno), FUNC_DEF(test_realloc),
};
int main(int argc, char *argv[]) {
// Because no libc is linked into this executable, there is no libc entry point
// to call main. Instead, integration.rs tells Clang to set the _main symbol
// as the entry point. (It has to be _main because a C compiler will throw
// away stuff not called by main().) Since this is the true entry point, there's
// no argc or argv and we must call exit() ourselves.
int main() {
int tests_run = 0;
int tests_passed = 0;
@@ -112,5 +152,5 @@ int main(int argc, char *argv[]) {
}
printf("Passed %d out of %d tests\n", tests_passed, tests_run);
return tests_run == tests_passed ? 0 : 1;
exit(tests_run == tests_passed ? 0 : 1);
}

View File

@@ -1,10 +1,12 @@
use std::env;
use std::env::current_dir;
use std::error::Error;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
// adapted from `assert_cmd` crate
fn target_dir() -> std::path::PathBuf {
fn target_dir() -> PathBuf {
env::current_exe()
.ok()
.map(|mut path| {
@@ -24,21 +26,81 @@ fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
.position(|window| window == needle)
}
fn build_test_app(tests_dir: &Path, test_app_path: &Path) -> Result<(), Box<dyn Error>> {
let clang_path = tests_dir
.join("llvm")
.join("bin")
.join(format!("clang{}", env::consts::EXE_SUFFIX));
if !clang_path.exists() {
panic!(
"Couldn't find Clang at {}. Please see {} for more details.",
clang_path.display(),
tests_dir.join("README.md").display()
);
}
let test_bin_path = test_app_path.join("TestApp");
eprintln!("Building {} for iPhone OS 2...", test_bin_path.display());
let mut cmd = Command::new(clang_path);
let output = cmd
// Use upstream LLVM linker (not system linker)
.arg("-fuse-ld=lld")
// On macOS only, Clang tries to use flags that ld64.lld doesn't
// support. Perhaps it's confused and thinks it's invoking Apple's ld64?
// Telling it not to use newer flags like this seems to avoid this, but
// I suspect there may be a better fix.
.arg("-mlinker-version=0")
// Target iPhone OS 2
.args(["-target", "armv6-apple-ios2"])
// We don't have a libc to link against, don't try
.arg("-nostdlib")
// Pass four args to the linker:
// `-e _main` sets the mangled C main() function as the entry point
// (normally the libc provides an entry point calling main(), but we
// have no libc)
// `-undefined dynamic_lookup` makes the linker tolerate undefined
// references, falling back to dynamic linking instead. This is needed
// because we have no system libraries/frameworks for it to link to.
.arg("-Wl,-e,_main,-undefined,dynamic_lookup")
// Input
.arg(tests_dir.join("TestApp_source").join("main.c"))
// Write the output to the bundle.
.arg("-o")
.arg(&test_bin_path)
.output()
.expect("failed to execute Clang process");
std::io::stdout().write_all(&output.stdout).unwrap();
std::io::stderr().write_all(&output.stderr).unwrap();
assert!(output.status.success());
eprintln!("Built successfully.");
Ok(())
}
#[test]
fn run_test_app() -> Result<(), Box<dyn std::error::Error>> {
fn run_test_app() -> Result<(), Box<dyn Error>> {
let tests_dir = current_dir()?.join("tests");
let test_app_path = tests_dir.join("TestApp.app");
build_test_app(&tests_dir, &test_app_path)?;
let binary_name = "touchHLE";
let binary_path = target_dir().join(format!("{}{}", binary_name, env::consts::EXE_SUFFIX));
let mut cmd = Command::new(binary_path);
let mut test_app_path = current_dir()?;
test_app_path.push("tests");
test_app_path.push("TestApp.app");
let output = cmd
.arg(test_app_path)
.output()
.expect("failed to execute process");
.expect("failed to execute touchHLE process");
std::io::stdout().write_all(&output.stdout).unwrap();
std::io::stderr().write_all(&output.stderr).unwrap();