From 2e75766ce3eee44cc8b10ce1b38c55a38e95459a Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Wed, 22 Jan 2025 11:55:42 -0700 Subject: [PATCH 01/11] npm install with exponential backup retries --- .github/actions/npm_install/action.yml | 47 ++++++++++++++++++++++++ .github/workflows/benchmark.yml | 3 +- .github/workflows/benchmark_parallel.yml | 8 ++-- .github/workflows/build_templates.yml | 4 +- .github/workflows/maintenance.yml | 9 ++--- .github/workflows/release.yml | 14 ++++--- .github/workflows/run_test.yml | 7 ++-- 7 files changed, 70 insertions(+), 22 deletions(-) create mode 100644 .github/actions/npm_install/action.yml diff --git a/.github/actions/npm_install/action.yml b/.github/actions/npm_install/action.yml new file mode 100644 index 0000000000..411a3aa003 --- /dev/null +++ b/.github/actions/npm_install/action.yml @@ -0,0 +1,47 @@ +name: 'NPM Install with Retries' +description: 'Run npm install with exponential backoff retries' + +inputs: + working-directory: + description: 'Directory where to run npm install' + required: false + default: '.' + max-attempts: + description: 'Maximum number of retry attempts' + required: false + default: '5' + initial-delay: + description: 'Initial delay in seconds before first retry' + required: false + default: '1' + max-delay: + description: 'Maximum delay in seconds between retries' + required: false + default: '60' + +runs: + using: 'composite' + steps: + - shell: bash + run: | + attempt=1 + max_attempts=${{ inputs.max-attempts }} + delay=${{ inputs.initial-delay }} + max_delay=${{ inputs.max-delay }} + + until cd ${{ inputs.working-directory }} && npm install; do + if [ $attempt -ge $max_attempts ]; then + echo "npm install failed after $attempt attempts" + exit 1 + fi + + attempt=$(($attempt + 1)) + echo "npm install failed, retrying in $delay seconds... (attempt $attempt/$max_attempts)" + sleep $delay + + # Exponential backoff with a configurable maximum + delay=$(( delay * 2 )) + if [ $delay -gt $max_delay ]; then + delay=$max_delay + fi + done diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index d4fc1b1508..ca6e66bea8 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -129,8 +129,7 @@ jobs: - uses: ./.github/actions/setup_dfx - - name: Install dependencies - run: npm install + - uses: ./.github/actions/npm_install - name: Analyze benchmarks run: npx tsx scripts/analyze_benchmarks/index.ts diff --git a/.github/workflows/benchmark_parallel.yml b/.github/workflows/benchmark_parallel.yml index 1c609e6415..70e0b580fe 100644 --- a/.github/workflows/benchmark_parallel.yml +++ b/.github/workflows/benchmark_parallel.yml @@ -76,11 +76,13 @@ jobs: - uses: ./.github/actions/setup_dfx - - run: npm install + - uses: ./.github/actions/npm_install + - run: npm link - - run: npm install - working-directory: ${{ matrix.test.path }} + - uses: ./.github/actions/npm_install + with: + working-directory: ${{ matrix.test.path }} - run: npm link azle working-directory: ${{ matrix.test.path }} diff --git a/.github/workflows/build_templates.yml b/.github/workflows/build_templates.yml index aaa5c6ec9d..035a86b779 100644 --- a/.github/workflows/build_templates.yml +++ b/.github/workflows/build_templates.yml @@ -39,9 +39,7 @@ jobs: - uses: ./.github/actions/setup_node - - uses: ./.github/actions/setup_dfx - - - run: npm install + - uses: ./.github/actions/npm_install - name: Install global dependencies run: | diff --git a/.github/workflows/maintenance.yml b/.github/workflows/maintenance.yml index 83aa50468e..741b6b4e5c 100644 --- a/.github/workflows/maintenance.yml +++ b/.github/workflows/maintenance.yml @@ -52,7 +52,7 @@ jobs: - uses: ./.github/actions/setup_dfx - - run: npm install + - uses: ./.github/actions/npm_install # Run global prettier/lint fixes - name: Run Prettier @@ -99,10 +99,9 @@ jobs: # Update package-lock.json in test directory - name: Update package-lock.json - working-directory: ${{ matrix.test.path }} - run: | - rm -f package-lock.json - npm install + uses: ./.github/actions/npm_install + with: + working-directory: ${{ matrix.test.path }} - name: Create branch name id: create-branch-name diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 755ff3c10a..0cc5ea185c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,18 +43,19 @@ jobs: - uses: ./.github/actions/setup_dfx - - run: npm install + - uses: ./.github/actions/npm_install - name: Install global dependencies run: | AZLE_VERBOSE=true npx azle install-global-dependencies --rust --wasi2ic - - name: Update version and build templates + - name: Update version run: | VERSION=${{ inputs.release-version }} sed -E -i "s/(\"version\": \")(.*)(\")/\1$VERSION\3/" package.json sed -E -i "s/(\"version\": \")(.*)(\")/\1$VERSION\3/" dfx_extension/extension.json - npm install + + - uses: ./.github/actions/npm_install - name: Build stable template run: npx azle template @@ -105,15 +106,16 @@ jobs: - uses: ./.github/actions/setup_dfx - - run: npm install + - uses: ./.github/actions/npm_install - name: Update azle version working-directory: ${{ matrix.test.path }} run: | sed -E -i "s/(\"azle\": \")(.*)(\")/\1${{ inputs.release-version }}\3/" package.json - - run: npm install - working-directory: ${{ matrix.test.path }} + - uses: ./.github/actions/npm_install + with: + working-directory: ${{ matrix.test.path }} - name: Start dfx with artificial delay 0 working-directory: ${{ matrix.test.path }} diff --git a/.github/workflows/run_test.yml b/.github/workflows/run_test.yml index 1ac49524da..126a461179 100644 --- a/.github/workflows/run_test.yml +++ b/.github/workflows/run_test.yml @@ -101,15 +101,16 @@ jobs: if: matrix.os == 'macos-latest' run: sudo networksetup -setdnsservers Ethernet 9.9.9.9 - - run: npm install + - uses: ./.github/actions/npm_install - run: npm link if: matrix.azle_source == 'repo' - run: npm run lint - - run: npm install - working-directory: ${{ matrix.test.path }} + - uses: ./.github/actions/npm_install + with: + working-directory: ${{ matrix.test.path }} - run: npm link azle if: matrix.azle_source == 'repo' From e4998dc829920f5d1707973c76839cd7b102416d Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Wed, 22 Jan 2025 12:46:30 -0700 Subject: [PATCH 02/11] add unstable examples --- .github/actions/get_exclude_dirs/action.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/actions/get_exclude_dirs/action.yml b/.github/actions/get_exclude_dirs/action.yml index 52cfc27fc2..70e3dde643 100644 --- a/.github/actions/get_exclude_dirs/action.yml +++ b/.github/actions/get_exclude_dirs/action.yml @@ -32,6 +32,8 @@ runs: examples/experimental/test/end_to_end/http_server/http_outcall_fetch examples/experimental/test/end_to_end/http_server/ic_evm_rpc examples/experimental/test/end_to_end/http_server/multi_deploy + examples/experimental/test/property/candid_rpc/opt + examples/experimental/test/property/candid_rpc/stable_b_tree_map ') }}" SLOW_EXCLUSIONS="${{ format(' @@ -49,7 +51,6 @@ runs: examples/experimental/test/end_to_end/http_server/autoreload examples/experimental/test/end_to_end/http_server/large_files examples/experimental/test/end_to_end/http_server/open_value_sharing - examples/experimental/test/property/candid_rpc/stable_b_tree_map ') }}" EXCLUDE_DIRS="" From 454ec04237b86f39bab188d9132206af14fb12b1 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Wed, 22 Jan 2025 12:47:31 -0700 Subject: [PATCH 03/11] remove unnecessary function --- test/property/index.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/test/property/index.ts b/test/property/index.ts index ce578a75b0..b51e5f2b71 100644 --- a/test/property/index.ts +++ b/test/property/index.ts @@ -1,8 +1,6 @@ import './set_experimental'; import { execSync } from 'child_process'; -// @ts-ignore -import libraryDeepEqual from 'deep-is'; import fc from 'fast-check'; import { existsSync, mkdirSync, writeFileSync } from 'fs'; @@ -132,15 +130,3 @@ export const shortArrayConstraints = { minLength: 5, maxLength: 20 }; - -export function deepEqual(a: any, b: any): boolean { - const result = libraryDeepEqual(a, b); - - if (result === false) { - console.info('deepEqual returned false'); - console.info('deepEqual value a', a); - console.info('deepEqual value b', b); - } - - return result; -} From 04bbf96a9f52c2b84d5a54d28ef7b69d2974fff0 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Wed, 22 Jan 2025 14:21:10 -0700 Subject: [PATCH 04/11] add backup equality check --- test/property/test/index.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/test/property/test/index.ts b/test/property/test/index.ts index 1b25163e34..6342dc8c5d 100644 --- a/test/property/test/index.ts +++ b/test/property/test/index.ts @@ -112,10 +112,18 @@ export function testEquality(actual: T, expected: T): AzleResult { if (deepEqual(actual, expected)) { return { Ok: { isSuccessful: true } }; } else { - const message = `Expected: ${jsonStringify( - expected - )}, Received: ${jsonStringify(actual)}`; - return { Ok: { isSuccessful: false, message } }; + const actualJson = jsonStringify(actual); + const expectedJson = jsonStringify(expected); + if (actualJson !== expectedJson) { + const message = `Expected: ${expectedJson}, Received: ${actualJson}`; + return { Ok: { isSuccessful: false, message } }; + } else { + console.log( + `Expected: ${expectedJson}, Received: ${actualJson}`, + 'deepEqual failed' + ); + return { Ok: { isSuccessful: true } }; + } } } From 61aaa3b1049214cefc892f3934c968eb030d1dbe Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Thu, 23 Jan 2025 09:41:31 -0700 Subject: [PATCH 05/11] pr fixes --- .github/workflows/build_templates.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build_templates.yml b/.github/workflows/build_templates.yml index 035a86b779..94e87978e2 100644 --- a/.github/workflows/build_templates.yml +++ b/.github/workflows/build_templates.yml @@ -39,6 +39,8 @@ jobs: - uses: ./.github/actions/setup_node + - uses: ./.github/actions/setup_dfx + - uses: ./.github/actions/npm_install - name: Install global dependencies From cf94f555bf6a16c4923cca90970b5ff293f0131d Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Thu, 23 Jan 2025 09:42:33 -0700 Subject: [PATCH 06/11] make retry command more generic --- .../{npm_install => retry-command}/action.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) rename .github/actions/{npm_install => retry-command}/action.yml (69%) diff --git a/.github/actions/npm_install/action.yml b/.github/actions/retry-command/action.yml similarity index 69% rename from .github/actions/npm_install/action.yml rename to .github/actions/retry-command/action.yml index 411a3aa003..ac1e4a5188 100644 --- a/.github/actions/npm_install/action.yml +++ b/.github/actions/retry-command/action.yml @@ -1,9 +1,12 @@ -name: 'NPM Install with Retries' -description: 'Run npm install with exponential backoff retries' +name: 'Command with retries' +description: 'Run any command with exponential backoff retries' inputs: + command: + description: 'Command to execute' + required: true working-directory: - description: 'Directory where to run npm install' + description: 'Directory where to run the command' required: false default: '.' max-attempts: @@ -29,14 +32,14 @@ runs: delay=${{ inputs.initial-delay }} max_delay=${{ inputs.max-delay }} - until cd ${{ inputs.working-directory }} && npm install; do + until cd ${{ inputs.working-directory }} && ${{ inputs.command }}; do if [ $attempt -ge $max_attempts ]; then - echo "npm install failed after $attempt attempts" + echo "Command failed after $attempt attempts" exit 1 fi attempt=$(($attempt + 1)) - echo "npm install failed, retrying in $delay seconds... (attempt $attempt/$max_attempts)" + echo "Command failed, retrying in $delay seconds... (attempt $attempt/$max_attempts)" sleep $delay # Exponential backoff with a configurable maximum From 9580bd8f858b6333fc5c7be2ac6343cd64bff45e Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Thu, 23 Jan 2025 10:16:38 -0700 Subject: [PATCH 07/11] fixup --- .github/actions/{retry-command => retry_command}/action.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/actions/{retry-command => retry_command}/action.yml (100%) diff --git a/.github/actions/retry-command/action.yml b/.github/actions/retry_command/action.yml similarity index 100% rename from .github/actions/retry-command/action.yml rename to .github/actions/retry_command/action.yml From 9a8c319f3e155e9aacd242a62ffaf9dd36abe096 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Thu, 23 Jan 2025 10:18:24 -0700 Subject: [PATCH 08/11] update uses of retry command --- .github/workflows/benchmark.yml | 4 +++- .github/workflows/benchmark_parallel.yml | 7 +++++-- .github/workflows/build_templates.yml | 4 +++- .github/workflows/maintenance.yml | 6 ++++-- .github/workflows/release.yml | 15 +++++++++++---- .github/workflows/run_test.yml | 7 +++++-- 6 files changed, 31 insertions(+), 12 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index ca6e66bea8..bb19583836 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -129,7 +129,9 @@ jobs: - uses: ./.github/actions/setup_dfx - - uses: ./.github/actions/npm_install + - uses: ./.github/actions/retry_command + with: + command: 'npm install' - name: Analyze benchmarks run: npx tsx scripts/analyze_benchmarks/index.ts diff --git a/.github/workflows/benchmark_parallel.yml b/.github/workflows/benchmark_parallel.yml index 70e0b580fe..5d337377b7 100644 --- a/.github/workflows/benchmark_parallel.yml +++ b/.github/workflows/benchmark_parallel.yml @@ -76,12 +76,15 @@ jobs: - uses: ./.github/actions/setup_dfx - - uses: ./.github/actions/npm_install + - uses: ./.github/actions/retry_command + with: + command: 'npm install' - run: npm link - - uses: ./.github/actions/npm_install + - uses: ./.github/actions/retry_command with: + command: 'npm install' working-directory: ${{ matrix.test.path }} - run: npm link azle diff --git a/.github/workflows/build_templates.yml b/.github/workflows/build_templates.yml index 94e87978e2..6b7c9d5173 100644 --- a/.github/workflows/build_templates.yml +++ b/.github/workflows/build_templates.yml @@ -41,7 +41,9 @@ jobs: - uses: ./.github/actions/setup_dfx - - uses: ./.github/actions/npm_install + - uses: ./.github/actions/retry_command + with: + command: 'npm install' - name: Install global dependencies run: | diff --git a/.github/workflows/maintenance.yml b/.github/workflows/maintenance.yml index 741b6b4e5c..cf60767b5f 100644 --- a/.github/workflows/maintenance.yml +++ b/.github/workflows/maintenance.yml @@ -52,7 +52,9 @@ jobs: - uses: ./.github/actions/setup_dfx - - uses: ./.github/actions/npm_install + - uses: ./.github/actions/retry_command + with: + command: 'npm install' # Run global prettier/lint fixes - name: Run Prettier @@ -99,7 +101,7 @@ jobs: # Update package-lock.json in test directory - name: Update package-lock.json - uses: ./.github/actions/npm_install + uses: ./.github/actions/retry_command with: working-directory: ${{ matrix.test.path }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0cc5ea185c..7630fab087 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,7 +43,9 @@ jobs: - uses: ./.github/actions/setup_dfx - - uses: ./.github/actions/npm_install + - uses: ./.github/actions/retry_command + with: + command: 'npm install' - name: Install global dependencies run: | @@ -55,7 +57,9 @@ jobs: sed -E -i "s/(\"version\": \")(.*)(\")/\1$VERSION\3/" package.json sed -E -i "s/(\"version\": \")(.*)(\")/\1$VERSION\3/" dfx_extension/extension.json - - uses: ./.github/actions/npm_install + - uses: ./.github/actions/retry_command + with: + command: 'npm install' - name: Build stable template run: npx azle template @@ -106,15 +110,18 @@ jobs: - uses: ./.github/actions/setup_dfx - - uses: ./.github/actions/npm_install + - uses: ./.github/actions/retry_command + with: + command: 'npm install' - name: Update azle version working-directory: ${{ matrix.test.path }} run: | sed -E -i "s/(\"azle\": \")(.*)(\")/\1${{ inputs.release-version }}\3/" package.json - - uses: ./.github/actions/npm_install + - uses: ./.github/actions/retry_command with: + command: 'npm install' working-directory: ${{ matrix.test.path }} - name: Start dfx with artificial delay 0 diff --git a/.github/workflows/run_test.yml b/.github/workflows/run_test.yml index 126a461179..0108a98c53 100644 --- a/.github/workflows/run_test.yml +++ b/.github/workflows/run_test.yml @@ -101,15 +101,18 @@ jobs: if: matrix.os == 'macos-latest' run: sudo networksetup -setdnsservers Ethernet 9.9.9.9 - - uses: ./.github/actions/npm_install + - uses: ./.github/actions/retry_command + with: + command: 'npm install' - run: npm link if: matrix.azle_source == 'repo' - run: npm run lint - - uses: ./.github/actions/npm_install + - uses: ./.github/actions/retry_command with: + command: 'npm run lint' working-directory: ${{ matrix.test.path }} - run: npm link azle From 804d32f22392794c347f2a9d3d4ecd46bac82ada Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Thu, 23 Jan 2025 10:18:24 -0700 Subject: [PATCH 09/11] fixup --- .github/workflows/maintenance.yml | 1 + .github/workflows/run_test.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/maintenance.yml b/.github/workflows/maintenance.yml index cf60767b5f..0dad4ffc1a 100644 --- a/.github/workflows/maintenance.yml +++ b/.github/workflows/maintenance.yml @@ -103,6 +103,7 @@ jobs: - name: Update package-lock.json uses: ./.github/actions/retry_command with: + command: 'npm install' working-directory: ${{ matrix.test.path }} - name: Create branch name diff --git a/.github/workflows/run_test.yml b/.github/workflows/run_test.yml index 0108a98c53..c6464df84c 100644 --- a/.github/workflows/run_test.yml +++ b/.github/workflows/run_test.yml @@ -112,7 +112,7 @@ jobs: - uses: ./.github/actions/retry_command with: - command: 'npm run lint' + command: 'npm install' working-directory: ${{ matrix.test.path }} - run: npm link azle From 46dede6deeeae14f4f9150b37751018f792a3c20 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Fri, 24 Jan 2025 10:56:42 -0700 Subject: [PATCH 10/11] update equality --- test/property/test/index.ts | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/test/property/test/index.ts b/test/property/test/index.ts index 6342dc8c5d..2324e69393 100644 --- a/test/property/test/index.ts +++ b/test/property/test/index.ts @@ -109,21 +109,18 @@ export async function runTests( // TODO is is better test framework conformity to call this assertEqual? I'll hold off for now, it should be easy to search for all testEquality and change it, easier than assertEqual I think // TODO so based on this I think I've actually seen this in other testing frameworks, assertEquals will take two and make sure they are equals, and assert will take one boolean. Right now we have test instead of assert but it would be easy to change export function testEquality(actual: T, expected: T): AzleResult { - if (deepEqual(actual, expected)) { + // We have historically had issues with various deepEqual implementations most tracing back to subtle differences in versions etc that have caused false negatives. + // Our jsonStringify takes out a lot of the possible oddities by serializing them into a more standard format. + // So until we convert this to jest and use it's various equality functions, this should be good enough. + const actualJsonString = jsonStringify(actual); + const expectedJsonString = jsonStringify(expected); + const actualJson = JSON.parse(actualJsonString); + const expectedJson = JSON.parse(expectedJsonString); + if (deepEqual(actualJson, expectedJson)) { return { Ok: { isSuccessful: true } }; } else { - const actualJson = jsonStringify(actual); - const expectedJson = jsonStringify(expected); - if (actualJson !== expectedJson) { - const message = `Expected: ${expectedJson}, Received: ${actualJson}`; - return { Ok: { isSuccessful: false, message } }; - } else { - console.log( - `Expected: ${expectedJson}, Received: ${actualJson}`, - 'deepEqual failed' - ); - return { Ok: { isSuccessful: true } }; - } + const message = `Expected: ${expectedJson}, Received: ${actualJson}`; + return { Ok: { isSuccessful: false, message } }; } } From af7f6c41d66a4a30f5faf7a6908fff4924d9c2fe Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Fri, 24 Jan 2025 13:16:51 -0700 Subject: [PATCH 11/11] handle undefined values --- src/lib/stable/stable_structures/stable_json.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lib/stable/stable_structures/stable_json.ts b/src/lib/stable/stable_structures/stable_json.ts index 54ee48a342..2802e213b9 100644 --- a/src/lib/stable/stable_structures/stable_json.ts +++ b/src/lib/stable/stable_structures/stable_json.ts @@ -54,6 +54,12 @@ export const stableJson = StableJson(); * @returns The converted value safe for JSON string serialization */ export function jsonReplacer(_key: string, value: any): any { + if (typeof value === 'undefined') { + return { + __undefined__: '__undefined__' + }; + } + if (typeof value === 'bigint') { return { __bigint__: value.toString() @@ -153,6 +159,10 @@ export function jsonReplacer(_key: string, value: any): any { */ export function jsonReviver(_key: string, value: any): any { if (typeof value === 'object' && value !== null) { + if (typeof value.__undefined__ === 'string') { + return undefined; + } + if (typeof value.__bigint__ === 'string') { return BigInt(value.__bigint__); }