diff --git a/.github/scripts/run-android-tests.sh b/.github/scripts/run-android-tests.sh new file mode 100755 index 00000000..1b2ede21 --- /dev/null +++ b/.github/scripts/run-android-tests.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +variant="$1" +test_command="$2" + +if [ "$variant" = "bare" ]; then + # These tests are independent of the bare/expo distinction. Bare tests are slightly faster. + echo "::group::Instrumented tests" + (cd android && ./gradlew :app:connectedAndroidTest) + echo "::endgroup::" +fi + +echo "::group::E2E tests" +npm run "$test_command" +echo "::endgroup::" diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index 4384db86..1b9fa23d 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -104,17 +104,12 @@ jobs: arch: x86 disable-animations: true # connectedAndroidTest is included here (rather than a separate job or step) to reuse - # this job's already-booted emulator, even though it means it runs once per matrix - # variant. The android-emulator-runner action has no post-cleanup step, so a second - # step would boot and tear down a second emulator; ::group:: markers keep the two - # test runs visually separated in the Actions log instead. - script: | - echo "::group::Instrumented tests" - (cd android && ./gradlew :app:connectedAndroidTest) - echo "::endgroup::" - echo "::group::E2E tests" - npm run ${{ matrix.test-command }} - echo "::endgroup::" + # this job's already-booted emulator. The emulator only lives for the duration of this + # action (it's killed at the end of the same invocation, not in a post/cleanup step), + # so the logic can't be split into a later workflow step - it has to run here. + # android-emulator-runner also runs each line of `script` as its own separate `sh -c` + # invocation, so multi-line shell logic is delegated to a script file instead of inlining here. + script: .github/scripts/run-android-tests.sh ${{ matrix.variant }} ${{ matrix.test-command }} ios-test: needs: lint @@ -141,10 +136,16 @@ jobs: restore-keys: | ${{ runner.os }}-npm- - - name: Run iOS Tests - run: | - npm install - npm run ${{ matrix.test-command }} + - name: Install dependencies + run: npm install + + - name: Run iOS unit tests + # These tests are independent of the bare/expo distinction. Bare tests are slightly faster. + if: matrix.variant == 'bare' + run: npm run test:unit:ios + + - name: Run iOS E2E Tests + run: npm run ${{ matrix.test-command }} - name: Upload iOS Simulator crash reports if: failure() diff --git a/.npmignore b/.npmignore index 57e0aa55..ef1963ef 100644 --- a/.npmignore +++ b/.npmignore @@ -52,6 +52,8 @@ android/.gradle android/**/*.iml android/.idea +ios/CodePushDiffPatchTests/ + # Windows windows/.vs/ diff --git a/CLAUDE.md b/CLAUDE.md index 02cdf64b..5820718e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,8 +10,10 @@ React Native CodePush is a native module that enables over-the-air updates for R #### Unit tests -- `cd android && ./gradlew :app:test` -- iOS: no unit tests yet. +- `npm run test:unit:android` +- `npm run test:unit:ios` + +Prefer unit testing what's possible (even though, on iOS, this involves a simulator). Legacy code used E2E tests for everything, which is complex, error-prone, and slow. The existing E2E tests are still useful, but this is not a pattern to follow. #### E2E Tests - `npm run test:android` - Run Android-specific tests @@ -54,7 +56,7 @@ React Native CodePush is a native module that enables over-the-air updates for R - **Custom Test Runner**: TypeScript-based test framework in `test/` - **Real App Testing**: Creates actual React Native apps for integration testing - **Scenario Testing**: Update, rollback, and error scenarios -- **No unit test infra for JS/iOS yet**: JS/iOS only have the mocha-based integration suite above. `src/acquisition-sdk/__tests__/` contains tests ported from upstream `microsoft/code-push`, kept for future reference - they are deliberately not wired into `npm test` or any runner. Don't assume they're dead/forgotten code, and don't wire them in without setting up real unit test infra first. +- **No unit test infra for JS yet**: JS only has the mocha-based integration suite above. `src/acquisition-sdk/__tests__/` contains tests ported from upstream `microsoft/code-push`, kept for future reference - they are deliberately not wired into `npm test` or any runner. Don't assume they're dead/forgotten code, and don't wire them in without setting up real unit test infra first. - **Templates**: `test/template/` holds native files (Podfile, AppDelegate, Android app files) and JS scenarios copied over top of a freshly generated RN/Expo app during test setup, overwriting its defaults — edit files here, not the generated project, for changes to persist - **`test:ios` vs `test:setup:ios` vs `test:fast:ios`**: `test:ios` is just `test:setup:ios` followed by `test:fast:ios` — the two are meant to be split apart for local iteration. - `test:setup:ios` (mocha `--ios --setup`) boots the simulator and provisions the test app once: copies templates, runs `pod install`, patches Info.plist/AppDelegate. It never builds or runs any test scenario. diff --git a/CodePush.podspec b/CodePush.podspec index 84e5f9f2..e16fa4c7 100644 --- a/CodePush.podspec +++ b/CodePush.podspec @@ -13,10 +13,23 @@ Pod::Spec.new do |s| s.ios.deployment_target = '15.5' s.tvos.deployment_target = '15.5' s.preserve_paths = '*.js' - s.library = 'z' - s.source_files = 'ios/CodePush/*.{h,m}' + s.libraries = 'z', 'bz2' + s.source_files = [ + 'ios/CodePush/*.{h,m}', + 'shared/diffpatch/*.{c,h}', + 'shared/third_party/hdiffpatch/libHDiffPatch/HPatch/patch.{c,h}', + 'shared/third_party/hdiffpatch/bsdiff_wrapper/bspatch_wrapper.{c,h}', + 'shared/third_party/hdiffpatch/file_for_patch.{c,h}', + ] s.public_header_files = ['ios/CodePush/CodePush.h'] - s.pod_target_xcconfig = { "DEFINES_MODULE" => "YES" } + s.pod_target_xcconfig = { + "DEFINES_MODULE" => "YES", + # HDiffPatch's bspatch-only usage: no multithreading, no directory diff/patch, and no raw + # block device support (which would otherwise probe Linux-only ioctls). + # Keep in sync with android/app/src/main/cpp/CMakeLists.txt. + "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) _IS_NEED_BLOCK_DEV=0 _IS_USED_MULTITHREAD=0 _IS_NEED_DIR_DIFF_PATCH=0", + "HEADER_SEARCH_PATHS" => "$(inherited) $(PODS_TARGET_SRCROOT)/shared $(PODS_TARGET_SRCROOT)/shared/diffpatch $(PODS_TARGET_SRCROOT)/shared/third_party/hdiffpatch $(PODS_TARGET_SRCROOT)/shared/third_party/hdiffpatch/libHDiffPatch/HPatch", + } # Note: Even though there are copy/pasted versions of some of these dependencies in the repo, # we explicitly let CocoaPods pull in the versions below so all dependencies are resolved and diff --git a/android/app/src/main/cpp/CMakeLists.txt b/android/app/src/main/cpp/CMakeLists.txt index 16080710..0ada956a 100644 --- a/android/app/src/main/cpp/CMakeLists.txt +++ b/android/app/src/main/cpp/CMakeLists.txt @@ -12,12 +12,12 @@ project(codepush_diffpatch C) # See third_party/README.md for what we vendor and why. add_library(codepush_diffpatch SHARED diffpatch_jni.c - bspatch_bridge.c + ../../../../../shared/diffpatch/bspatch_bridge.c + + ../../../../../shared/third_party/hdiffpatch/libHDiffPatch/HPatch/patch.c + ../../../../../shared/third_party/hdiffpatch/bsdiff_wrapper/bspatch_wrapper.c + ../../../../../shared/third_party/hdiffpatch/file_for_patch.c - third_party/hdiffpatch/libHDiffPatch/HPatch/patch.c - third_party/hdiffpatch/bsdiff_wrapper/bspatch_wrapper.c - third_party/hdiffpatch/file_for_patch.c - bzip2_error_stub.c third_party/bzip2/bzlib.c third_party/bzip2/decompress.c @@ -28,6 +28,8 @@ add_library(codepush_diffpatch SHARED target_include_directories(codepush_diffpatch PRIVATE . + ../../../../../shared/diffpatch + ../../../../../shared third_party/bzip2 ) diff --git a/android/app/src/main/cpp/third_party/README.md b/android/app/src/main/cpp/third_party/README.md index 3cbf62e2..70255e73 100644 --- a/android/app/src/main/cpp/third_party/README.md +++ b/android/app/src/main/cpp/third_party/README.md @@ -1,23 +1,6 @@ # Vendored sources -These directories contain trimmed copies of two upstream libraries, pinned -to a specific commit. Only the files needed to *apply* a BSDIFF40-style -patch (the `hdiffz -BSD` producer output) are vendored. `hdiffpatch/` is an -unmodified-source copy; `bzip2/bzlib.c` carries one small, documented patch -(see below). - -## hdiffpatch/ - -Source: https://github.com/sisong/HDiffPatch -Pinned commit: `3b9dca715ca492873bf2c49e22e5d5b7d2a78620` (2026-07-31) -License: MIT. - -Files were chosen by tracing the actual dependency graph of -`bsdiff_wrapper/bspatch_wrapper.c` (the BSDIFF40-compatible patch applier), -not by directory boundaries. In particular `libHDiffPatch/HPatch/patch.c` -(~157KB) is HDiffPatch's own diff-format decoder, but it's still required -here because `bspatch_wrapper.c` shares its low-level stream-cache helpers -(`_TOutStreamCache_*`, `getStreamClip`, `_patch_cache_all_old`, etc.). +Note: `shared/third_party/` also contains vendored sources, shared between Android and iOS. ## bzip2/ diff --git a/android/app/src/main/java/com/microsoft/codepush/react/diffpatch/DiffPatch.kt b/android/app/src/main/java/com/microsoft/codepush/react/diffpatch/DiffPatch.kt index 61b8243b..c6b1214e 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/diffpatch/DiffPatch.kt +++ b/android/app/src/main/java/com/microsoft/codepush/react/diffpatch/DiffPatch.kt @@ -37,7 +37,7 @@ object DiffPatch { UNKNOWN; companion object { - // Keep in sync with cpp/bspatch_bridge.h + // Keep in sync with shared/diffpatch/bspatch_bridge.h fun fromNativeCode(code: Int): PatchResult = when (code) { 0 -> OK 1 -> BAD_DIFF_HEADER diff --git a/ios/CodePush.xcodeproj/project.pbxproj b/ios/CodePush.xcodeproj/project.pbxproj index 3010ff50..03c3887e 100644 --- a/ios/CodePush.xcodeproj/project.pbxproj +++ b/ios/CodePush.xcodeproj/project.pbxproj @@ -7,6 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + 0ABCB5DEFE01A7A15552A498 /* file_for_patch.c in Sources */ = {isa = PBXBuildFile; fileRef = 70779807AB59EA4711737F4E /* file_for_patch.c */; }; + 08B8B3B8260E70B7ECA85451 /* bspatch_bridge.c in Sources */ = {isa = PBXBuildFile; fileRef = A430CBE260F09A3233110E28 /* bspatch_bridge.c */; }; + A75C1A7A555663186E25AA3D /* libHDiffPatch/HPatch/patch.c in Sources */ = {isa = PBXBuildFile; fileRef = 827016DF6E52E356F4B89D18 /* libHDiffPatch/HPatch/patch.c */; }; + D8C294C1D2625E79BC302CCC /* bsdiff_wrapper/bspatch_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 0DC7989C75C72A774EF3685F /* bsdiff_wrapper/bspatch_wrapper.c */; }; + 78E9CECCB820DC4BA09BF09A /* file_for_patch.c in Sources */ = {isa = PBXBuildFile; fileRef = 70779807AB59EA4711737F4E /* file_for_patch.c */; }; 13BE3DEE1AC21097009241FE /* CodePush.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* CodePush.m */; }; 1B23B9141BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */; }; 1B762E901C9A5E9A006EF800 /* CodePushErrorUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B762E8F1C9A5E9A006EF800 /* CodePushErrorUtils.m */; }; @@ -77,10 +82,12 @@ 3221E4902C8ABE1400268379 /* SSZipCommon.h in Headers */ = {isa = PBXBuildFile; fileRef = 3221E44E2C8ABE1300268379 /* SSZipCommon.h */; }; 3221E4912C8ABE1400268379 /* SSZipArchive.m in Sources */ = {isa = PBXBuildFile; fileRef = 3221E44F2C8ABE1300268379 /* SSZipArchive.m */; }; 3221E4922C8ABE1400268379 /* SSZipArchive.m in Sources */ = {isa = PBXBuildFile; fileRef = 3221E44F2C8ABE1300268379 /* SSZipArchive.m */; }; + 47F66D5AF3C3185E1A3E3B15 /* bspatch_bridge.c in Sources */ = {isa = PBXBuildFile; fileRef = A430CBE260F09A3233110E28 /* bspatch_bridge.c */; }; 540D20121C7684FE00D6EF41 /* CodePushUpdateUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 540D20111C7684FE00D6EF41 /* CodePushUpdateUtils.m */; }; 5421FE311C58AD5A00986A55 /* CodePushTelemetryManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */; }; 5498D8F61D21F14100B5EB43 /* CodePushUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 5498D8F51D21F14100B5EB43 /* CodePushUtils.m */; }; 54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */; }; + 5C6B00FACE8707503D366F7C /* Fixtures in Resources */ = {isa = PBXBuildFile; fileRef = E74D80ECD8DD03C4C0772B7B /* Fixtures */; }; 6463C82D1EBA0CFB0095B8CD /* CodePushUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 5498D8F51D21F14100B5EB43 /* CodePushUtils.m */; }; 6463C82E1EBA0CFB0095B8CD /* CodePush.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* CodePush.m */; }; 6463C82F1EBA0CFB0095B8CD /* CodePushConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 81D51F391B6181C2000DA084 /* CodePushConfig.m */; }; @@ -97,6 +104,9 @@ 81D51F3A1B6181C2000DA084 /* CodePushConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 81D51F391B6181C2000DA084 /* CodePushConfig.m */; }; 8482F84C1E24C58300F793DB /* CodePush.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 13BE3DEC1AC21097009241FE /* CodePush.h */; }; 8482F84E1E24C66300F793DB /* CodePush.h in Headers */ = {isa = PBXBuildFile; fileRef = 13BE3DEC1AC21097009241FE /* CodePush.h */; }; + A88F11124A2120A8373A8B61 /* bsdiff_wrapper/bspatch_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 0DC7989C75C72A774EF3685F /* bsdiff_wrapper/bspatch_wrapper.c */; }; + B5B50F91FAAF80444988E284 /* BSPatchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95D6DD0EACAC8D095880DFD0 /* BSPatchTests.swift */; }; + DC983F1C71E0E7131BB343C5 /* libHDiffPatch/HPatch/patch.c in Sources */ = {isa = PBXBuildFile; fileRef = 827016DF6E52E356F4B89D18 /* libHDiffPatch/HPatch/patch.c */; }; F85736761F4F03BF00C9C00A /* MF_Base64Additions.h in Headers */ = {isa = PBXBuildFile; fileRef = F85736731F4F03BF00C9C00A /* MF_Base64Additions.h */; }; F85736771F4F03BF00C9C00A /* MF_Base64Additions.m in Sources */ = {isa = PBXBuildFile; fileRef = F85736741F4F03BF00C9C00A /* MF_Base64Additions.m */; }; F886644D1F4AD1EE0036D01B /* JWTAlgorithm.h in Headers */ = {isa = PBXBuildFile; fileRef = F88664151F4AD1EE0036D01B /* JWTAlgorithm.h */; }; @@ -171,6 +181,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0BF68F85125CF81D7EB65ABB /* bspatch_bridge.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = bspatch_bridge.h; sourceTree = ""; }; + 0DC7989C75C72A774EF3685F /* bsdiff_wrapper/bspatch_wrapper.c */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.c; path = bsdiff_wrapper/bspatch_wrapper.c; sourceTree = ""; }; 134814201AA4EA6300B7C361 /* libCodePush.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCodePush.a; sourceTree = BUILT_PRODUCTS_DIR; }; 13BE3DEC1AC21097009241FE /* CodePush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CodePush.h; path = CodePush/CodePush.h; sourceTree = ""; }; 13BE3DED1AC21097009241FE /* CodePush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePush.m; path = CodePush/CodePush.m; sourceTree = ""; }; @@ -214,12 +226,19 @@ 3221E44E2C8ABE1300268379 /* SSZipCommon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSZipCommon.h; sourceTree = ""; }; 3221E44F2C8ABE1300268379 /* SSZipArchive.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSZipArchive.m; sourceTree = ""; }; 3221E4502C8ABE1300268379 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4AB96C756000A7E833CBA8B7 /* CodePushTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CodePushTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 540D20111C7684FE00D6EF41 /* CodePushUpdateUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushUpdateUtils.m; path = CodePush/CodePushUpdateUtils.m; sourceTree = ""; }; 5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushTelemetryManager.m; path = CodePush/CodePushTelemetryManager.m; sourceTree = ""; }; 5498D8F51D21F14100B5EB43 /* CodePushUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushUtils.m; path = CodePush/CodePushUtils.m; sourceTree = ""; }; 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushDownloadHandler.m; path = CodePush/CodePushDownloadHandler.m; sourceTree = ""; }; + 70779807AB59EA4711737F4E /* file_for_patch.c */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.c; path = file_for_patch.c; sourceTree = ""; }; 810D4E6C1B96935000B397E9 /* CodePushPackage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushPackage.m; path = CodePush/CodePushPackage.m; sourceTree = ""; }; 81D51F391B6181C2000DA084 /* CodePushConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushConfig.m; path = CodePush/CodePushConfig.m; sourceTree = ""; }; + 827016DF6E52E356F4B89D18 /* libHDiffPatch/HPatch/patch.c */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.c; path = libHDiffPatch/HPatch/patch.c; sourceTree = ""; }; + 95D6DD0EACAC8D095880DFD0 /* BSPatchTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BSPatchTests.swift; sourceTree = ""; }; + A430CBE260F09A3233110E28 /* bspatch_bridge.c */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.c; path = bspatch_bridge.c; sourceTree = ""; }; + E74D80ECD8DD03C4C0772B7B /* Fixtures */ = {isa = PBXFileReference; explicitFileType = folder; includeInIndex = 0; path = Fixtures; sourceTree = ""; }; + E9FA144425AE78B97AD6C870 /* DiffPatchTests-Bridging-Header.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "DiffPatchTests-Bridging-Header.h"; sourceTree = ""; }; F85736731F4F03BF00C9C00A /* MF_Base64Additions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MF_Base64Additions.h; sourceTree = ""; }; F85736741F4F03BF00C9C00A /* MF_Base64Additions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MF_Base64Additions.m; sourceTree = ""; }; F85736751F4F03BF00C9C00A /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -291,6 +310,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 92F210124F3F87350E684641 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -298,10 +324,21 @@ isa = PBXGroup; children = ( 134814201AA4EA6300B7C361 /* libCodePush.a */, + 4AB96C756000A7E833CBA8B7 /* CodePushTests.xctest */, ); name = Products; sourceTree = ""; }; + 2EB4092D590839720984EE01 /* RepoRoot */ = { + isa = PBXGroup; + children = ( + 66FDDF29DBB202771A6A06DD /* shared-diffpatch */, + 41A7D02DA375C6871A90065B /* third_party-hdiffpatch */, + ); + name = RepoRoot; + path = ..; + sourceTree = ""; + }; 3221E4282C8ABE1300268379 /* SSZipArchive */ = { isa = PBXGroup; children = ( @@ -371,6 +408,17 @@ path = "Supporting Files"; sourceTree = ""; }; + 41A7D02DA375C6871A90065B /* third_party-hdiffpatch */ = { + isa = PBXGroup; + children = ( + 827016DF6E52E356F4B89D18 /* libHDiffPatch/HPatch/patch.c */, + 0DC7989C75C72A774EF3685F /* bsdiff_wrapper/bspatch_wrapper.c */, + 70779807AB59EA4711737F4E /* file_for_patch.c */, + ); + name = "third_party-hdiffpatch"; + path = shared/third_party/hdiffpatch; + sourceTree = ""; + }; 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( @@ -392,7 +440,29 @@ 134814211AA4EA7D00B7C361 /* Products */, C31BB4D5018A48D5288C5137 /* Frameworks */, F886647B1F4ADB500036D01B /* libCodePush.a */, + 2EB4092D590839720984EE01 /* RepoRoot */, + C283376E6F60830C5CB41994 /* CodePushDiffPatchTests */, + ); + sourceTree = ""; + }; + 66FDDF29DBB202771A6A06DD /* shared-diffpatch */ = { + isa = PBXGroup; + children = ( + A430CBE260F09A3233110E28 /* bspatch_bridge.c */, + 0BF68F85125CF81D7EB65ABB /* bspatch_bridge.h */, + ); + name = "shared-diffpatch"; + path = shared/diffpatch; + sourceTree = ""; + }; + C283376E6F60830C5CB41994 /* CodePushDiffPatchTests */ = { + isa = PBXGroup; + children = ( + 95D6DD0EACAC8D095880DFD0 /* BSPatchTests.swift */, + E9FA144425AE78B97AD6C870 /* DiffPatchTests-Bridging-Header.h */, + E74D80ECD8DD03C4C0772B7B /* Fixtures */, ); + path = CodePushDiffPatchTests; sourceTree = ""; }; C31BB4D5018A48D5288C5137 /* Frameworks */ = { @@ -692,6 +762,23 @@ productReference = F886647B1F4ADB500036D01B /* libCodePush.a */; productType = "com.apple.product-type.library.static"; }; + 9750B3DE460E834FF67380BD /* CodePushTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = A11B0911B46B588F8C213676 /* Build configuration list for PBXNativeTarget "CodePushTests" */; + buildPhases = ( + 7F757C5E9DC4CC7D30C29506 /* Sources */, + 92F210124F3F87350E684641 /* Frameworks */, + C50E6815B9794CFCD65A8F0C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CodePushTests; + productName = CodePushTests; + productReference = 4AB96C756000A7E833CBA8B7 /* CodePushTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -708,6 +795,9 @@ CreatedOnToolsVersion = 8.3.2; ProvisioningStyle = Automatic; }; + 9750B3DE460E834FF67380BD = { + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "CodePush" */; @@ -725,15 +815,31 @@ targets = ( 58B511DA1A9E6C8500147676 /* CodePush */, 6463C8231EBA0CB60095B8CD /* CodePush-tvOS */, + 9750B3DE460E834FF67380BD /* CodePushTests */, ); }; /* End PBXProject section */ +/* Begin PBXResourcesBuildPhase section */ + C50E6815B9794CFCD65A8F0C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5C6B00FACE8707503D366F7C /* Fixtures in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 58B511D71A9E6C8500147676 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 08B8B3B8260E70B7ECA85451 /* bspatch_bridge.c in Sources */, + A75C1A7A555663186E25AA3D /* libHDiffPatch/HPatch/patch.c in Sources */, + D8C294C1D2625E79BC302CCC /* bsdiff_wrapper/bspatch_wrapper.c in Sources */, + 78E9CECCB820DC4BA09BF09A /* file_for_patch.c in Sources */, 3221E47D2C8ABE1400268379 /* mz_os_posix.c in Sources */, 3221E4792C8ABE1300268379 /* mz_os.c in Sources */, F88664621F4AD1EE0036D01B /* JWTCryptoSecurity.m in Sources */, @@ -818,9 +924,52 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7F757C5E9DC4CC7D30C29506 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B5B50F91FAAF80444988E284 /* BSPatchTests.swift in Sources */, + 47F66D5AF3C3185E1A3E3B15 /* bspatch_bridge.c in Sources */, + DC983F1C71E0E7131BB343C5 /* libHDiffPatch/HPatch/patch.c in Sources */, + A88F11124A2120A8373A8B61 /* bsdiff_wrapper/bspatch_wrapper.c in Sources */, + 0ABCB5DEFE01A7A15552A498 /* file_for_patch.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ + 4E5D9A15C8BA6CEA802EA38B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "_IS_NEED_BLOCK_DEV=0", + "_IS_USED_MULTITHREAD=0", + "_IS_NEED_DIR_DIFF_PATCH=0", + ); + GENERATE_INFOPLIST_FILE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../shared", + "$(SRCROOT)/../shared/diffpatch", + "$(SRCROOT)/../shared/third_party/hdiffpatch", + "$(SRCROOT)/../shared/third_party/hdiffpatch/libHDiffPatch/HPatch", + ); + IPHONEOS_DEPLOYMENT_TARGET = 15.5; + OTHER_LDFLAGS = ( + "$(inherited)", + "-lbz2", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.codepush.CodePushTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_OBJC_BRIDGING_HEADER = "CodePushDiffPatchTests/DiffPatchTests-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; 58B511ED1A9E6C8500147676 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -931,18 +1080,26 @@ HAVE_WZAES, HAVE_ZLIB, ZLIB_COMPAT, + "_IS_NEED_BLOCK_DEV=0", + "_IS_USED_MULTITHREAD=0", + "_IS_NEED_DIR_DIFF_PATCH=0", ); HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../../react-native/React/**", "$(SRC_ROOT)/JWT/**", + "$(SRCROOT)/../shared", + "$(SRCROOT)/../shared/diffpatch", + "$(SRCROOT)/../shared/third_party/hdiffpatch", + "$(SRCROOT)/../shared/third_party/hdiffpatch/libHDiffPatch/HPatch", ); IPHONEOS_DEPLOYMENT_TARGET = 15.5; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = ( "-ObjC", "-lz", + "-lbz2", ); PRODUCT_NAME = CodePush; SKIP_INSTALL = YES; @@ -960,18 +1117,26 @@ HAVE_WZAES, HAVE_ZLIB, ZLIB_COMPAT, + "_IS_NEED_BLOCK_DEV=0", + "_IS_USED_MULTITHREAD=0", + "_IS_NEED_DIR_DIFF_PATCH=0", ); HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../../react-native/React/**", "$(SRC_ROOT)/JWT/**", + "$(SRCROOT)/../shared", + "$(SRCROOT)/../shared/diffpatch", + "$(SRCROOT)/../shared/third_party/hdiffpatch", + "$(SRCROOT)/../shared/third_party/hdiffpatch/libHDiffPatch/HPatch", ); IPHONEOS_DEPLOYMENT_TARGET = 15.5; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = ( "-ObjC", "-lz", + "-lbz2", ); PRODUCT_NAME = CodePush; SKIP_INSTALL = YES; @@ -1016,6 +1181,38 @@ }; name = Release; }; + D41A1215D79D9643EC63FA1F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "_IS_NEED_BLOCK_DEV=0", + "_IS_USED_MULTITHREAD=0", + "_IS_NEED_DIR_DIFF_PATCH=0", + ); + GENERATE_INFOPLIST_FILE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../shared", + "$(SRCROOT)/../shared/diffpatch", + "$(SRCROOT)/../shared/third_party/hdiffpatch", + "$(SRCROOT)/../shared/third_party/hdiffpatch/libHDiffPatch/HPatch", + ); + IPHONEOS_DEPLOYMENT_TARGET = 15.5; + OTHER_LDFLAGS = ( + "$(inherited)", + "-lbz2", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.codepush.CodePushTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_OBJC_BRIDGING_HEADER = "CodePushDiffPatchTests/DiffPatchTests-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1046,6 +1243,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + A11B0911B46B588F8C213676 /* Build configuration list for PBXNativeTarget "CodePushTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D41A1215D79D9643EC63FA1F /* Release */, + 4E5D9A15C8BA6CEA802EA38B /* Debug */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 58B511D31A9E6C8500147676 /* Project object */; diff --git a/ios/CodePush.xcodeproj/xcshareddata/xcschemes/CodePushTests.xcscheme b/ios/CodePush.xcodeproj/xcshareddata/xcschemes/CodePushTests.xcscheme new file mode 100644 index 00000000..8420f54f --- /dev/null +++ b/ios/CodePush.xcodeproj/xcshareddata/xcschemes/CodePushTests.xcscheme @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/CodePushDiffPatchTests/BSPatchTests.swift b/ios/CodePushDiffPatchTests/BSPatchTests.swift new file mode 100644 index 00000000..726ddff6 --- /dev/null +++ b/ios/CodePushDiffPatchTests/BSPatchTests.swift @@ -0,0 +1,127 @@ +import XCTest + +final class BSPatchTests: XCTestCase { + + private var tempDir: URL! + + override func setUpWithError() throws { + tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) + } + + override func tearDownWithError() throws { + try? FileManager.default.removeItem(at: tempDir) + } + + private func fixtureURL(_ relativePath: String) -> URL { + let bundle = Bundle(for: type(of: self)) + guard let resourceURL = bundle.url(forResource: "Fixtures", withExtension: nil) else { + fatalError("Fixtures resource folder not found in test bundle") + } + return resourceURL.appendingPathComponent(relativePath) + } + + private func outputURL(_ name: String) -> URL { + tempDir.appendingPathComponent(name) + } + + private func applyPatch(oldFile: URL, diffFile: URL, outFile: URL) -> CodePushBSPatchResult { + oldFile.path.withCString { oldPath in + diffFile.path.withCString { diffPath in + outFile.path.withCString { outPath in + codepush_bspatch_apply(oldPath, diffPath, outPath) + } + } + } + } + + // An ordinary text-file diff, several inserted/changed/copied regions. + func testApplyPatch_basicDiff_succeedsAndMatchesExpectedOutput() throws { + let oldFile = fixtureURL("basic/old.dat") + let diffFile = fixtureURL("basic/patch.bsdiff") + let expectedNewFile = fixtureURL("basic/new.dat") + let outFile = outputURL("basic_out.dat") + + let result = applyPatch(oldFile: oldFile, diffFile: diffFile, outFile: outFile) + + XCTAssertEqual(result, CODEPUSH_BSPATCH_OK) + XCTAssertEqual(try Data(contentsOf: outFile), try Data(contentsOf: expectedNewFile)) + } + + // Real BSDIFF40 patch whose only control entry is a single full-length copy from the old file. + func testApplyPatch_identicalOldAndNew_succeeds() throws { + let oldFile = fixtureURL("identical/old.dat") + let diffFile = fixtureURL("identical/patch.bsdiff") + let expectedNewFile = fixtureURL("identical/new.dat") + let outFile = outputURL("identical_out.dat") + + let result = applyPatch(oldFile: oldFile, diffFile: diffFile, outFile: outFile) + + XCTAssertEqual(result, CODEPUSH_BSPATCH_OK) + XCTAssertEqual(try Data(contentsOf: outFile), try Data(contentsOf: expectedNewFile)) + } + + func testApplyPatch_emptyOldFile_succeeds() throws { + let oldFile = fixtureURL("empty_old/old.dat") + let diffFile = fixtureURL("empty_old/patch.bsdiff") + let expectedNewFile = fixtureURL("empty_old/new.dat") + let outFile = outputURL("empty_old_out.dat") + + let result = applyPatch(oldFile: oldFile, diffFile: diffFile, outFile: outFile) + + XCTAssertEqual(result, CODEPUSH_BSPATCH_OK) + XCTAssertEqual(try Data(contentsOf: outFile), try Data(contentsOf: expectedNewFile)) + } + + // Well-formed length, wrong magic bytes (hand-written, not a real bsdiff output). + func testApplyPatch_badDiffHeader_returnsBadDiffHeader() { + let oldFile = fixtureURL("bad_header/old.dat") + let diffFile = fixtureURL("bad_header/patch.bsdiff") + let outFile = outputURL("bad_header_out.dat") + + let result = applyPatch(oldFile: oldFile, diffFile: diffFile, outFile: outFile) + + XCTAssertEqual(result, CODEPUSH_BSPATCH_ERR_BAD_DIFF_HEADER) + XCTAssertFalse(FileManager.default.fileExists(atPath: outFile.path), + "output file should not be left behind after a failed patch") + } + + // HDiffPatch's bounds checks must reject these inputs rather than reading out of range or + // silently emitting corrupt output. wrong_old/old.dat is unrelated to (and shorter than) + // basic/old.dat, so basic/patch.bsdiff's copy instructions reference offsets out of range for it. + func testApplyPatch_mismatchedOldFile_returnsPatchFailed() { + let oldFile = fixtureURL("wrong_old/old.dat") + let diffFile = fixtureURL("basic/patch.bsdiff") + let outFile = outputURL("mismatched_old_out.dat") + + let result = applyPatch(oldFile: oldFile, diffFile: diffFile, outFile: outFile) + + XCTAssertEqual(result, CODEPUSH_BSPATCH_ERR_PATCH_FAILED) + XCTAssertFalse(FileManager.default.fileExists(atPath: outFile.path), + "output file should not be left behind after a failed patch") + } + + func testApplyPatch_missingOldFile_returnsOpenOldFailed() { + let missingOldFile = outputURL("does_not_exist_old.dat") + let diffFile = fixtureURL("basic/patch.bsdiff") + let outFile = outputURL("missing_old_out.dat") + + let result = applyPatch(oldFile: missingOldFile, diffFile: diffFile, outFile: outFile) + + XCTAssertEqual(result, CODEPUSH_BSPATCH_ERR_OPEN_OLD) + XCTAssertFalse(FileManager.default.fileExists(atPath: outFile.path), + "output file should not be left behind after a failed patch") + } + + func testApplyPatch_missingDiffFile_returnsOpenDiffFailed() { + let oldFile = fixtureURL("basic/old.dat") + let missingDiffFile = outputURL("does_not_exist.bsdiff") + let outFile = outputURL("missing_diff_out.dat") + + let result = applyPatch(oldFile: oldFile, diffFile: missingDiffFile, outFile: outFile) + + XCTAssertEqual(result, CODEPUSH_BSPATCH_ERR_OPEN_DIFF) + XCTAssertFalse(FileManager.default.fileExists(atPath: outFile.path), + "output file should not be left behind after a failed patch") + } +} diff --git a/ios/CodePushDiffPatchTests/DiffPatchTests-Bridging-Header.h b/ios/CodePushDiffPatchTests/DiffPatchTests-Bridging-Header.h new file mode 100644 index 00000000..43382433 --- /dev/null +++ b/ios/CodePushDiffPatchTests/DiffPatchTests-Bridging-Header.h @@ -0,0 +1 @@ +#import "bspatch_bridge.h" diff --git a/ios/CodePushDiffPatchTests/Fixtures/bad_header/old.dat b/ios/CodePushDiffPatchTests/Fixtures/bad_header/old.dat new file mode 100644 index 00000000..f6bfa2d0 --- /dev/null +++ b/ios/CodePushDiffPatchTests/Fixtures/bad_header/old.dat @@ -0,0 +1,2 @@ +Irrelevant content - old.dat just needs to open successfully so the +test reaches the diff-header check this fixture is actually exercising. diff --git a/ios/CodePushDiffPatchTests/Fixtures/bad_header/patch.bsdiff b/ios/CodePushDiffPatchTests/Fixtures/bad_header/patch.bsdiff new file mode 100644 index 00000000..d574da6b Binary files /dev/null and b/ios/CodePushDiffPatchTests/Fixtures/bad_header/patch.bsdiff differ diff --git a/ios/CodePushDiffPatchTests/Fixtures/basic/new.dat b/ios/CodePushDiffPatchTests/Fixtures/basic/new.dat new file mode 100644 index 00000000..54241f83 --- /dev/null +++ b/ios/CodePushDiffPatchTests/Fixtures/basic/new.dat @@ -0,0 +1,25 @@ +function greet(name) { + console.log("Hello there, " + name + "!"); + return "Hello there, " + name + "!"; +} + +function farewell(name) { + console.log("Goodbye, " + name + "."); + return "Goodbye, " + name + "."; +} + +function shout(name) { + console.log("HEY, " + name.toUpperCase() + "!!!"); + return "HEY, " + name.toUpperCase() + "!!!"; +} + +var VERSION = "1.1.0"; +var BUILD_NUMBER = 43; + +module.exports = { + greet: greet, + farewell: farewell, + shout: shout, + VERSION: VERSION, + BUILD_NUMBER: BUILD_NUMBER, +}; diff --git a/ios/CodePushDiffPatchTests/Fixtures/basic/old.dat b/ios/CodePushDiffPatchTests/Fixtures/basic/old.dat new file mode 100644 index 00000000..b4667705 --- /dev/null +++ b/ios/CodePushDiffPatchTests/Fixtures/basic/old.dat @@ -0,0 +1,19 @@ +function greet(name) { + console.log("Hello, " + name + "!"); + return "Hello, " + name + "!"; +} + +function farewell(name) { + console.log("Goodbye, " + name + "."); + return "Goodbye, " + name + "."; +} + +var VERSION = "1.0.0"; +var BUILD_NUMBER = 42; + +module.exports = { + greet: greet, + farewell: farewell, + VERSION: VERSION, + BUILD_NUMBER: BUILD_NUMBER, +}; diff --git a/ios/CodePushDiffPatchTests/Fixtures/basic/patch.bsdiff b/ios/CodePushDiffPatchTests/Fixtures/basic/patch.bsdiff new file mode 100644 index 00000000..a0e9d31a Binary files /dev/null and b/ios/CodePushDiffPatchTests/Fixtures/basic/patch.bsdiff differ diff --git a/ios/CodePushDiffPatchTests/Fixtures/empty_old/new.dat b/ios/CodePushDiffPatchTests/Fixtures/empty_old/new.dat new file mode 100644 index 00000000..3559a9ff --- /dev/null +++ b/ios/CodePushDiffPatchTests/Fixtures/empty_old/new.dat @@ -0,0 +1,3 @@ +Everything in this file is new: the old side is a zero-byte file, so +the whole patch body is a literal insert with no copy-from-old control +entries at all. diff --git a/ios/CodePushDiffPatchTests/Fixtures/empty_old/old.dat b/ios/CodePushDiffPatchTests/Fixtures/empty_old/old.dat new file mode 100644 index 00000000..e69de29b diff --git a/ios/CodePushDiffPatchTests/Fixtures/empty_old/patch.bsdiff b/ios/CodePushDiffPatchTests/Fixtures/empty_old/patch.bsdiff new file mode 100644 index 00000000..4b376b26 Binary files /dev/null and b/ios/CodePushDiffPatchTests/Fixtures/empty_old/patch.bsdiff differ diff --git a/ios/CodePushDiffPatchTests/Fixtures/identical/new.dat b/ios/CodePushDiffPatchTests/Fixtures/identical/new.dat new file mode 100644 index 00000000..3e69fff2 --- /dev/null +++ b/ios/CodePushDiffPatchTests/Fixtures/identical/new.dat @@ -0,0 +1,4 @@ +This file is byte-for-byte identical on both sides of the patch. +It exercises the zero-delta path: a real BSDIFF40 patch whose only +control entry is a single full-length copy from the old file, with +no literal bytes and no seek. Nothing to add or skip. diff --git a/ios/CodePushDiffPatchTests/Fixtures/identical/old.dat b/ios/CodePushDiffPatchTests/Fixtures/identical/old.dat new file mode 100644 index 00000000..3e69fff2 --- /dev/null +++ b/ios/CodePushDiffPatchTests/Fixtures/identical/old.dat @@ -0,0 +1,4 @@ +This file is byte-for-byte identical on both sides of the patch. +It exercises the zero-delta path: a real BSDIFF40 patch whose only +control entry is a single full-length copy from the old file, with +no literal bytes and no seek. Nothing to add or skip. diff --git a/ios/CodePushDiffPatchTests/Fixtures/identical/patch.bsdiff b/ios/CodePushDiffPatchTests/Fixtures/identical/patch.bsdiff new file mode 100644 index 00000000..59b175d0 Binary files /dev/null and b/ios/CodePushDiffPatchTests/Fixtures/identical/patch.bsdiff differ diff --git a/ios/CodePushDiffPatchTests/Fixtures/wrong_old/old.dat b/ios/CodePushDiffPatchTests/Fixtures/wrong_old/old.dat new file mode 100644 index 00000000..9c90f626 --- /dev/null +++ b/ios/CodePushDiffPatchTests/Fixtures/wrong_old/old.dat @@ -0,0 +1,3 @@ +This is a completely unrelated old file, deliberately shaped so that +applying fixtures/basic/patch.bsdiff against it does not match the old +file bsdiff was actually built from. diff --git a/package.json b/package.json index f9003811..fe662829 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,8 @@ "test:fast:expo:ios": "mocha --recursive --bail bin/test --ios --expo", "test:debugger:android": "mocha --recursive --inspect-brk=0.0.0.0 bin/test --android", "test:debugger:ios": "mocha --recursive --inspect-brk=0.0.0.0 bin/test --ios", + "test:unit:android": "cd android && ./gradlew :app:test", + "test:unit:ios": "xcodebuild test -project ios/CodePush.xcodeproj -scheme CodePushTests -destination \"platform=iOS Simulator,id=$(xcrun simctl list devices available | grep -m1 iPhone | grep -oE '[0-9A-F-]{36}')\"", "tslint": "tslint -c tslint.json test/**/*.ts" }, "repository": { diff --git a/android/app/src/main/cpp/bspatch_bridge.c b/shared/diffpatch/bspatch_bridge.c similarity index 100% rename from android/app/src/main/cpp/bspatch_bridge.c rename to shared/diffpatch/bspatch_bridge.c diff --git a/android/app/src/main/cpp/bspatch_bridge.h b/shared/diffpatch/bspatch_bridge.h similarity index 100% rename from android/app/src/main/cpp/bspatch_bridge.h rename to shared/diffpatch/bspatch_bridge.h diff --git a/shared/third_party/README.md b/shared/third_party/README.md new file mode 100644 index 00000000..338762e6 --- /dev/null +++ b/shared/third_party/README.md @@ -0,0 +1,16 @@ +# Vendored sources + +Vendored libraries shared across native iOS and Android modules. + +## hdiffpatch/ + +Source: https://github.com/sisong/HDiffPatch +Pinned commit: `3b9dca715ca492873bf2c49e22e5d5b7d2a78620` (2026-07-31) +License: MIT. + +Files were chosen by tracing the actual dependency graph of +`bsdiff_wrapper/bspatch_wrapper.c` (the BSDIFF40-compatible patch applier), +not by directory boundaries. In particular `libHDiffPatch/HPatch/patch.c` +(~157KB) is HDiffPatch's own diff-format decoder, but it's still required +here because `bspatch_wrapper.c` shares its low-level stream-cache helpers +(`_TOutStreamCache_*`, `getStreamClip`, `_patch_cache_all_old`, etc.). diff --git a/android/app/src/main/cpp/third_party/hdiffpatch/LICENSE b/shared/third_party/hdiffpatch/LICENSE similarity index 100% rename from android/app/src/main/cpp/third_party/hdiffpatch/LICENSE rename to shared/third_party/hdiffpatch/LICENSE diff --git a/android/app/src/main/cpp/third_party/hdiffpatch/bsdiff_wrapper/bspatch_wrapper.c b/shared/third_party/hdiffpatch/bsdiff_wrapper/bspatch_wrapper.c similarity index 100% rename from android/app/src/main/cpp/third_party/hdiffpatch/bsdiff_wrapper/bspatch_wrapper.c rename to shared/third_party/hdiffpatch/bsdiff_wrapper/bspatch_wrapper.c diff --git a/android/app/src/main/cpp/third_party/hdiffpatch/bsdiff_wrapper/bspatch_wrapper.h b/shared/third_party/hdiffpatch/bsdiff_wrapper/bspatch_wrapper.h similarity index 100% rename from android/app/src/main/cpp/third_party/hdiffpatch/bsdiff_wrapper/bspatch_wrapper.h rename to shared/third_party/hdiffpatch/bsdiff_wrapper/bspatch_wrapper.h diff --git a/android/app/src/main/cpp/third_party/hdiffpatch/decompress_plugin_demo.h b/shared/third_party/hdiffpatch/decompress_plugin_demo.h similarity index 100% rename from android/app/src/main/cpp/third_party/hdiffpatch/decompress_plugin_demo.h rename to shared/third_party/hdiffpatch/decompress_plugin_demo.h diff --git a/android/app/src/main/cpp/third_party/hdiffpatch/dirDiffPatch/dir_patch/dir_patch_types.h b/shared/third_party/hdiffpatch/dirDiffPatch/dir_patch/dir_patch_types.h similarity index 100% rename from android/app/src/main/cpp/third_party/hdiffpatch/dirDiffPatch/dir_patch/dir_patch_types.h rename to shared/third_party/hdiffpatch/dirDiffPatch/dir_patch/dir_patch_types.h diff --git a/android/app/src/main/cpp/third_party/hdiffpatch/file_for_patch.c b/shared/third_party/hdiffpatch/file_for_patch.c similarity index 100% rename from android/app/src/main/cpp/third_party/hdiffpatch/file_for_patch.c rename to shared/third_party/hdiffpatch/file_for_patch.c diff --git a/android/app/src/main/cpp/third_party/hdiffpatch/file_for_patch.h b/shared/third_party/hdiffpatch/file_for_patch.h similarity index 100% rename from android/app/src/main/cpp/third_party/hdiffpatch/file_for_patch.h rename to shared/third_party/hdiffpatch/file_for_patch.h diff --git a/android/app/src/main/cpp/third_party/hdiffpatch/libHDiffPatch/HPatch/checksum_plugin.h b/shared/third_party/hdiffpatch/libHDiffPatch/HPatch/checksum_plugin.h similarity index 100% rename from android/app/src/main/cpp/third_party/hdiffpatch/libHDiffPatch/HPatch/checksum_plugin.h rename to shared/third_party/hdiffpatch/libHDiffPatch/HPatch/checksum_plugin.h diff --git a/android/app/src/main/cpp/third_party/hdiffpatch/libHDiffPatch/HPatch/hpatch_mt/hpatch_mt.h b/shared/third_party/hdiffpatch/libHDiffPatch/HPatch/hpatch_mt/hpatch_mt.h similarity index 100% rename from android/app/src/main/cpp/third_party/hdiffpatch/libHDiffPatch/HPatch/hpatch_mt/hpatch_mt.h rename to shared/third_party/hdiffpatch/libHDiffPatch/HPatch/hpatch_mt/hpatch_mt.h diff --git a/android/app/src/main/cpp/third_party/hdiffpatch/libHDiffPatch/HPatch/patch.c b/shared/third_party/hdiffpatch/libHDiffPatch/HPatch/patch.c similarity index 100% rename from android/app/src/main/cpp/third_party/hdiffpatch/libHDiffPatch/HPatch/patch.c rename to shared/third_party/hdiffpatch/libHDiffPatch/HPatch/patch.c diff --git a/android/app/src/main/cpp/third_party/hdiffpatch/libHDiffPatch/HPatch/patch.h b/shared/third_party/hdiffpatch/libHDiffPatch/HPatch/patch.h similarity index 100% rename from android/app/src/main/cpp/third_party/hdiffpatch/libHDiffPatch/HPatch/patch.h rename to shared/third_party/hdiffpatch/libHDiffPatch/HPatch/patch.h diff --git a/android/app/src/main/cpp/third_party/hdiffpatch/libHDiffPatch/HPatch/patch_private.h b/shared/third_party/hdiffpatch/libHDiffPatch/HPatch/patch_private.h similarity index 100% rename from android/app/src/main/cpp/third_party/hdiffpatch/libHDiffPatch/HPatch/patch_private.h rename to shared/third_party/hdiffpatch/libHDiffPatch/HPatch/patch_private.h diff --git a/android/app/src/main/cpp/third_party/hdiffpatch/libHDiffPatch/HPatch/patch_types.h b/shared/third_party/hdiffpatch/libHDiffPatch/HPatch/patch_types.h similarity index 100% rename from android/app/src/main/cpp/third_party/hdiffpatch/libHDiffPatch/HPatch/patch_types.h rename to shared/third_party/hdiffpatch/libHDiffPatch/HPatch/patch_types.h