-
Notifications
You must be signed in to change notification settings - Fork 37
chore: Add react-native contract tets. #1149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
cc0e50a
chore: Add react-native contract tets.
kinyoklion 79a7790
fix: Resolve lint errors in react-native contract tests entity
kinyoklion 2bbcd86
chore: Pin GitHub Actions to SHAs in contract tests workflow
kinyoklion 528852f
fix: Use POSIX-compatible shell syntax in emulator runner script
kinyoklion 847ad59
fix: Move emulator runner script to external file
kinyoklion 87e2b88
perf: Switch to debug build with x86_64-only ABI for contract tests
kinyoklion b2ea02a
fix: Use release build (debug has no JS bundle) and capture logs insi…
kinyoklion 968a3f5
fix: Also skip lintVitalRelease task (not just its subtasks)
kinyoklion 496963f
fix: Require SDK_TEST_HARNESS_PATH instead of hardcoding personal path
kinyoklion 5ef11f3
fix: Move lib into compilerOptions in adapter tsconfig
kinyoklion 3ded7cf
fix: Address PR review comments
kinyoklion File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| name: sdk/react-native/contract-tests | ||
|
|
||
| on: | ||
| push: | ||
| branches: [main, 'feat/**'] | ||
| paths-ignore: | ||
| - '**.md' | ||
| pull_request: | ||
| branches: [main, 'feat/**'] | ||
| paths: | ||
| - 'packages/shared/common/**' | ||
| - 'packages/shared/sdk-client/**' | ||
| - 'packages/sdk/react-native/**' | ||
| - '.github/workflows/react-native-contract-tests.yml' | ||
|
|
||
| jobs: | ||
| contract-tests-android: | ||
| runs-on: ubuntu-22.04 | ||
| timeout-minutes: 30 | ||
| steps: | ||
| # https://github.com/actions/checkout/releases/tag/v6.0.2 | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
|
|
||
| - name: Setup Node.js | ||
| # https://github.com/actions/setup-node/releases/tag/v6.2.0 | ||
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | ||
| with: | ||
| node-version: 18 | ||
|
|
||
| - name: Install dependencies | ||
| run: yarn workspaces focus react-native-contract-test-adapter react-native-contract-test-entity | ||
|
|
||
| - name: Build SDK and dependencies | ||
| run: yarn workspaces foreach -pR --topological-dev --from '@launchdarkly/react-native-client-sdk' run build | ||
|
|
||
| - name: Build contract test adapter | ||
| run: yarn workspace react-native-contract-test-adapter run build | ||
|
|
||
| - name: Enable KVM group perms (for emulator performance) | ||
| run: | | ||
| echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules | ||
| sudo udevadm control --reload-rules | ||
| sudo udevadm trigger --name-match=kvm | ||
|
|
||
| - name: Expo Prebuild | ||
| working-directory: packages/sdk/react-native/contract-tests/entity | ||
| run: npx expo prebuild --platform android | ||
|
|
||
| # Java setup is after expo prebuild so that it can locate the gradle configuration. | ||
| - name: Setup Java | ||
| # https://github.com/actions/setup-java/releases/tag/v5.2.0 | ||
| uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 | ||
| with: | ||
| distribution: temurin | ||
| java-version: 17 | ||
| cache: 'gradle' | ||
|
|
||
| - name: Build APK (x86_64 only for emulator) | ||
| working-directory: packages/sdk/react-native/contract-tests/entity/android | ||
| run: ./gradlew assembleRelease -PreactNativeArchitectures=x86_64 -x lintVitalRelease -x lintVitalAnalyzeRelease -x lintVitalReportRelease | ||
|
|
||
| - name: Make space for the emulator | ||
| # https://github.com/jlumbroso/free-disk-space/releases/tag/main | ||
| uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be | ||
| with: | ||
| android: false | ||
| large-packages: false | ||
|
|
||
| - name: Start adapter in background | ||
| run: | | ||
| yarn workspace react-native-contract-test-adapter run start > /tmp/adapter.log 2>&1 & | ||
| echo $! > /tmp/adapter.pid | ||
|
|
||
| - name: Wait for adapter to be ready | ||
| run: | | ||
| echo "Waiting for adapter on port 8001..." | ||
| for i in $(seq 1 30); do | ||
| if nc -z localhost 8001; then | ||
| echo "Adapter WebSocket ready" | ||
| break | ||
| fi | ||
| if [ "$i" -eq 30 ]; then | ||
| echo "Timeout waiting for adapter" | ||
| cat /tmp/adapter.log | ||
| exit 1 | ||
| fi | ||
| sleep 1 | ||
| done | ||
|
|
||
| - name: Download contract test harness | ||
| run: | | ||
| # https://github.com/launchdarkly/sdk-test-harness/releases/tag/v2.34.0 | ||
| curl -sL -o sdk-test-harness.tar.gz "https://github.com/launchdarkly/sdk-test-harness/releases/download/v2.34.0/sdk-test-harness_Linux_x86_64.tar.gz" | ||
| tar -xzf sdk-test-harness.tar.gz sdk-test-harness | ||
| chmod +x sdk-test-harness | ||
|
|
||
| - name: Run contract tests on Android emulator | ||
| # https://github.com/ReactiveCircus/android-emulator-runner/releases/tag/v2.34.0 | ||
| uses: reactivecircus/android-emulator-runner@f0d1ed2dcad93c7479e8b2f2226c83af54494915 # v2 | ||
| with: | ||
| api-level: 31 | ||
| arch: x86_64 | ||
| avd-name: contract_test_emulator | ||
| script: packages/sdk/react-native/contract-tests/run-ci-contract-tests.sh | ||
|
|
||
| - name: Cleanup | ||
| if: always() | ||
| run: | | ||
| [ -f /tmp/adapter.pid ] && kill $(cat /tmp/adapter.pid) || true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
packages/sdk/react-native/contract-tests/adapter/package.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| { | ||
| "name": "react-native-contract-test-adapter", | ||
| "version": "1.0.0", | ||
| "description": "Adapts REST interface to a websocket for use in React Native.", | ||
| "main": "dist/index.js", | ||
| "scripts": { | ||
| "build": "tsc", | ||
| "start": "yarn build && node dist/index.js" | ||
| }, | ||
| "author": "", | ||
| "license": "UNLICENSED", | ||
| "dependencies": { | ||
| "body-parser": "^1.20.3", | ||
| "cors": "^2.8.5", | ||
| "express": "^4.21.0", | ||
| "ws": "^8.18.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/cors": "^2.8.17", | ||
| "@types/express": "^4.17.21", | ||
| "@types/ws": "^8.5.12", | ||
| "typescript": "^5.6.2" | ||
| } | ||
| } |
112 changes: 112 additions & 0 deletions
112
packages/sdk/react-native/contract-tests/adapter/src/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| /* eslint-disable no-console */ | ||
|
|
||
| /* eslint-disable @typescript-eslint/no-explicit-any */ | ||
| import bodyParser from 'body-parser'; | ||
| import cors from 'cors'; | ||
| import { randomUUID } from 'crypto'; | ||
| import express from 'express'; | ||
| import http from 'node:http'; | ||
| import util from 'node:util'; | ||
| import { WebSocketServer } from 'ws'; | ||
|
|
||
| let server: http.Server | undefined; | ||
|
|
||
| async function main() { | ||
| const wss = new WebSocketServer({ port: 8001 }); | ||
| const waiters: Record<string, (data: unknown) => void> = {}; | ||
|
|
||
| console.log('Running contract test harness adapter.'); | ||
| wss.on('connection', async (ws) => { | ||
| ws.on('error', console.error); | ||
|
|
||
| ws.on('message', (stringData: string) => { | ||
| const data = JSON.parse(stringData); | ||
| if (Object.prototype.hasOwnProperty.call(waiters, data.reqId)) { | ||
| waiters[data.reqId](data); | ||
| delete waiters[data.reqId]; | ||
| } else { | ||
| console.error('Did not find outstanding request', data.reqId); | ||
| } | ||
| }); | ||
|
|
||
| const send = (data: { [key: string]: unknown; reqId: string }): Promise<any> => { | ||
| let resolver: (data: unknown) => void; | ||
| const waiter = new Promise((resolve) => { | ||
| resolver = resolve; | ||
| }); | ||
| // @ts-expect-error The body of the above assignment runs sequentially. | ||
| waiters[data.reqId] = resolver; | ||
| ws.send(JSON.stringify(data)); | ||
| return waiter; | ||
| }; | ||
|
|
||
| if (server) { | ||
| await util.promisify(server.close).call(server); | ||
| server = undefined; | ||
| } | ||
|
|
||
| const app = express(); | ||
|
|
||
| const port = 8000; | ||
|
|
||
| app.use( | ||
| cors({ | ||
| origin: '*', | ||
| allowedHeaders: '*', | ||
| methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], | ||
| }), | ||
| ); | ||
| app.use(bodyParser.json()); | ||
|
|
||
| app.get('/', async (_req, res) => { | ||
| const commandResult = await send({ command: 'getCapabilities', reqId: randomUUID() }); | ||
| res.header('Content-Type', 'application/json'); | ||
| res.json(commandResult); | ||
| }); | ||
|
|
||
| app.delete('/', () => { | ||
| process.exit(); | ||
| }); | ||
|
|
||
| app.post('/', async (req, res) => { | ||
| const commandResult = await send({ | ||
| command: 'createClient', | ||
| body: req.body, | ||
| reqId: randomUUID(), | ||
| }); | ||
| if (commandResult.resourceUrl) { | ||
| res.set('Location', commandResult.resourceUrl); | ||
| } | ||
| if (commandResult.status) { | ||
| res.status(commandResult.status); | ||
| } | ||
| res.send(); | ||
| }); | ||
|
|
||
| app.post('/clients/:id', async (req, res) => { | ||
| const commandResult = await send({ | ||
| command: 'runCommand', | ||
| id: req.params.id, | ||
| body: req.body, | ||
| reqId: randomUUID(), | ||
| }); | ||
| if (commandResult.status) { | ||
| res.status(commandResult.status); | ||
| } | ||
| if (commandResult.body) { | ||
| res.write(JSON.stringify(commandResult.body)); | ||
| } | ||
| res.send(); | ||
| }); | ||
|
|
||
| app.delete('/clients/:id', async (req, res) => { | ||
| await send({ command: 'deleteClient', id: req.params.id, reqId: randomUUID() }); | ||
| res.send(); | ||
| }); | ||
|
|
||
| server = app.listen(port, () => { | ||
| console.log('Listening on port %d', port); | ||
| }); | ||
| }); | ||
| } | ||
| main(); |
15 changes: 15 additions & 0 deletions
15
packages/sdk/react-native/contract-tests/adapter/tsconfig.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "target": "ES6", | ||
| "lib": ["ES6"], | ||
| "module": "commonjs", | ||
| "esModuleInterop": true, | ||
| "forceConsistentCasingInFileNames": true, | ||
| "strict": true, | ||
| "moduleResolution": "node", | ||
| "outDir": "dist", | ||
| "sourceMap": true, | ||
| "skipLibCheck": true | ||
| }, | ||
| "exclude": ["**/*.test.ts", "dist", "node_modules"] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| android/ | ||
| ios/ | ||
| node_modules/ | ||
| .expo/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import React, { useEffect, useState } from 'react'; | ||
| import { StyleSheet, Text, View } from 'react-native'; | ||
|
|
||
| import TestHarnessWebSocket from './src/TestHarnessWebSocket'; | ||
|
|
||
| const styles = StyleSheet.create({ | ||
| container: { | ||
| flex: 1, | ||
| justifyContent: 'center', | ||
| alignItems: 'center', | ||
| backgroundColor: '#fff', | ||
| }, | ||
| text: { | ||
| fontSize: 20, | ||
| fontWeight: 'bold', | ||
| }, | ||
| status: { | ||
| fontSize: 16, | ||
| marginTop: 10, | ||
| }, | ||
| }); | ||
|
|
||
| export default function App() { | ||
| const [connected, setConnected] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| const ws = new TestHarnessWebSocket('ws://localhost:8001', setConnected); | ||
| ws.connect(); | ||
| return () => ws.disconnect(); | ||
| }, []); | ||
|
|
||
| return ( | ||
| <View style={styles.container}> | ||
| <Text style={styles.text}>RN Contract Test Entity</Text> | ||
| <Text style={styles.status}>WebSocket: {connected ? 'Connected' : 'Disconnected'}</Text> | ||
| </View> | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| "expo": { | ||
| "name": "rn-contract-test-entity", | ||
| "slug": "rn-contract-test-entity", | ||
| "version": "1.0.0", | ||
| "orientation": "portrait", | ||
| "android": { | ||
| "package": "com.launchdarkly.rncontracttestentity" | ||
| }, | ||
| "plugins": [ | ||
| "./plugins/withCleartextTraffic" | ||
| ] | ||
| } | ||
| } |
6 changes: 6 additions & 0 deletions
6
packages/sdk/react-native/contract-tests/entity/babel.config.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| module.exports = function (api) { | ||
| api.cache(true); | ||
| return { | ||
| presets: ['babel-preset-expo'], | ||
| }; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| // We have to use a custom entrypoint for monorepo workspaces to work. | ||
| // https://docs.expo.dev/guides/monorepos/#change-default-entrypoint | ||
| import { registerRootComponent } from 'expo'; | ||
|
|
||
| import App from './App'; | ||
|
|
||
| // registerRootComponent calls AppRegistry.registerComponent('main', () => App); | ||
| // It also ensures that whether you load the app in Expo Go or in a native build, | ||
| // the environment is set up appropriately | ||
| registerRootComponent(App); |
26 changes: 26 additions & 0 deletions
26
packages/sdk/react-native/contract-tests/entity/metro.config.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // We need to use a custom metro config for monorepo workspaces to work. | ||
| // https://docs.expo.dev/guides/monorepos/#modify-the-metro-config | ||
| /** | ||
| * @type {import('expo/metro-config')} | ||
| */ | ||
| const { getDefaultConfig } = require('expo/metro-config'); | ||
| const path = require('path'); | ||
|
|
||
| // Find the project and workspace directories | ||
| const projectRoot = __dirname; | ||
| // This can be replaced with `find-yarn-workspace-root` | ||
| const workspaceRoot = path.resolve(projectRoot, '../../../../..'); | ||
|
|
||
| const config = getDefaultConfig(projectRoot); | ||
|
|
||
| // 1. Watch all files within the monorepo | ||
| config.watchFolders = [workspaceRoot]; | ||
| // 2. Let Metro know where to resolve packages and in what order | ||
| config.resolver.nodeModulesPaths = [ | ||
| path.resolve(projectRoot, 'node_modules'), | ||
| path.resolve(workspaceRoot, 'node_modules'), | ||
| ]; | ||
| // 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths` | ||
| config.resolver.disableHierarchicalLookup = true; | ||
|
|
||
| module.exports = config; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is slightly amusing.