diff --git a/.github/.editorconfig b/.github/.editorconfig new file mode 100644 index 00000000000..751cd705457 --- /dev/null +++ b/.github/.editorconfig @@ -0,0 +1,8 @@ +# Make sure this is the top-level editorconfig +# https://editorconfig.org/ +root = true + +# GitHub Actions Workflows +[workflows/**.yml] +indent_style = space +indent_size = 2 diff --git a/.github/codecov.yml b/.github/codecov.yml index d1ecba7ade3..f0cb9583cf2 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -4,7 +4,7 @@ codecov: wait_for_ci: true comment: - require_changes: false + require_changes: true coverage: round: down @@ -13,7 +13,7 @@ coverage: project: default: target: auto - threshold: 10 # Let's decrease this later. + threshold: 5 # Let's decrease this later. base: parent if_no_uploads: error if_not_found: success @@ -22,12 +22,12 @@ coverage: patch: default: target: auto - threshold: 10 # Let's decrease this later. + threshold: 5 # Let's decrease this later. base: auto if_no_uploads: error if_not_found: success if_ci_failed: error - only_pulls: false + only_pulls: true # Only check patch coverage on PRs flag_management: default_rules: diff --git a/.github/golangci.yml b/.github/golangci.yml index b8bd5537135..ca85620b7e6 100644 --- a/.github/golangci.yml +++ b/.github/golangci.yml @@ -44,9 +44,11 @@ linters: linters-settings: gofmt: simplify: true + goconst: min-len: 3 min-occurrences: 3 + gosec: excludes: - G204 # Subprocess launched with a potential tainted input or cmd arguments @@ -56,6 +58,7 @@ linters-settings: checks: [ "all", "-ST1022", "-ST1003" ] errorlint: asserts: false + gocritic: enabled-tags: - diagnostic @@ -63,6 +66,7 @@ linters-settings: - opinionated - performance - style + forbidigo: forbid: - p: '^regexp\.(Match|MatchString)$' @@ -74,6 +78,7 @@ issues: max-same-issues: 0 new: false fix: false + exclude-rules: - path: _test\.go linters: diff --git a/.github/workflows/auto-author-assign.yml b/.github/workflows/auto-author-assign.yml index 06dfb4ab903..890e70da9ae 100644 --- a/.github/workflows/auto-author-assign.yml +++ b/.github/workflows/auto-author-assign.yml @@ -1,4 +1,4 @@ -name: auto-author-assign +name: Auto Assign PR Author on: pull_request_target: diff --git a/.github/workflows/autocounterd.yml b/.github/workflows/autocounterd.yml index 9217fe2eef2..dcba56178bd 100644 --- a/.github/workflows/autocounterd.yml +++ b/.github/workflows/autocounterd.yml @@ -1,19 +1,13 @@ -name: autocounterd +name: Portal Loop - autocounterd on: - pull_request: - branches: - - master push: + branches: + - "master" paths: - misc/autocounterd - misc/loop - .github/workflows/autocounterd.yml - branches: - - "master" - - "misc/autocounterd" - tags: - - "v*" permissions: contents: read diff --git a/.github/workflows/benchmark-master-push.yml b/.github/workflows/benchmark-master-push.yml index 622baefc0de..1c054077a3a 100644 --- a/.github/workflows/benchmark-master-push.yml +++ b/.github/workflows/benchmark-master-push.yml @@ -1,14 +1,14 @@ -name: run benchmarks when pushing on main branch +name: Run and Save Benchmarks on: push: branches: - master paths: - - contribs/** - - gno.land/** - - gnovm/** - - tm2/** + - contribs/**/*.go + - gno.land/**/*.go + - gnovm/**/*.go + - tm2/**/*.go permissions: # deployments permission to deploy GitHub pages website @@ -22,7 +22,7 @@ env: jobs: benchmarks: if: ${{ github.repository == 'gnolang/gno' }} - runs-on: [self-hosted, Linux, X64, benchmarks] + runs-on: [ self-hosted, Linux, X64, benchmarks ] steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d2eef9d7445..4745788714d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -9,13 +9,17 @@ # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # -name: "CodeQL" +name: CodeQL on: push: branches: [ "master", "chain/*" ] pull_request: branches: [ "master", "chain/*" ] + paths: + - '**/*.go' + - 'go.mod' + - 'go.sum' schedule: - cron: '22 17 * * 3' @@ -41,8 +45,8 @@ jobs: fail-fast: false matrix: include: - - language: go - build-mode: autobuild + - language: go + build-mode: autobuild # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' # Use `c-cpp` to analyze code written in C, C++ or both # Use 'java-kotlin' to analyze code written in Java, Kotlin or both @@ -52,38 +56,38 @@ jobs: # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality - # If the analyze step fails for one of the languages you are analyzing with - # "We were unable to automatically build your code", modify the matrix above - # to set the build mode to "manual" for that language. Then modify this step - # to build your code. - # ℹī¸ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - if: matrix.build-mode == 'manual' - run: | - echo 'If you are using a "manual" build mode for one or more of the' \ - 'languages you are analyzing, replace this with the commands to build' \ - 'your code, for example:' - echo ' make bootstrap' - echo ' make release' - exit 1 + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{matrix.language}}" + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/contribs.yml b/.github/workflows/contribs.yml index 3739339f7be..c1de5e78c35 100644 --- a/.github/workflows/contribs.yml +++ b/.github/workflows/contribs.yml @@ -1,30 +1,54 @@ -name: contribs +name: Contribs on: push: branches: - master - workflow_dispatch: pull_request: + paths: + - contribs/** + workflow_dispatch: jobs: setup: runs-on: ubuntu-latest outputs: programs: ${{ steps.set-matrix.outputs.programs }} + go-versions: ${{ steps.get-go-versions.outputs.go-versions }} steps: - uses: actions/checkout@v4 + - id: set-matrix - run: echo "::set-output name=programs::$(ls -d contribs/*/ | cut -d/ -f2 | jq -R -s -c 'split("\n")[:-1]')" + run: | + echo "::set-output name=programs::$(ls -d contribs/*/ | cut -d/ -f2 | jq -R -s -c 'split("\n")[:-1]')" + + - id: get-go-versions + run: | + contribs_programs=$(ls -d contribs/*/ | cut -d/ -f2) + versions_map="{" + + for p in $contribs_programs; do + # Fetch the go version of the contribs entry, and save it + # to a versions map we can reference later in the workflow + go_version=$(grep "^go [0-9]" contribs/$p/go.mod | cut -d ' ' -f2) + versions_map="$versions_map\"$p\":\"$go_version\"," + done + + # Close out the JSON + versions_map="${versions_map%,}" + versions_map="$versions_map}" + echo "::set-output name=go-versions::$versions_map" + main: needs: setup strategy: - fail-fast: false - matrix: - program: ${{ fromJson(needs.setup.outputs.programs) }} + fail-fast: false + matrix: + program: ${{ fromJson(needs.setup.outputs.programs) }} name: Run Main uses: ./.github/workflows/main_template.yml with: modulepath: contribs/${{ matrix.program }} + go-version: ${{ (fromJson(needs.setup.outputs.go-versions))[matrix.program] }} secrets: codecov-token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/dependabot-validate.yml b/.github/workflows/dependabot-validate.yml index b1387dc0bb2..3d7b2c315c6 100644 --- a/.github/workflows/dependabot-validate.yml +++ b/.github/workflows/dependabot-validate.yml @@ -1,10 +1,11 @@ -name: dependabot validate +name: Validate Dependabot Config on: pull_request: paths: - '.github/dependabot.yml' - '.github/workflows/dependabot-validate.yml' + jobs: validate: runs-on: ubuntu-latest diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index d800147a498..f180f1679b1 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -1,4 +1,6 @@ -name: deploy docs on gnolang/docs.gno.land repository +# This workflow triggers a cross-repo workflow call, +# that deploys the monorepo docs on Netlify, using Docusaurus +name: Deploy the Documentation on: push: branches: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs-linter.yml similarity index 95% rename from .github/workflows/docs.yml rename to .github/workflows/docs-linter.yml index c9d9af0fb6f..d603d796ae9 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs-linter.yml @@ -1,8 +1,8 @@ -name: "docs / lint" +name: Docs Linter on: push: - paths: + branches: - master pull_request: paths: diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 41d579c4567..e933db9ea19 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -1,9 +1,13 @@ -name: examples +name: Gno Examples on: - pull_request: push: - branches: ["master"] + branches: + - master + pull_request: + paths: + - gnovm/**/*.gno + - examples/**/*.gno concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -67,26 +71,16 @@ jobs: # TODO: consider running lint on every other directories, maybe in "warning" mode? # TODO: track coverage fmt: - strategy: - fail-fast: false - matrix: - goversion: ["1.22.x"] - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.goversion }} - - run: | - make fmt -C ./examples - # Check if there are changes after running make fmt - git diff --exit-code || (echo "Some gno files are not formatted, please run 'make fmt'." && exit 1) + name: Run gno fmt on examples + uses: ./.github/workflows/gnofmt_template.yml + with: + path: "examples/..." + mod-tidy: strategy: fail-fast: false matrix: - go-version: ["1.22.x"] + go-version: [ "1.22.x" ] # unittests: TODO: matrix with contracts runs-on: ubuntu-latest timeout-minutes: 10 diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml index 41d9a2cba94..0a94211cb90 100644 --- a/.github/workflows/fossa.yml +++ b/.github/workflows/fossa.yml @@ -1,6 +1,18 @@ name: Dependency License Scanning on: + push: + branches: + - master + paths: + - '**/*.go' + - 'go.mod' + - 'go.sum' + pull_request_target: + paths: + - '**/*.go' + - 'go.mod' + - 'go.sum' workflow_dispatch: permissions: diff --git a/.github/workflows/genesis-verify.yml b/.github/workflows/genesis-verify.yml index 1288d588100..acc41cc99ad 100644 --- a/.github/workflows/genesis-verify.yml +++ b/.github/workflows/genesis-verify.yml @@ -1,9 +1,10 @@ -name: genesis-verify +name: Deployment genesis.json Verification on: - pull_request: + push: branches: - master + pull_request: paths: - "misc/deployments/**/genesis.json" - ".github/workflows/genesis-verify.yml" @@ -13,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - testnet: ["test5.gno.land"] + testnet: [ "test5.gno.land" ] runs-on: ubuntu-latest steps: - name: Checkout code diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 993c11b5941..a293469bb5d 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -1,9 +1,11 @@ -# generate docs and publish on gh-pages branch -name: gh-pages +# generate Go docs and publish on gh-pages branch +# Live at: https://gnolang.github.io/gno +name: Go Reference Docs Deployment on: push: - branches: [ "master" ] + branches: + - master workflow_dispatch: permissions: @@ -28,10 +30,10 @@ jobs: - run: echo $GOROOT - run: "cd misc/stdlib_diff && make gen" - run: "cd misc/gendocs && make install gen" - - run: "mkdir -p pages_ouput/stdlib_diff" + - run: "mkdir -p pages_output/stdlib_diff" - run: | - mv misc/gendocs/godoc pages_output - mv misc/stdlib_diff/stdlib_diff pages_ouput/stdlib_diff + cp -r misc/gendocs/godoc/* pages_output/ + cp -r misc/stdlib_diff/stdlib_diff/* pages_output/stdlib_diff/ - uses: actions/configure-pages@v5 id: pages - uses: actions/upload-pages-artifact@v3 diff --git a/.github/workflows/gnofmt_template.yml b/.github/workflows/gnofmt_template.yml index 1ba66d0fbe3..096dbaa1b5d 100644 --- a/.github/workflows/gnofmt_template.yml +++ b/.github/workflows/gnofmt_template.yml @@ -1,12 +1,15 @@ on: workflow_call: - inputs: - path: - required: true - type: string - go-version: - required: true - type: string + inputs: + path: + description: "Path to run gno fmt on" + required: true + type: string + go-version: + description: "Go version to use" + required: false + type: string + default: "1.22.x" jobs: fmt: @@ -16,9 +19,15 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ inputs.go-version }} + - name: Checkout code uses: actions/checkout@v4 - - name: Fmt + + - name: Format code with gno fmt env: GNOFMT_PATH: ${{ inputs.path }} run: go run ./gnovm/cmd/gno fmt -v -diff $GNOFMT_PATH + + - name: Check for unformatted code + run: | + git diff --exit-code || (echo "Some gno files are not formatted, please run 'make fmt'." && exit 1) \ No newline at end of file diff --git a/.github/workflows/gnoland.yml b/.github/workflows/gnoland.yml index 59050f1baa4..0d3a7a10516 100644 --- a/.github/workflows/gnoland.yml +++ b/.github/workflows/gnoland.yml @@ -4,12 +4,18 @@ on: push: branches: - master - workflow_dispatch: pull_request: + paths: + - gno.land/** + # We trigger the testing workflow for gno.land on the following, + # since there are integration suites that cover the gnovm / tm2 + - gnovm/** + - tm2/** + workflow_dispatch: jobs: main: - name: Run Main + name: Run gno.land suite uses: ./.github/workflows/main_template.yml with: modulepath: "gno.land" diff --git a/.github/workflows/gnovm.yml b/.github/workflows/gnovm.yml index 7e7586b23d9..7a015b74e09 100644 --- a/.github/workflows/gnovm.yml +++ b/.github/workflows/gnovm.yml @@ -1,23 +1,26 @@ -name: gnovm +name: GnoVM on: push: branches: - master - workflow_dispatch: pull_request: + paths: + - gnovm/** + - tm2/** # GnoVM has a dependency on TM2 types + workflow_dispatch: jobs: main: - name: Run Main + name: Run GnoVM suite uses: ./.github/workflows/main_template.yml with: modulepath: "gnovm" + tests-extra-args: "-coverpkg=github.com/gnolang/gno/gnovm/..." secrets: codecov-token: ${{ secrets.CODECOV_TOKEN }} fmt: - name: Run Gno Fmt + name: Run gno fmt on stdlibs uses: ./.github/workflows/gnofmt_template.yml with: path: "gnovm/stdlibs/..." - go-version: "1.22.x" diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 06b2daa1d3d..56075c31db3 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,6 +1,6 @@ -name: "Pull Request Labeler" +name: Pull Request Labeler on: -- pull_request_target + - pull_request_target jobs: triage: @@ -9,5 +9,5 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/labeler@v5 + - uses: actions/checkout@v4 + - uses: actions/labeler@v5 diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml index 631f764c37f..3c7236b264f 100644 --- a/.github/workflows/lint-pr-title.yml +++ b/.github/workflows/lint-pr-title.yml @@ -1,4 +1,4 @@ -name: "lint-pr-title" +name: PR Title Linter on: pull_request_target: diff --git a/.github/workflows/lint_template.yml b/.github/workflows/lint_template.yml index b7568d19c41..43246572daa 100644 --- a/.github/workflows/lint_template.yml +++ b/.github/workflows/lint_template.yml @@ -8,7 +8,6 @@ on: required: true type: string - jobs: lint: runs-on: ubuntu-latest diff --git a/.github/workflows/main_template.yml b/.github/workflows/main_template.yml index 5b3437b54a1..a463bb330ea 100644 --- a/.github/workflows/main_template.yml +++ b/.github/workflows/main_template.yml @@ -1,41 +1,42 @@ on: - workflow_call: - inputs: - modulepath: - required: true - type: string - tests-extra-args: - required: false - type: string - secrets: - codecov-token: - required: true - -# TODO: environment variables cannot be sent to reusable workflows: https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations -# env: -# GO_VERSION: "1.22.x" + workflow_call: + inputs: + modulepath: + required: true + type: string + tests-extra-args: + required: false + type: string + go-version: + description: "Go version to use" + required: false + type: string + default: "1.22.x" + secrets: + codecov-token: + required: true jobs: - lint: - name: Go Linter - uses: ./.github/workflows/lint_template.yml - with: - modulepath: ${{ inputs.modulepath }} - go-version: "1.22.x" - build: - name: Go Build - uses: ./.github/workflows/build_template.yml - with: - modulepath: ${{ inputs.modulepath }} - go-version: "1.22.x" - test: - name: Go Test - uses: ./.github/workflows/test_template.yml - with: - modulepath: ${{ inputs.modulepath }} - tests-timeout: "30m" - go-version: "1.22.x" - tests-extra-args: ${{ inputs.tests-extra-args }} - secrets: - codecov-token: ${{ secrets.codecov-token }} + lint: + name: Go Lint + uses: ./.github/workflows/lint_template.yml + with: + modulepath: ${{ inputs.modulepath }} + go-version: ${{ inputs.go-version }} + build: + name: Go Build + uses: ./.github/workflows/build_template.yml + with: + modulepath: ${{ inputs.modulepath }} + go-version: ${{ inputs.go-version }} + test: + name: Go Test + uses: ./.github/workflows/test_template.yml + with: + modulepath: ${{ inputs.modulepath }} + tests-timeout: "30m" + go-version: ${{ inputs.go-version }} + tests-extra-args: ${{ inputs.tests-extra-args }} + secrets: + codecov-token: ${{ secrets.codecov-token }} diff --git a/.github/workflows/misc.yml b/.github/workflows/misc.yml index ad2c886e2ac..1116a87c300 100644 --- a/.github/workflows/misc.yml +++ b/.github/workflows/misc.yml @@ -6,8 +6,10 @@ on: push: branches: - master - workflow_dispatch: pull_request: + paths: + - misc/** + workflow_dispatch: jobs: main: @@ -21,9 +23,10 @@ jobs: - genstd - goscan - loop - name: Run Main + name: Run misc suite uses: ./.github/workflows/main_template.yml with: modulepath: misc/${{ matrix.program }} + tests-extra-args: "-coverpkg=github.com/gnolang/gno/misc/..." secrets: codecov-token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/mod-tidy.yml b/.github/workflows/mod-tidy.yml index 24eab553d19..5b6401b0d13 100644 --- a/.github/workflows/mod-tidy.yml +++ b/.github/workflows/mod-tidy.yml @@ -1,11 +1,19 @@ -name: Ensure go.mods are tidied +name: go.mod Tidy Checker on: push: branches: - master - workflow_dispatch: + paths: + - '**/*.go' + - 'go.mod' + - 'go.sum' pull_request: + paths: + - '**/*.go' + - 'go.mod' + - 'go.sum' + workflow_dispatch: jobs: main: diff --git a/.github/workflows/portal-loop.yml b/.github/workflows/portal-loop.yml index b898a149e9d..b5cafa459a7 100644 --- a/.github/workflows/portal-loop.yml +++ b/.github/workflows/portal-loop.yml @@ -1,19 +1,13 @@ -name: portal-loop +name: Portal Loop on: - pull_request: - branches: - - master push: + branches: + - "master" + pull_request: paths: - "misc/loop/**" - ".github/workflows/portal-loop.yml" - branches: - - "master" - # NOTE(albttx): branch name to simplify tests for this workflow - - "ci/portal-loop" - tags: - - "v*" permissions: contents: read @@ -69,14 +63,14 @@ jobs: while block_height=$(curl -s localhost:26657/status | jq -r '.result.sync_info.latest_block_height') echo "Current block height: $block_height" - [[ "$block_height" -lt 10 ]] + [[ "$block_height" -lt 2 ]] do sleep 1 done curl -s localhost:26657/status | jq - - name: "Buid new gnolang/gno image" + - name: "Build new gnolang/gno image" run: | docker build -t ghcr.io/gnolang/gno/gnoland:master -f Dockerfile --target gnoland . @@ -96,7 +90,7 @@ jobs: while block_height=$(curl -s localhost:26657/status | jq -r '.result.sync_info.latest_block_height') echo "Current block height: $block_height" - [[ "$block_height" -lt 10 ]] + [[ "$block_height" -lt 2 ]] do sleep 5 done diff --git a/.github/workflows/releaser-master.yml b/.github/workflows/releaser-master.yml index 3d194e2cb4c..6e3eed31914 100644 --- a/.github/workflows/releaser-master.yml +++ b/.github/workflows/releaser-master.yml @@ -1,9 +1,9 @@ -name: Trigger master build +name: Master Releases on: push: branches: - - "master" + - master workflow_dispatch: permissions: diff --git a/.github/workflows/releaser-nightly.yml b/.github/workflows/releaser-nightly.yml index 4308f1c4a7d..4f6e636af1b 100644 --- a/.github/workflows/releaser-nightly.yml +++ b/.github/workflows/releaser-nightly.yml @@ -1,4 +1,4 @@ -name: Trigger nightly build +name: Nightly Releases on: schedule: diff --git a/.github/workflows/releaser.yml b/.github/workflows/releaser.yml deleted file mode 100644 index 309664bdcce..00000000000 --- a/.github/workflows/releaser.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Go Releaser - -on: - push: - tags: - - "v*" - -permissions: - contents: write # needed to write releases - id-token: write # needed for keyless signing - packages: write # needed for ghcr access - -jobs: - goreleaser: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - cache: true - - - uses: sigstore/cosign-installer@v3.7.0 - - uses: anchore/sbom-action/download-syft@v0.17.8 - - - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - uses: goreleaser/goreleaser-action@v6 - with: - distribution: goreleaser-pro - version: ~> v2 - args: release --clean --config ./.github/goreleaser.yaml - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml index 55a17ac60a8..6eb38ac5728 100644 --- a/.github/workflows/stale-bot.yml +++ b/.github/workflows/stale-bot.yml @@ -1,4 +1,4 @@ -name: "Close stale PRs" +name: Stale PR Bot on: schedule: - cron: "30 1 * * *" diff --git a/.github/workflows/test_template.yml b/.github/workflows/test_template.yml index c7956b4caf4..a1bc58ecebb 100644 --- a/.github/workflows/test_template.yml +++ b/.github/workflows/test_template.yml @@ -1,84 +1,70 @@ on: - workflow_call: - inputs: - modulepath: - required: true - type: string - tests-timeout: - required: true - type: string - go-version: - required: true - type: string - tests-extra-args: - required: false - type: string - secrets: - codecov-token: - required: true + workflow_call: + inputs: + modulepath: + required: true + type: string + tests-timeout: + required: true + type: string + go-version: + required: true + type: string + tests-extra-args: + required: false + type: string + secrets: + codecov-token: + required: true jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Install Go - uses: actions/setup-go@v5 - with: - go-version: ${{ inputs.go-version }} - - name: Go test - working-directory: ${{ inputs.modulepath }} - env: - TXTARCOVERDIR: /tmp/txtarcoverdir # txtar cover output - GOCOVERDIR: /tmp/gocoverdir # go cover output - COVERDIR: /tmp/coverdir # final output - run: | - set -x # print commands + test: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: ${{ inputs.go-version }} + - name: Go test + working-directory: ${{ inputs.modulepath }} + env: + TXTARCOVERDIR: /tmp/txtarcoverdir # txtar cover output + GOCOVERDIR: /tmp/gocoverdir # go cover output + COVERDIR: /tmp/coverdir # final output + run: | + set -x # print commands + + mkdir -p "$GOCOVERDIR" "$TXTARCOVERDIR" "$COVERDIR" + + # Craft a filter flag based on the module path to avoid expanding coverage on unrelated tags. + export filter="-pkg=github.com/gnolang/gno/${{ inputs.modulepath }}/..." + + # codecov only supports "boolean" coverage (whether a line is + # covered or not); so using -covermode=count or atomic would be + # pointless here. + # XXX: Simplify coverage of txtar - the current setup is a bit + # confusing and meticulous. There will be some improvements in Go + # 1.23 regarding coverage, so we can use this as a workaround until + # then. + go test -covermode=set -timeout ${{ inputs.tests-timeout }} ${{ inputs.tests-extra-args }} ./... -test.gocoverdir=$GOCOVERDIR + + # Print results + (set +x; echo 'go coverage results:') + go tool covdata percent $filter -i=$GOCOVERDIR + (set +x; echo 'txtar coverage results:') + go tool covdata percent $filter -i=$TXTARCOVERDIR + + # Generate final coverage output + go tool covdata textfmt -v 1 $filter -i=$GOCOVERDIR,$TXTARCOVERDIR -o gocoverage.out - mkdir -p "$GOCOVERDIR" "$TXTARCOVERDIR" "$COVERDIR" - - # Craft a filter flag based on the module path to avoid expanding coverage on unrelated tags. - export filter="-pkg=github.com/gnolang/gno/${{ inputs.modulepath }}/..." - - # codecov only supports "boolean" coverage (whether a line is - # covered or not); so using -covermode=count or atomic would be - # pointless here. - # XXX: Simplify coverage of txtar - the current setup is a bit - # confusing and meticulous. There will be some improvements in Go - # 1.23 regarding coverage, so we can use this as a workaround until - # then. - go test -covermode=set -timeout ${{ inputs.tests-timeout }} ${{ inputs.tests-extra-args }} ./... -test.gocoverdir=$GOCOVERDIR - - # Print results - (set +x; echo 'go coverage results:') - go tool covdata percent $filter -i=$GOCOVERDIR - (set +x; echo 'txtar coverage results:') - go tool covdata percent $filter -i=$TXTARCOVERDIR - - # Generate final coverage output - go tool covdata textfmt -v 1 $filter -i=$GOCOVERDIR,$TXTARCOVERDIR -o gocoverage.out - - - name: Upload go coverage to Codecov - uses: codecov/codecov-action@v5 - with: - disable_search: true - fail_ci_if_error: true - files: ${{ inputs.modulepath }}/gocoverage.out - flags: ${{ inputs.modulepath }} - token: ${{ secrets.codecov-token }} - verbose: true # keep this enable as it help debugging when coverage fail randomly on the CI - - # TODO: We have to fix race conditions before running this job - # test-with-race: - # runs-on: ubuntu-latest - # steps: - # - name: Install Go - # uses: actions/setup-go@v5 - # with: - # go-version: ${{ inputs.go-version }} - # - name: Checkout code - # uses: actions/checkout@v4 - # - name: Go race test - # run: go test -race -timeout ${{ inputs.tests-timeout }} ./... - # working-directory: ${{ inputs.modulepath }} + - name: Upload go coverage to Codecov + uses: codecov/codecov-action@v5 + with: + disable_search: true + fail_ci_if_error: true + files: ${{ inputs.modulepath }}/gocoverage.out + flags: ${{ inputs.modulepath }} + token: ${{ secrets.codecov-token }} + verbose: true # keep this enable as it help debugging when coverage fails randomly on the CI diff --git a/.github/workflows/tm2.yml b/.github/workflows/tm2.yml index 57e84793c94..757391eab8c 100644 --- a/.github/workflows/tm2.yml +++ b/.github/workflows/tm2.yml @@ -1,17 +1,20 @@ -name: tm2 +name: TM2 on: push: branches: - master - workflow_dispatch: pull_request: + paths: + - tm2/** + workflow_dispatch: jobs: main: - name: Run Main + name: Run TM2 suite uses: ./.github/workflows/main_template.yml with: modulepath: "tm2" + tests-extra-args: "-coverpkg=github.com/gnolang/gno/tm2/..." secrets: codecov-token: ${{ secrets.CODECOV_TOKEN }} diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index b5b5a402c2a..6ca47408a75 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -93,7 +93,7 @@ require ( go.opentelemetry.io/otel/trace v1.29.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap/exp v0.2.0 // indirect + go.uber.org/zap/exp v0.3.0 // indirect golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/mod v0.20.0 // indirect diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum index bab6e5364e8..912345d61a8 100644 --- a/contribs/gnodev/go.sum +++ b/contribs/gnodev/go.sum @@ -133,8 +133,6 @@ github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gotuna/gotuna v0.6.0 h1:N1lQKXEi/lwRp8u3sccTYLhzOffA4QasExz/1M5Riws= -github.com/gotuna/gotuna v0.6.0/go.mod h1:F/ecRt29ChB6Ycy1AFIBpBiMNK0j7Heq+gFbLWquhjc= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= @@ -257,8 +255,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs= -go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ= +go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= +go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/contribs/gnofaucet/go.mod b/contribs/gnofaucet/go.mod index eab9fc90c50..3abc189b86a 100644 --- a/contribs/gnofaucet/go.mod +++ b/contribs/gnofaucet/go.mod @@ -40,7 +40,7 @@ require ( go.opentelemetry.io/otel/trace v1.29.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap/exp v0.2.0 // indirect + go.uber.org/zap/exp v0.3.0 // indirect golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/mod v0.20.0 // indirect diff --git a/contribs/gnofaucet/go.sum b/contribs/gnofaucet/go.sum index aabe858e893..fafbc4d1060 100644 --- a/contribs/gnofaucet/go.sum +++ b/contribs/gnofaucet/go.sum @@ -146,8 +146,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs= -go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ= +go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= +go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/examples/gno.land/p/demo/avl/list/gno.mod b/examples/gno.land/p/demo/avl/list/gno.mod new file mode 100644 index 00000000000..c05923b7708 --- /dev/null +++ b/examples/gno.land/p/demo/avl/list/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/avl/list diff --git a/examples/gno.land/p/demo/avl/list/list.gno b/examples/gno.land/p/demo/avl/list/list.gno new file mode 100644 index 00000000000..0875eb66e01 --- /dev/null +++ b/examples/gno.land/p/demo/avl/list/list.gno @@ -0,0 +1,298 @@ +// Package list implements a dynamic list data structure backed by an AVL tree. +// It provides O(log n) operations for most list operations while maintaining +// order stability. +// +// The list supports various operations including append, get, set, delete, +// range queries, and iteration. It can store values of any type. +// +// Example usage: +// +// // Create a new list and add elements +// var l list.List +// l.Append(1, 2, 3) +// +// // Get and set elements +// value := l.Get(1) // returns 2 +// l.Set(1, 42) // updates index 1 to 42 +// +// // Delete elements +// l.Delete(0) // removes first element +// +// // Iterate over elements +// l.ForEach(func(index int, value interface{}) bool { +// ufmt.Printf("index %d: %v\n", index, value) +// return false // continue iteration +// }) +// // Output: +// // index 0: 42 +// // index 1: 3 +// +// // Create a list of specific size +// l = list.Make(3, "default") // creates [default, default, default] +// +// // Create a list using a variable declaration +// var l2 list.List +// l2.Append(4, 5, 6) +// println(l2.Len()) // Output: 3 +package list + +import ( + "gno.land/p/demo/avl" + "gno.land/p/demo/seqid" +) + +// List represents an ordered sequence of items backed by an AVL tree +type List struct { + tree avl.Tree + idGen seqid.ID +} + +// Len returns the number of elements in the list. +// +// Example: +// +// l := list.New() +// l.Append(1, 2, 3) +// println(l.Len()) // Output: 3 +func (l *List) Len() int { + return l.tree.Size() +} + +// Append adds one or more values to the end of the list. +// +// Example: +// +// l := list.New() +// l.Append(1) // adds single value +// l.Append(2, 3, 4) // adds multiple values +// println(l.Len()) // Output: 4 +func (l *List) Append(values ...interface{}) { + for _, v := range values { + l.tree.Set(l.idGen.Next().String(), v) + } +} + +// Get returns the value at the specified index. +// Returns nil if index is out of bounds. +// +// Example: +// +// l := list.New() +// l.Append(1, 2, 3) +// println(l.Get(1)) // Output: 2 +// println(l.Get(-1)) // Output: nil +// println(l.Get(999)) // Output: nil +func (l *List) Get(index int) interface{} { + if index < 0 || index >= l.tree.Size() { + return nil + } + _, value := l.tree.GetByIndex(index) + return value +} + +// Set updates or appends a value at the specified index. +// Returns true if the operation was successful, false otherwise. +// For empty lists, only index 0 is valid (append case). +// +// Example: +// +// l := list.New() +// l.Append(1, 2, 3) +// +// l.Set(1, 42) // updates existing index +// println(l.Get(1)) // Output: 42 +// +// l.Set(3, 4) // appends at end +// println(l.Get(3)) // Output: 4 +// +// l.Set(-1, 5) // invalid index +// println(l.Len()) // Output: 4 (list unchanged) +func (l *List) Set(index int, value interface{}) bool { + size := l.tree.Size() + + // Handle empty list case - only allow index 0 + if size == 0 { + if index == 0 { + l.Append(value) + return true + } + return false + } + + if index < 0 || index > size { + return false + } + + // If setting at the end (append case) + if index == size { + l.Append(value) + return true + } + + // Get the key at the specified index + key, _ := l.tree.GetByIndex(index) + if key == "" { + return false + } + + // Update the value at the existing key + l.tree.Set(key, value) + return true +} + +// Delete removes the element at the specified index. +// Returns the deleted value and true if successful, nil and false otherwise. +// +// Example: +// +// l := list.New() +// l.Append(1, 2, 3) +// +// val, ok := l.Delete(1) +// println(val, ok) // Output: 2 true +// println(l.Len()) // Output: 2 +// +// val, ok = l.Delete(-1) +// println(val, ok) // Output: nil false +func (l *List) Delete(index int) (interface{}, bool) { + size := l.tree.Size() + // Always return nil, false for empty list + if size == 0 { + return nil, false + } + + if index < 0 || index >= size { + return nil, false + } + + key, value := l.tree.GetByIndex(index) + if key == "" { + return nil, false + } + + l.tree.Remove(key) + return value, true +} + +// Slice returns a slice of values from startIndex (inclusive) to endIndex (exclusive). +// Returns nil if the range is invalid. +// +// Example: +// +// l := list.New() +// l.Append(1, 2, 3, 4, 5) +// +// println(l.Slice(1, 4)) // Output: [2 3 4] +// println(l.Slice(-1, 2)) // Output: [1 2] +// println(l.Slice(3, 999)) // Output: [4 5] +// println(l.Slice(3, 2)) // Output: nil +func (l *List) Slice(startIndex, endIndex int) []interface{} { + size := l.tree.Size() + + // Normalize bounds + if startIndex < 0 { + startIndex = 0 + } + if endIndex > size { + endIndex = size + } + if startIndex >= endIndex { + return nil + } + + count := endIndex - startIndex + result := make([]interface{}, count) + + i := 0 + l.tree.IterateByOffset(startIndex, count, func(_ string, value interface{}) bool { + result[i] = value + i++ + return false + }) + return result +} + +// ForEach iterates through all elements in the list. +func (l *List) ForEach(fn func(index int, value interface{}) bool) { + if l.tree.Size() == 0 { + return + } + + index := 0 + l.tree.IterateByOffset(0, l.tree.Size(), func(_ string, value interface{}) bool { + result := fn(index, value) + index++ + return result + }) +} + +// Clone creates a shallow copy of the list. +// +// Example: +// +// l := list.New() +// l.Append(1, 2, 3) +// +// clone := l.Clone() +// clone.Set(0, 42) +// +// println(l.Get(0)) // Output: 1 +// println(clone.Get(0)) // Output: 42 +func (l *List) Clone() *List { + newList := &List{ + tree: avl.Tree{}, + idGen: l.idGen, + } + + size := l.tree.Size() + if size == 0 { + return newList + } + + l.tree.IterateByOffset(0, size, func(_ string, value interface{}) bool { + newList.Append(value) + return false + }) + + return newList +} + +// DeleteRange removes elements from startIndex (inclusive) to endIndex (exclusive). +// Returns the number of elements deleted. +// +// Example: +// +// l := list.New() +// l.Append(1, 2, 3, 4, 5) +// +// deleted := l.DeleteRange(1, 4) +// println(deleted) // Output: 3 +// println(l.Range(0, l.Len())) // Output: [1 5] +func (l *List) DeleteRange(startIndex, endIndex int) int { + size := l.tree.Size() + + // Normalize bounds + if startIndex < 0 { + startIndex = 0 + } + if endIndex > size { + endIndex = size + } + if startIndex >= endIndex { + return 0 + } + + // Collect keys to delete + keysToDelete := make([]string, 0, endIndex-startIndex) + l.tree.IterateByOffset(startIndex, endIndex-startIndex, func(key string, _ interface{}) bool { + keysToDelete = append(keysToDelete, key) + return false + }) + + // Delete collected keys + for _, key := range keysToDelete { + l.tree.Remove(key) + } + + return len(keysToDelete) +} diff --git a/examples/gno.land/p/demo/avl/list/list_test.gno b/examples/gno.land/p/demo/avl/list/list_test.gno new file mode 100644 index 00000000000..265fbdb5eb1 --- /dev/null +++ b/examples/gno.land/p/demo/avl/list/list_test.gno @@ -0,0 +1,409 @@ +package list + +import ( + "testing" +) + +func TestList_Basic(t *testing.T) { + var l List + + // Test empty list + if l.Len() != 0 { + t.Errorf("new list should be empty, got len %d", l.Len()) + } + + // Test append and length + l.Append(1, 2, 3) + if l.Len() != 3 { + t.Errorf("expected len 3, got %d", l.Len()) + } + + // Test get + if v := l.Get(0); v != 1 { + t.Errorf("expected 1 at index 0, got %v", v) + } + if v := l.Get(1); v != 2 { + t.Errorf("expected 2 at index 1, got %v", v) + } + if v := l.Get(2); v != 3 { + t.Errorf("expected 3 at index 2, got %v", v) + } + + // Test out of bounds + if v := l.Get(-1); v != nil { + t.Errorf("expected nil for negative index, got %v", v) + } + if v := l.Get(3); v != nil { + t.Errorf("expected nil for out of bounds index, got %v", v) + } +} + +func TestList_Set(t *testing.T) { + var l List + l.Append(1, 2, 3) + + // Test valid set within bounds + if ok := l.Set(1, 42); !ok { + t.Error("Set should return true for valid index") + } + if v := l.Get(1); v != 42 { + t.Errorf("expected 42 after Set, got %v", v) + } + + // Test set at size (append) + if ok := l.Set(3, 4); !ok { + t.Error("Set should return true when appending at size") + } + if v := l.Get(3); v != 4 { + t.Errorf("expected 4 after Set at size, got %v", v) + } + + // Test invalid sets + if ok := l.Set(-1, 10); ok { + t.Error("Set should return false for negative index") + } + if ok := l.Set(5, 10); ok { + t.Error("Set should return false for index > size") + } + + // Verify list state hasn't changed after invalid operations + expected := []interface{}{1, 42, 3, 4} + for i, want := range expected { + if got := l.Get(i); got != want { + t.Errorf("index %d = %v; want %v", i, got, want) + } + } +} + +func TestList_Delete(t *testing.T) { + var l List + l.Append(1, 2, 3) + + // Test valid delete + if v, ok := l.Delete(1); !ok || v != 2 { + t.Errorf("Delete(1) = %v, %v; want 2, true", v, ok) + } + if l.Len() != 2 { + t.Errorf("expected len 2 after delete, got %d", l.Len()) + } + if v := l.Get(1); v != 3 { + t.Errorf("expected 3 at index 1 after delete, got %v", v) + } + + // Test invalid delete + if v, ok := l.Delete(-1); ok || v != nil { + t.Errorf("Delete(-1) = %v, %v; want nil, false", v, ok) + } + if v, ok := l.Delete(2); ok || v != nil { + t.Errorf("Delete(2) = %v, %v; want nil, false", v, ok) + } +} + +func TestList_Slice(t *testing.T) { + var l List + l.Append(1, 2, 3, 4, 5) + + // Test valid ranges + values := l.Slice(1, 4) + expected := []interface{}{2, 3, 4} + if !sliceEqual(values, expected) { + t.Errorf("Slice(1,4) = %v; want %v", values, expected) + } + + // Test edge cases + if values := l.Slice(-1, 2); !sliceEqual(values, []interface{}{1, 2}) { + t.Errorf("Slice(-1,2) = %v; want [1 2]", values) + } + if values := l.Slice(3, 10); !sliceEqual(values, []interface{}{4, 5}) { + t.Errorf("Slice(3,10) = %v; want [4 5]", values) + } + if values := l.Slice(3, 2); values != nil { + t.Errorf("Slice(3,2) = %v; want nil", values) + } +} + +func TestList_ForEach(t *testing.T) { + var l List + l.Append(1, 2, 3) + + sum := 0 + l.ForEach(func(index int, value interface{}) bool { + sum += value.(int) + return false + }) + + if sum != 6 { + t.Errorf("ForEach sum = %d; want 6", sum) + } + + // Test early termination + count := 0 + l.ForEach(func(index int, value interface{}) bool { + count++ + return true // stop after first item + }) + + if count != 1 { + t.Errorf("ForEach early termination count = %d; want 1", count) + } +} + +func TestList_Clone(t *testing.T) { + var l List + l.Append(1, 2, 3) + + clone := l.Clone() + + // Test same length + if clone.Len() != l.Len() { + t.Errorf("clone.Len() = %d; want %d", clone.Len(), l.Len()) + } + + // Test same values + for i := 0; i < l.Len(); i++ { + if clone.Get(i) != l.Get(i) { + t.Errorf("clone.Get(%d) = %v; want %v", i, clone.Get(i), l.Get(i)) + } + } + + // Test independence + l.Set(0, 42) + if clone.Get(0) == l.Get(0) { + t.Error("clone should be independent of original") + } +} + +func TestList_DeleteRange(t *testing.T) { + var l List + l.Append(1, 2, 3, 4, 5) + + // Test valid range delete + deleted := l.DeleteRange(1, 4) + if deleted != 3 { + t.Errorf("DeleteRange(1,4) deleted %d elements; want 3", deleted) + } + if l.Len() != 2 { + t.Errorf("after DeleteRange(1,4) len = %d; want 2", l.Len()) + } + expected := []interface{}{1, 5} + for i, want := range expected { + if got := l.Get(i); got != want { + t.Errorf("after DeleteRange(1,4) index %d = %v; want %v", i, got, want) + } + } + + // Test edge cases + l = List{} + l.Append(1, 2, 3) + + // Delete with negative start + if deleted := l.DeleteRange(-1, 2); deleted != 2 { + t.Errorf("DeleteRange(-1,2) deleted %d elements; want 2", deleted) + } + + // Delete with end > length + l = List{} + l.Append(1, 2, 3) + if deleted := l.DeleteRange(1, 5); deleted != 2 { + t.Errorf("DeleteRange(1,5) deleted %d elements; want 2", deleted) + } + + // Delete invalid range + if deleted := l.DeleteRange(2, 1); deleted != 0 { + t.Errorf("DeleteRange(2,1) deleted %d elements; want 0", deleted) + } + + // Delete empty range + if deleted := l.DeleteRange(1, 1); deleted != 0 { + t.Errorf("DeleteRange(1,1) deleted %d elements; want 0", deleted) + } +} + +func TestList_EmptyOperations(t *testing.T) { + var l List + + // Operations on empty list + if v := l.Get(0); v != nil { + t.Errorf("Get(0) on empty list = %v; want nil", v) + } + + // Set should work at index 0 for empty list (append case) + if ok := l.Set(0, 1); !ok { + t.Error("Set(0,1) on empty list = false; want true") + } + if v := l.Get(0); v != 1 { + t.Errorf("Get(0) after Set = %v; want 1", v) + } + + l = List{} // Reset to empty list + if v, ok := l.Delete(0); ok || v != nil { + t.Errorf("Delete(0) on empty list = %v, %v; want nil, false", v, ok) + } + if values := l.Slice(0, 1); values != nil { + t.Errorf("Range(0,1) on empty list = %v; want nil", values) + } +} + +func TestList_DifferentTypes(t *testing.T) { + var l List + + // Test with different types + l.Append(42, "hello", true, 3.14) + + if v := l.Get(0).(int); v != 42 { + t.Errorf("Get(0) = %v; want 42", v) + } + if v := l.Get(1).(string); v != "hello" { + t.Errorf("Get(1) = %v; want 'hello'", v) + } + if v := l.Get(2).(bool); !v { + t.Errorf("Get(2) = %v; want true", v) + } + if v := l.Get(3).(float64); v != 3.14 { + t.Errorf("Get(3) = %v; want 3.14", v) + } +} + +func TestList_LargeOperations(t *testing.T) { + var l List + + // Test with larger number of elements + n := 1000 + for i := 0; i < n; i++ { + l.Append(i) + } + + if l.Len() != n { + t.Errorf("Len() = %d; want %d", l.Len(), n) + } + + // Test range on large list + values := l.Slice(n-3, n) + expected := []interface{}{n - 3, n - 2, n - 1} + if !sliceEqual(values, expected) { + t.Errorf("Range(%d,%d) = %v; want %v", n-3, n, values, expected) + } + + // Test large range deletion + deleted := l.DeleteRange(100, 900) + if deleted != 800 { + t.Errorf("DeleteRange(100,900) = %d; want 800", deleted) + } + if l.Len() != 200 { + t.Errorf("Len() after large delete = %d; want 200", l.Len()) + } +} + +func TestList_ChainedOperations(t *testing.T) { + var l List + + // Test sequence of operations + l.Append(1, 2, 3) + l.Delete(1) + l.Append(4) + l.Set(1, 5) + + expected := []interface{}{1, 5, 4} + for i, want := range expected { + if got := l.Get(i); got != want { + t.Errorf("index %d = %v; want %v", i, got, want) + } + } +} + +func TestList_RangeEdgeCases(t *testing.T) { + var l List + l.Append(1, 2, 3, 4, 5) + + // Test various edge cases for Range + cases := []struct { + start, end int + want []interface{} + }{ + {-10, 2, []interface{}{1, 2}}, + {3, 10, []interface{}{4, 5}}, + {0, 0, nil}, + {5, 5, nil}, + {4, 3, nil}, + {-1, -1, nil}, + } + + for _, tc := range cases { + got := l.Slice(tc.start, tc.end) + if !sliceEqual(got, tc.want) { + t.Errorf("Slice(%d,%d) = %v; want %v", tc.start, tc.end, got, tc.want) + } + } +} + +func TestList_IndexConsistency(t *testing.T) { + var l List + + // Initial additions + l.Append(1, 2, 3, 4, 5) // [1,2,3,4,5] + + // Delete from middle + l.Delete(2) // [1,2,4,5] + + // Add more elements + l.Append(6, 7) // [1,2,4,5,6,7] + + // Delete range from middle + l.DeleteRange(1, 4) // [1,6,7] + + // Add more elements + l.Append(8, 9, 10) // [1,6,7,8,9,10] + + // Verify sequence is continuous + expected := []interface{}{1, 6, 7, 8, 9, 10} + for i, want := range expected { + if got := l.Get(i); got != want { + t.Errorf("index %d = %v; want %v", i, got, want) + } + } + + // Verify no extra elements exist + if l.Len() != len(expected) { + t.Errorf("length = %d; want %d", l.Len(), len(expected)) + } + + // Verify all indices are accessible + allValues := l.Slice(0, l.Len()) + if !sliceEqual(allValues, expected) { + t.Errorf("Slice(0, Len()) = %v; want %v", allValues, expected) + } + + // Verify no gaps in iteration + var iteratedValues []interface{} + var indices []int + l.ForEach(func(index int, value interface{}) bool { + iteratedValues = append(iteratedValues, value) + indices = append(indices, index) + return false + }) + + // Check values from iteration + if !sliceEqual(iteratedValues, expected) { + t.Errorf("ForEach values = %v; want %v", iteratedValues, expected) + } + + // Check indices are sequential + for i, idx := range indices { + if idx != i { + t.Errorf("ForEach index %d = %d; want %d", i, idx, i) + } + } +} + +// Helper function to compare slices +func sliceEqual(a, b []interface{}) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} diff --git a/examples/gno.land/p/demo/avl/node.gno b/examples/gno.land/p/demo/avl/node.gno index 7308e163768..7d4ddffff02 100644 --- a/examples/gno.land/p/demo/avl/node.gno +++ b/examples/gno.land/p/demo/avl/node.gno @@ -384,7 +384,7 @@ func (node *Node) TraverseInRange(start, end string, ascending bool, leavesOnly // TraverseByOffset traverses all nodes, including inner nodes. // A limit of math.MaxInt means no limit. -func (node *Node) TraverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { +func (node *Node) TraverseByOffset(offset, limit int, ascending bool, leavesOnly bool, cb func(*Node) bool) bool { if node == nil { return false } @@ -401,21 +401,21 @@ func (node *Node) TraverseByOffset(offset, limit int, descending bool, leavesOnl } // go to the actual recursive function. - return node.traverseByOffset(offset, limit, descending, leavesOnly, cb) + return node.traverseByOffset(offset, limit, ascending, leavesOnly, cb) } // TraverseByOffset traverses the subtree rooted at the node by offset and limit, // in either ascending or descending order, and applies the callback function to each traversed node. // If leavesOnly is true, only leaf nodes are visited. -func (node *Node) traverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { +func (node *Node) traverseByOffset(offset, limit int, ascending bool, leavesOnly bool, cb func(*Node) bool) bool { // caller guarantees: offset < node.size; limit > 0. if !leavesOnly { if cb(node) { - return true + return true // Stop traversal if callback returns true } } first, second := node.getLeftNode(), node.getRightNode() - if descending { + if !ascending { first, second = second, first } if first.IsLeaf() { @@ -423,10 +423,12 @@ func (node *Node) traverseByOffset(offset, limit int, descending bool, leavesOnl if offset > 0 { offset-- } else { - cb(first) + if cb(first) { + return true // Stop traversal if callback returns true + } limit-- if limit <= 0 { - return false + return true // Stop traversal when limit is reached } } } else { @@ -437,7 +439,7 @@ func (node *Node) traverseByOffset(offset, limit int, descending bool, leavesOnl if offset >= first.size { offset -= first.size // 1 } else { - if first.traverseByOffset(offset, limit, descending, leavesOnly, cb) { + if first.traverseByOffset(offset, limit, ascending, leavesOnly, cb) { return true } // number of leaves which could actually be called from inside @@ -460,7 +462,7 @@ func (node *Node) traverseByOffset(offset, limit int, descending bool, leavesOnl } // => if it is not a leaf, it will still be enough to recursively call this // function with the updated offset and limit - return second.traverseByOffset(offset, limit, descending, leavesOnly, cb) + return second.traverseByOffset(offset, limit, ascending, leavesOnly, cb) } // Only used in testing... diff --git a/examples/gno.land/p/demo/avl/node_test.gno b/examples/gno.land/p/demo/avl/node_test.gno index f24217625ea..3682cbc7c80 100644 --- a/examples/gno.land/p/demo/avl/node_test.gno +++ b/examples/gno.land/p/demo/avl/node_test.gno @@ -17,36 +17,34 @@ Book Browser` tt := []struct { name string - desc bool + asc bool }{ - {"ascending", false}, - {"descending", true}, + {"ascending", true}, + {"descending", false}, } for _, tt := range tt { t.Run(tt.name, func(t *testing.T) { + // use sl to insert the values, and reversed to match the values + // we do this to ensure that the order of TraverseByOffset is independent + // from the insertion order sl := strings.Split(testStrings, "\n") - - // sort a first time in the order opposite to how we'll be traversing - // the tree, to ensure that we are not just iterating through with - // insertion order. sort.Strings(sl) - if !tt.desc { - reverseSlice(sl) + reversed := append([]string{}, sl...) + reverseSlice(reversed) + + if !tt.asc { + sl, reversed = reversed, sl } - r := NewNode(sl[0], nil) - for _, v := range sl[1:] { + r := NewNode(reversed[0], nil) + for _, v := range reversed[1:] { r, _ = r.Set(v, nil) } - // then sort sl in the order we'll be traversing it, so that we can - // compare the result with sl. - reverseSlice(sl) - var result []string for i := 0; i < len(sl); i++ { - r.TraverseByOffset(i, 1, tt.desc, true, func(n *Node) bool { + r.TraverseByOffset(i, 1, tt.asc, true, func(n *Node) bool { result = append(result, n.Key()) return false }) @@ -66,7 +64,7 @@ Browser` exp := sl[i:max] actual := []string{} - r.TraverseByOffset(i, l, tt.desc, true, func(tr *Node) bool { + r.TraverseByOffset(i, l, tt.asc, true, func(tr *Node) bool { actual = append(actual, tr.Key()) return false }) @@ -422,6 +420,30 @@ func TestTraverse(t *testing.T) { t.Errorf("want %v got %v", expected, result) } }) + + t.Run("early termination", func(t *testing.T) { + if len(tt.input) == 0 { + return // Skip for empty tree + } + + var result []string + var count int + tree.Iterate("", "", func(n *Node) bool { + count++ + result = append(result, n.Key()) + return true // Stop after first item + }) + + if count != 1 { + t.Errorf("Expected callback to be called exactly once, got %d calls", count) + } + if len(result) != 1 { + t.Errorf("Expected exactly one result, got %d items", len(result)) + } + if len(result) > 0 && result[0] != tt.expected[0] { + t.Errorf("Expected first item to be %v, got %v", tt.expected[0], result[0]) + } + }) }) } } @@ -435,7 +457,7 @@ func TestRotateWhenHeightDiffers(t *testing.T) { { "right rotation when left subtree is higher", []string{"E", "C", "A", "B", "D"}, - []string{"A", "B", "C", "E", "D"}, + []string{"A", "B", "C", "D", "E"}, }, { "left rotation when right subtree is higher", @@ -445,12 +467,12 @@ func TestRotateWhenHeightDiffers(t *testing.T) { { "left-right rotation", []string{"E", "A", "C", "B", "D"}, - []string{"A", "B", "C", "E", "D"}, + []string{"A", "B", "C", "D", "E"}, }, { "right-left rotation", []string{"A", "E", "C", "B", "D"}, - []string{"A", "B", "C", "E", "D"}, + []string{"A", "B", "C", "D", "E"}, }, } @@ -533,7 +555,7 @@ func slicesEqual(w1, w2 []string) bool { return false } for i := 0; i < len(w1); i++ { - if w1[0] != w2[0] { + if w1[i] != w2[i] { return false } } diff --git a/examples/gno.land/p/demo/avl/pager/pager.gno b/examples/gno.land/p/demo/avl/pager/pager.gno index ca3eadde032..f5f909a473d 100644 --- a/examples/gno.land/p/demo/avl/pager/pager.gno +++ b/examples/gno.land/p/demo/avl/pager/pager.gno @@ -90,12 +90,12 @@ func (p *Pager) GetPageWithSize(pageNumber, pageSize int) *Page { items := []Item{} if p.Reversed { - p.Tree.IterateByOffset(startIndex, endIndex-startIndex, func(key string, value interface{}) bool { + p.Tree.ReverseIterateByOffset(startIndex, endIndex-startIndex, func(key string, value interface{}) bool { items = append(items, Item{Key: key, Value: value}) return false }) } else { - p.Tree.ReverseIterateByOffset(startIndex, endIndex-startIndex, func(key string, value interface{}) bool { + p.Tree.IterateByOffset(startIndex, endIndex-startIndex, func(key string, value interface{}) bool { items = append(items, Item{Key: key, Value: value}) return false }) diff --git a/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno b/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno index 27842932dd3..e5fe33cacad 100644 --- a/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno +++ b/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno @@ -8,7 +8,7 @@ import ( // It calls the provided callback function for each key-value pair encountered. // If the callback returns true, the iteration is stopped. // The prefix and keys are treated as byte strings, ignoring possible multi-byte Unicode runes. -func IterateByteStringKeysByPrefix(tree avl.Tree, prefix string, cb avl.IterCbFn) { +func IterateByteStringKeysByPrefix(tree avl.ITree, prefix string, cb avl.IterCbFn) { end := "" n := len(prefix) // To make the end of the search, increment the final character ASCII by one. @@ -28,7 +28,7 @@ func IterateByteStringKeysByPrefix(tree avl.Tree, prefix string, cb avl.IterCbFn // Get a list of keys starting from the given prefix. Limit the // number of results to maxResults. // The prefix and keys are treated as byte strings, ignoring possible multi-byte Unicode runes. -func ListByteStringKeysByPrefix(tree avl.Tree, prefix string, maxResults int) []string { +func ListByteStringKeysByPrefix(tree avl.ITree, prefix string, maxResults int) []string { result := []string{} IterateByteStringKeysByPrefix(tree, prefix, func(key string, value interface{}) bool { result = append(result, key) diff --git a/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno b/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno index 1c7873e297a..5ecda41d1a6 100644 --- a/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno +++ b/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno @@ -10,7 +10,7 @@ import ( ) func main() { - tree := avl.Tree{} + tree := avl.NewTree() { // Empty tree. @@ -44,7 +44,7 @@ func main() { println("match: " + matches[0]) } - tree = avl.Tree{} + tree = avl.NewTree() tree.Set("a\xff", "") tree.Set("a\xff\xff", "") tree.Set("b", "") diff --git a/examples/gno.land/p/n2p5/mgroup/mgroup_test.gno b/examples/gno.land/p/n2p5/mgroup/mgroup_test.gno index 7ef0619188f..cd02db98683 100644 --- a/examples/gno.land/p/n2p5/mgroup/mgroup_test.gno +++ b/examples/gno.land/p/n2p5/mgroup/mgroup_test.gno @@ -297,13 +297,13 @@ func TestManagedGroup(t *testing.T) { if len(owners) != 3 { t.Errorf("expected 2, got %v", len(owners)) } - if owners[0] != u2.String() { + if owners[0] != u1.String() { t.Errorf("expected %v, got %v", u2, owners[0]) } if owners[1] != u3.String() { t.Errorf("expected %v, got %v", u3, owners[1]) } - if owners[2] != u1.String() { + if owners[2] != u2.String() { t.Errorf("expected %v, got %v", u3, owners[1]) } }) @@ -317,13 +317,13 @@ func TestManagedGroup(t *testing.T) { if len(members) != 3 { t.Errorf("expected 2, got %v", len(members)) } - if members[0] != u2.String() { + if members[0] != u1.String() { t.Errorf("expected %v, got %v", u2, members[0]) } if members[1] != u3.String() { t.Errorf("expected %v, got %v", u3, members[1]) } - if members[2] != u1.String() { + if members[2] != u2.String() { t.Errorf("expected %v, got %v", u3, members[1]) } }) diff --git a/examples/gno.land/r/demo/users/users.gno b/examples/gno.land/r/demo/users/users.gno index 8547a6e60e0..451afc7bf96 100644 --- a/examples/gno.land/r/demo/users/users.gno +++ b/examples/gno.land/r/demo/users/users.gno @@ -260,7 +260,7 @@ func GetUserByAddressOrName(input users.AddressOrName) *users.User { // Get a list of user names starting from the given prefix. Limit the // number of results to maxResults. (This can be used for a name search tool.) func ListUsersByPrefix(prefix string, maxResults int) []string { - return avlhelpers.ListByteStringKeysByPrefix(name2User, prefix, maxResults) + return avlhelpers.ListByteStringKeysByPrefix(&name2User, prefix, maxResults) } func Resolve(input users.AddressOrName) std.Address { diff --git a/examples/gno.land/r/x/jeronimo_render_proxy/example.gno b/examples/gno.land/r/x/jeronimo_render_proxy/example.gno new file mode 100644 index 00000000000..7b3da098232 --- /dev/null +++ b/examples/gno.land/r/x/jeronimo_render_proxy/example.gno @@ -0,0 +1,20 @@ +package example + +func Render(string) string { + return `# Render Proxy + +This example shows how proxying render calls can be used to allow updating realms to new +versions while keeping the same realm path. The idea is to have a simple "parent" realm +that only keeps track of the latest realm version and forwards all render calls to it. + +By only focusing on the 'Render()' function the proxy realm keeps its public functions +stable allowing each version to update their public functions and exposed types without +needing to also update the proxy realm. + +Any interaction or transaction must be sent to the latest, or target, realm version while +render calls are sent to the proxy realm. + +Each realm version registers itself on deployment as the latest available version, and +its allowed to do so because each versioned realm path shares the proxy realm path. +` +} diff --git a/examples/gno.land/r/x/jeronimo_render_proxy/gno.mod b/examples/gno.land/r/x/jeronimo_render_proxy/gno.mod new file mode 100644 index 00000000000..9236b28f5ad --- /dev/null +++ b/examples/gno.land/r/x/jeronimo_render_proxy/gno.mod @@ -0,0 +1 @@ +module gno.land/r/x/jeronimo_render_proxy diff --git a/examples/gno.land/r/x/jeronimo_render_proxy/home/home.gno b/examples/gno.land/r/x/jeronimo_render_proxy/home/home.gno new file mode 100644 index 00000000000..c73e99cc583 --- /dev/null +++ b/examples/gno.land/r/x/jeronimo_render_proxy/home/home.gno @@ -0,0 +1,52 @@ +package home + +import ( + "std" + "strings" +) + +// RenderFn defines the type for the render function of realms. +type RenderFn func(string) string + +var current = struct { + realmPath string + renderFn RenderFn +}{} + +// CurrentRealmPath returns the path of the realm that is currently registered. +func CurrentRealmPath() string { + return current.realmPath +} + +// Register registers a render function of a realm. +func Register(fn RenderFn) { + if fn == nil { + panic("render function must not be nil") + } + + proxyPath := std.CurrentRealm().PkgPath() + callerPath := std.PrevRealm().PkgPath() + if !strings.HasPrefix(callerPath, proxyPath+"/") { + panic("caller realm path must start with " + proxyPath) + } + + current.renderFn = fn + current.realmPath = callerPath +} + +// URL returns a URL that links to the proxy realm. +func URL(renderPath string) string { + url := "http://" + std.CurrentRealm().PkgPath() + if renderPath != "" { + url += ":" + renderPath + } + return url +} + +// Render renders the rendered Markdown of the realm that is currently registered. +func Render(path string) string { + if current.renderFn == nil { + panic("no realm has been registered") + } + return current.renderFn(path) +} diff --git a/examples/gno.land/r/x/jeronimo_render_proxy/home/v1/v1.gno b/examples/gno.land/r/x/jeronimo_render_proxy/home/v1/v1.gno new file mode 100644 index 00000000000..8698998577c --- /dev/null +++ b/examples/gno.land/r/x/jeronimo_render_proxy/home/v1/v1.gno @@ -0,0 +1,16 @@ +package v1 + +import "gno.land/r/x/jeronimo_render_proxy/home" + +func init() { + // Register the private render function with the render proxy + home.Register(render) +} + +func render(string) string { + return "Rendered by v1" +} + +func Render(string) string { + return "[Home](" + home.URL("") + ")" +} diff --git a/examples/gno.land/r/x/jeronimo_render_proxy/home/v1/z_filetest.gno b/examples/gno.land/r/x/jeronimo_render_proxy/home/v1/z_filetest.gno new file mode 100644 index 00000000000..cebe2aeb5ba --- /dev/null +++ b/examples/gno.land/r/x/jeronimo_render_proxy/home/v1/z_filetest.gno @@ -0,0 +1,10 @@ +package main + +import "gno.land/r/x/jeronimo_render_proxy/home/v1" + +func main() { + println(v1.Render("")) +} + +// Output: +// [Home](http://gno.land/r/x/jeronimo_render_proxy/home) diff --git a/examples/gno.land/r/x/jeronimo_render_proxy/home/v2/v2.gno b/examples/gno.land/r/x/jeronimo_render_proxy/home/v2/v2.gno new file mode 100644 index 00000000000..031f8568441 --- /dev/null +++ b/examples/gno.land/r/x/jeronimo_render_proxy/home/v2/v2.gno @@ -0,0 +1,16 @@ +package v2 + +import "gno.land/r/x/jeronimo_render_proxy/home" + +func init() { + // Register the private render function with the render proxy + home.Register(render) +} + +func render(string) string { + return "Rendered by v2" +} + +func Render(string) string { + return "[Home](" + home.URL("") + ")" +} diff --git a/examples/gno.land/r/x/jeronimo_render_proxy/home/v2/z_filetest.gno b/examples/gno.land/r/x/jeronimo_render_proxy/home/v2/z_filetest.gno new file mode 100644 index 00000000000..feff15533ee --- /dev/null +++ b/examples/gno.land/r/x/jeronimo_render_proxy/home/v2/z_filetest.gno @@ -0,0 +1,10 @@ +package main + +import "gno.land/r/x/jeronimo_render_proxy/home/v2" + +func main() { + println(v2.Render("")) +} + +// Output: +// [Home](http://gno.land/r/x/jeronimo_render_proxy/home) diff --git a/examples/gno.land/r/x/jeronimo_render_proxy/home/z_a_filetest.gno b/examples/gno.land/r/x/jeronimo_render_proxy/home/z_a_filetest.gno new file mode 100644 index 00000000000..c7d4d7febd2 --- /dev/null +++ b/examples/gno.land/r/x/jeronimo_render_proxy/home/z_a_filetest.gno @@ -0,0 +1,10 @@ +package main + +import "gno.land/r/x/jeronimo_render_proxy/home" + +func main() { + home.Render("") +} + +// Error: +// no realm has been registered diff --git a/examples/gno.land/r/x/jeronimo_render_proxy/home/z_b_filetest.gno b/examples/gno.land/r/x/jeronimo_render_proxy/home/z_b_filetest.gno new file mode 100644 index 00000000000..6ebdace67b4 --- /dev/null +++ b/examples/gno.land/r/x/jeronimo_render_proxy/home/z_b_filetest.gno @@ -0,0 +1,15 @@ +package main + +import ( + "gno.land/r/x/jeronimo_render_proxy/home" + _ "gno.land/r/x/jeronimo_render_proxy/home/v1" +) + +func main() { + println(home.CurrentRealmPath()) + println(home.Render("")) +} + +// Output: +// gno.land/r/x/jeronimo_render_proxy/home/v1 +// Rendered by v1 diff --git a/examples/gno.land/r/x/jeronimo_render_proxy/home/z_c_filetest.gno b/examples/gno.land/r/x/jeronimo_render_proxy/home/z_c_filetest.gno new file mode 100644 index 00000000000..f85b13bc5dd --- /dev/null +++ b/examples/gno.land/r/x/jeronimo_render_proxy/home/z_c_filetest.gno @@ -0,0 +1,16 @@ +package main + +import ( + "gno.land/r/x/jeronimo_render_proxy/home" + _ "gno.land/r/x/jeronimo_render_proxy/home/v1" + _ "gno.land/r/x/jeronimo_render_proxy/home/v2" +) + +func main() { + println(home.CurrentRealmPath()) + println(home.Render("")) +} + +// Output: +// gno.land/r/x/jeronimo_render_proxy/home/v2 +// Rendered by v2 diff --git a/gno.land/cmd/gnoland/integration_test.go b/gno.land/cmd/gnoland/integration_test.go deleted file mode 100644 index 37451df9704..00000000000 --- a/gno.land/cmd/gnoland/integration_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import ( - "testing" - - "github.com/gnolang/gno/gno.land/pkg/integration" -) - -func TestTestdata(t *testing.T) { - integration.RunGnolandTestscripts(t, "testdata") -} diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go index ccc3369766d..d7844d77b57 100644 --- a/gno.land/pkg/gnoland/genesis.go +++ b/gno.land/pkg/gnoland/genesis.go @@ -17,7 +17,7 @@ import ( "github.com/pelletier/go-toml" ) -const initGasPrice = "10ugnot/100gas" +const initGasPrice = "1ugnot/1000gas" // LoadGenesisBalancesFile loads genesis balances from the provided file path. func LoadGenesisBalancesFile(path string) ([]Balance, error) { diff --git a/gno.land/pkg/gnoweb/components/directory.gohtml b/gno.land/pkg/gnoweb/components/directory.gohtml index 4cdeff12a38..2254886f7af 100644 --- a/gno.land/pkg/gnoweb/components/directory.gohtml +++ b/gno.land/pkg/gnoweb/components/directory.gohtml @@ -1,15 +1,14 @@ {{ define "renderDir" }}
-
- +
{{ $pkgpath := .PkgPath }} -
-
+
+

{{ $pkgpath }}

-
+
Directory ¡ {{ .FileCounter }} Files
diff --git a/gno.land/pkg/gnoweb/components/help.gohtml b/gno.land/pkg/gnoweb/components/help.gohtml index 1ea8ba1927a..535cb56e9d6 100644 --- a/gno.land/pkg/gnoweb/components/help.gohtml +++ b/gno.land/pkg/gnoweb/components/help.gohtml @@ -89,7 +89,7 @@

Command

-
-
+
diff --git a/gno.land/pkg/gnoweb/components/source.gohtml b/gno.land/pkg/gnoweb/components/source.gohtml index 20e710ca29b..cb2430b504a 100644 --- a/gno.land/pkg/gnoweb/components/source.gohtml +++ b/gno.land/pkg/gnoweb/components/source.gohtml @@ -5,10 +5,10 @@

{{ .FileName }}

-
- {{ .FileSize }} ¡ {{ .FileLines }} lines -