Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/scripts/run-android-tests.sh
Original file line number Diff line number Diff line change
@@ -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::"
31 changes: 16 additions & 15 deletions .github/workflows/ci-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Comment thread
ofalvai marked this conversation as resolved.

- name: Run iOS E2E Tests
run: npm run ${{ matrix.test-command }}

- name: Upload iOS Simulator crash reports
if: failure()
Expand Down
2 changes: 2 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ android/.gradle
android/**/*.iml
android/.idea

ios/CodePushDiffPatchTests/


# Windows
windows/.vs/
Expand Down
8 changes: 5 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
19 changes: 16 additions & 3 deletions CodePush.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -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 <linux/fs.h> 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
Expand Down
12 changes: 7 additions & 5 deletions android/app/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -28,6 +28,8 @@ add_library(codepush_diffpatch SHARED

target_include_directories(codepush_diffpatch PRIVATE
.
../../../../../shared/diffpatch
../../../../../shared
third_party/bzip2
)

Expand Down
19 changes: 1 addition & 18 deletions android/app/src/main/cpp/third_party/README.md
Original file line number Diff line number Diff line change
@@ -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/

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading