-
Notifications
You must be signed in to change notification settings - Fork 10
ci: add integration tests for write-program-buffer
#24
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
Open
sparten11740
wants to merge
4
commits into
solana-foundation:main
Choose a base branch
from
ExodusForks:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
ef867ff
ci: add integration tests for `write-program-buffer` (#3)
sparten11740 e3c416d
fix: wait for validator to advance past deploy slot (#4)
sparten11740 fcc5ea2
fix: review findings (#5)
sparten11740 cb108dd
fix: address 2nd round of reviews (#6)
sparten11740 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,73 @@ | ||
| name: Test write-program-buffer | ||
|
|
||
| on: | ||
| pull_request: | ||
| paths: | ||
| - "write-program-buffer/**" | ||
| - "start-test-validator/**" | ||
| - "setup-all/**" | ||
| - ".github/workflows/test-write-program-buffer.yaml" | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: test-write-program-buffer-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| integration: | ||
| name: ${{ matrix.scenario }} (solana ${{ matrix.solana-version }}) | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 10 | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| solana-version: ["4.1.2"] | ||
| scenario: [fresh, delta, clamp, authority, nearmax] | ||
| steps: | ||
| - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||
|
|
||
| - uses: ./setup-all | ||
| with: | ||
| solana_version: ${{ matrix.solana-version }} | ||
|
|
||
| - name: Start local validator | ||
| id: validator | ||
| uses: ./start-test-validator | ||
|
|
||
| - name: Prepare scenario | ||
| id: prepare | ||
| shell: bash | ||
| env: | ||
| RPC_URL: ${{ steps.validator.outputs.rpc-url }} | ||
| run: ./write-program-buffer/tests/integration/prepare-${{ matrix.scenario }}.sh | ||
|
|
||
| - name: Run write-program-buffer action | ||
| id: run | ||
| uses: ./write-program-buffer | ||
| with: | ||
| program-id: ${{ steps.prepare.outputs.program-id }} | ||
| program: fixture-${{ matrix.scenario }} | ||
| rpc-url: ${{ steps.validator.outputs.rpc-url }} | ||
| keypair: ${{ steps.prepare.outputs.keypair }} | ||
| buffer-authority-address: ${{ steps.prepare.outputs.buffer-authority }} | ||
|
|
||
| - name: Assert scenario | ||
| shell: bash | ||
| env: | ||
| RPC_URL: ${{ steps.validator.outputs.rpc-url }} | ||
| BUFFER: ${{ steps.run.outputs.buffer }} | ||
| PROGRAM_ID: ${{ steps.prepare.outputs.program-id }} | ||
| DEPLOYER: ${{ steps.prepare.outputs.deployer }} | ||
| BUFFER_AUTHORITY: ${{ steps.prepare.outputs.buffer-authority }} | ||
| PRE_LEN: ${{ steps.prepare.outputs.pre-len }} | ||
| run: ./write-program-buffer/tests/integration/assert-${{ matrix.scenario }}.sh | ||
|
|
||
| - name: Dump validator logs on failure | ||
| if: failure() | ||
| shell: bash | ||
| run: | | ||
| tail -100 "$RUNNER_TEMP/validator-stdout.log" || true | ||
| tail -100 "${{ steps.validator.outputs.ledger-dir }}/validator.log" || 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| name: "Start Test Validator" | ||
| description: "Starts a local solana-test-validator in the background and waits until it is healthy" | ||
|
|
||
| inputs: | ||
| ledger-dir: | ||
| description: "Ledger directory (defaults to <runner temp>/test-ledger)" | ||
| required: false | ||
| default: "" | ||
| max-attempts: | ||
| description: "Maximum health check attempts, roughly one per second" | ||
| required: false | ||
| default: "60" | ||
|
|
||
| outputs: | ||
| rpc-url: | ||
| description: "RPC URL of the started validator" | ||
| value: ${{ steps.start.outputs.rpc-url }} | ||
| ledger-dir: | ||
| description: "Ledger directory of the started validator" | ||
| value: ${{ steps.start.outputs.ledger-dir }} | ||
|
|
||
| runs: | ||
| using: "composite" | ||
| steps: | ||
| - name: Start validator | ||
| id: start | ||
| shell: bash -euo pipefail {0} | ||
| env: | ||
| LEDGER_DIR_INPUT: ${{ inputs.ledger-dir }} | ||
| run: | | ||
| RPC_URL="http://127.0.0.1:8899" | ||
| LEDGER_DIR="$LEDGER_DIR_INPUT" | ||
| if [ -z "$LEDGER_DIR" ]; then | ||
| LEDGER_DIR="${RUNNER_TEMP:-/tmp}/test-ledger" | ||
| fi | ||
|
|
||
| solana-test-validator --reset --quiet --ledger "$LEDGER_DIR" > "${RUNNER_TEMP:-/tmp}/validator-stdout.log" 2>&1 & | ||
| echo "Validator started with PID $!" | ||
|
|
||
| echo "rpc-url=$RPC_URL" >> "$GITHUB_OUTPUT" | ||
| echo "ledger-dir=$LEDGER_DIR" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Wait for validator health | ||
| uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0 | ||
| with: | ||
| timeout_seconds: 5 | ||
| max_attempts: ${{ inputs.max-attempts }} | ||
| retry_wait_seconds: 1 | ||
| command: solana cluster-version -u ${{ steps.start.outputs.rpc-url }} | ||
|
|
||
| - name: Dump validator logs if unhealthy | ||
| if: failure() | ||
| shell: bash | ||
| run: | | ||
| tail -50 "${RUNNER_TEMP:-/tmp}/validator-stdout.log" 2>/dev/null || true | ||
| tail -50 "${{ steps.start.outputs.ledger-dir }}/validator.log" 2>/dev/null || true |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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 @@ | ||
| [package] | ||
| name = "fixture-program" | ||
| version = "0.1.0" | ||
| edition = "2021" | ||
|
|
||
| [lib] | ||
| crate-type = ["cdylib"] | ||
|
|
||
| [features] | ||
| medium = [] | ||
| big = [] | ||
| huge = [] | ||
|
|
||
| [workspace] |
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,25 @@ | ||
| #![no_std] | ||
|
|
||
| #[cfg(feature = "medium")] | ||
| #[no_mangle] | ||
| pub static PAD: [u8; 20_000] = [1; 20_000]; | ||
|
|
||
| #[cfg(feature = "big")] | ||
| #[no_mangle] | ||
| pub static PAD: [u8; 4096] = [1; 4096]; | ||
|
|
||
| #[cfg(feature = "huge")] | ||
| #[no_mangle] | ||
| pub static PAD: [u8; 10_481_816] = [1; 10_481_816]; | ||
|
|
||
| #[no_mangle] | ||
| pub extern "C" fn entrypoint(_input: *mut u8) -> u64 { | ||
| #[cfg(any(feature = "medium", feature = "big", feature = "huge"))] | ||
| core::hint::black_box(&PAD); | ||
| 0 | ||
| } | ||
|
|
||
| #[panic_handler] | ||
| fn panic(_info: &core::panic::PanicInfo) -> ! { | ||
| loop {} | ||
| } |
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,47 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
| cd "$(dirname "${BASH_SOURCE[0]}")" | ||
|
|
||
| command -v cargo-build-sbf >/dev/null || { | ||
| echo "cargo-build-sbf is required, install the agave tool suite first" >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| build() { | ||
| local variant="$1" feature="$2" | ||
| local args=(--arch v3 --manifest-path program/Cargo.toml --sbf-out-dir program/out) | ||
| if [ -n "$feature" ]; then | ||
| args+=(--features "$feature") | ||
| fi | ||
| cargo-build-sbf "${args[@]}" | ||
| cp program/out/fixture_program.so "program-$variant.so" | ||
| rm -rf program/out program/target | ||
| } | ||
|
|
||
| build small "" | ||
| build medium medium | ||
| build big big | ||
| build huge huge | ||
|
|
||
| SMALL=$(wc -c < program-small.so | tr -d ' ') | ||
| MEDIUM=$(wc -c < program-medium.so | tr -d ' ') | ||
| BIG=$(wc -c < program-big.so | tr -d ' ') | ||
| HUGE=$(wc -c < program-huge.so | tr -d ' ') | ||
| echo "small=$SMALL medium=$MEDIUM big=$BIG huge=$HUGE" | ||
|
|
||
| if [ "$BIG" -le "$SMALL" ] || [ $((BIG - SMALL)) -ge 10240 ]; then | ||
| echo "Size band violated: big-small delta must be in (0, 10240)" >&2 | ||
| exit 1 | ||
| fi | ||
| if [ $((MEDIUM - SMALL)) -le 10240 ]; then | ||
| echo "Size band violated: medium-small delta must exceed 10240" >&2 | ||
| exit 1 | ||
| fi | ||
| if [ "$HUGE" -le 10477475 ] || [ "$HUGE" -gt 10485715 ]; then | ||
| echo "Size band violated: huge must be in (10477475, 10485715]" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| gzip -9 -n -c program-huge.so > program-huge.so.gz | ||
| rm program-huge.so | ||
| echo "Fixtures rebuilt" |
34 changes: 34 additions & 0 deletions
34
write-program-buffer/tests/integration/assert-authority.sh
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,34 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| RPC_URL="${RPC_URL:-http://127.0.0.1:8899}" | ||
|
sparten11740 marked this conversation as resolved.
|
||
| ARTIFACT="target/deploy/fixture-authority.so" | ||
|
|
||
| fail() { | ||
| echo "ASSERT FAIL: $1" >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| [ -n "${BUFFER:-}" ] || fail "action did not output a buffer address" | ||
| [ -n "${BUFFER_AUTHORITY:-}" ] || fail "BUFFER_AUTHORITY env not set" | ||
| [ -n "${DEPLOYER:-}" ] || fail "DEPLOYER env not set" | ||
| [ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set" | ||
| [ -n "${PRE_LEN:-}" ] || fail "PRE_LEN env not set" | ||
|
|
||
| BUFFER_INFO=$(solana program show "$BUFFER" -u "$RPC_URL") | ||
| echo "$BUFFER_INFO" | ||
| AUTHORITY=$(echo "$BUFFER_INFO" | grep "Authority:" | awk '{print $2}' || true) | ||
| [ -n "$AUTHORITY" ] || fail "could not read authority of buffer $BUFFER" | ||
| [ "$AUTHORITY" = "$BUFFER_AUTHORITY" ] || fail "buffer authority is $AUTHORITY, expected $BUFFER_AUTHORITY" | ||
| [ "$AUTHORITY" != "$DEPLOYER" ] || fail "buffer authority still equals the deployer" | ||
|
|
||
| POST_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | sed -E 's/.*Data Length: ([0-9]+).*/\1/' | cut -d ' ' -f1 || true) | ||
| [ -n "$POST_LEN" ] || fail "could not read data length of program $PROGRAM_ID" | ||
| [ "$POST_LEN" -eq "$PRE_LEN" ] || fail "program was resized from $PRE_LEN to $POST_LEN bytes, expected no resize" | ||
|
|
||
| DUMP="$(mktemp)" | ||
| solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER" | ||
| cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact" | ||
| rm -f "$DUMP" | ||
|
|
||
| echo "Authority transfer assertions passed: program stayed at $PRE_LEN bytes" | ||
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,27 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| RPC_URL="${RPC_URL:-http://127.0.0.1:8899}" | ||
| ARTIFACT="target/deploy/fixture-clamp.so" | ||
| MIN_EXTEND_SIZE=10240 | ||
|
|
||
| fail() { | ||
| echo "ASSERT FAIL: $1" >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| [ -n "${BUFFER:-}" ] || fail "action did not output a buffer address" | ||
| [ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set" | ||
| [ -n "${PRE_LEN:-}" ] || fail "PRE_LEN env not set" | ||
|
|
||
| POST_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | sed -E 's/.*Data Length: ([0-9]+).*/\1/' | cut -d ' ' -f1 || true) | ||
| [ -n "$POST_LEN" ] || fail "could not read data length of program $PROGRAM_ID" | ||
| GROWTH=$((POST_LEN - PRE_LEN)) | ||
| [ "$GROWTH" -eq "$MIN_EXTEND_SIZE" ] || fail "program grew by $GROWTH bytes, expected exactly $MIN_EXTEND_SIZE" | ||
|
|
||
| DUMP="$(mktemp)" | ||
| solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER" | ||
| cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact" | ||
| rm -f "$DUMP" | ||
|
|
||
| echo "Clamp regression assertions passed: program grew by exactly $MIN_EXTEND_SIZE bytes" |
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,31 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| RPC_URL="${RPC_URL:-http://127.0.0.1:8899}" | ||
| ARTIFACT="target/deploy/fixture-delta.so" | ||
| MIN_EXTEND_SIZE=10240 | ||
|
|
||
| fail() { | ||
| echo "ASSERT FAIL: $1" >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| [ -n "${BUFFER:-}" ] || fail "action did not output a buffer address" | ||
| [ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set" | ||
| [ -n "${PRE_LEN:-}" ] || fail "PRE_LEN env not set" | ||
|
|
||
| REQUIRED_SIZE=$(wc -c < "$ARTIFACT" | tr -d ' ') | ||
| EXPECTED_GROWTH=$((REQUIRED_SIZE - PRE_LEN)) | ||
| [ "$EXPECTED_GROWTH" -gt "$MIN_EXTEND_SIZE" ] || fail "scenario setup invalid: expected growth $EXPECTED_GROWTH must exceed $MIN_EXTEND_SIZE" | ||
|
|
||
| POST_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | sed -E 's/.*Data Length: ([0-9]+).*/\1/' | cut -d ' ' -f1 || true) | ||
| [ -n "$POST_LEN" ] || fail "could not read data length of program $PROGRAM_ID" | ||
| GROWTH=$((POST_LEN - PRE_LEN)) | ||
| [ "$GROWTH" -eq "$EXPECTED_GROWTH" ] || fail "program grew by $GROWTH bytes, expected the exact delta $EXPECTED_GROWTH" | ||
|
|
||
| DUMP="$(mktemp)" | ||
| solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER" | ||
| cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact" | ||
| rm -f "$DUMP" | ||
|
|
||
| echo "Plain-delta extend assertions passed: program grew by exactly $EXPECTED_GROWTH bytes" |
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,32 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| RPC_URL="${RPC_URL:-http://127.0.0.1:8899}" | ||
| ARTIFACT="target/deploy/fixture-fresh.so" | ||
|
|
||
| fail() { | ||
| echo "ASSERT FAIL: $1" >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| [ -n "${BUFFER:-}" ] || fail "action did not output a buffer address" | ||
| [ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set" | ||
| [ -n "${DEPLOYER:-}" ] || fail "DEPLOYER env not set" | ||
|
|
||
| DUMP="$(mktemp)" | ||
| solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER" | ||
| cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact" | ||
| rm -f "$DUMP" | ||
|
|
||
| BUFFER_INFO=$(solana program show "$BUFFER" -u "$RPC_URL") | ||
| echo "$BUFFER_INFO" | ||
| AUTHORITY=$(echo "$BUFFER_INFO" | grep "Authority:" | awk '{print $2}' || true) | ||
| [ -n "$AUTHORITY" ] || fail "could not read authority of buffer $BUFFER" | ||
| [ "$AUTHORITY" = "$DEPLOYER" ] || fail "buffer authority is $AUTHORITY, expected deployer $DEPLOYER" | ||
|
|
||
| ABSENCE_CHECK=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" 2>&1 || true) | ||
| if ! echo "$ABSENCE_CHECK" | grep -q "Unable to find the account"; then | ||
| fail "program $PROGRAM_ID unexpectedly exists or the absence check errored: $ABSENCE_CHECK" | ||
| fi | ||
|
|
||
| echo "Fresh-program scenario assertions passed" |
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,29 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| RPC_URL="${RPC_URL:-http://127.0.0.1:8899}" | ||
| ARTIFACT="target/deploy/fixture-nearmax.so" | ||
| MAX_PERMITTED_DATA_LENGTH=10485760 | ||
| PROGRAMDATA_METADATA_SIZE=45 | ||
| MAX_PROGRAM_SIZE=$((MAX_PERMITTED_DATA_LENGTH - PROGRAMDATA_METADATA_SIZE)) | ||
|
|
||
| fail() { | ||
| echo "ASSERT FAIL: $1" >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| [ -n "${BUFFER:-}" ] || fail "action did not output a buffer address" | ||
| [ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set" | ||
| [ -n "${PRE_LEN:-}" ] || fail "PRE_LEN env not set" | ||
|
|
||
| POST_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | sed -E 's/.*Data Length: ([0-9]+).*/\1/' | cut -d ' ' -f1 || true) | ||
| [ -n "$POST_LEN" ] || fail "could not read data length of program $PROGRAM_ID" | ||
| [ "$POST_LEN" -eq "$MAX_PROGRAM_SIZE" ] || fail "program data length is $POST_LEN, expected the exact maximum $MAX_PROGRAM_SIZE" | ||
| echo "Program extended from $PRE_LEN to $POST_LEN (exact headroom of $((POST_LEN - PRE_LEN)) bytes)" | ||
|
|
||
| DUMP="$(mktemp)" | ||
| solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER" | ||
| cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact" | ||
| rm -f "$DUMP" | ||
|
|
||
| echo "Near-max extend assertions passed" |
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.