diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7c134d9..07d3794 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,4 @@ -# Copyright the DMorph contributors. +# SPDX-FileCopyrightText: 2025 The DMorph contributors. # SPDX-License-Identifier: MPL-2.0 /.github/ @AlphaOne1 \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 7fefb0a..1dd502a 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2025 The Dmorph contributors. +# SPDX-License-Identifier: MPL-2.0 + # These are supported funding model platforms github: [AlphaOne1] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 3205926..c214d4e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,3 +1,7 @@ + + --- name: Bug report about: Create a report to help us improve @@ -14,8 +18,8 @@ A clear and concise description of what the bug is. Steps to reproduce the behavior: 1. Go to '...' -2. Click on '....' -3. Scroll down to '....' +2. Click on '...' +3. Scroll down to '...' 4. See error **Expected behavior** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index cc96547..6923bab 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,3 +1,7 @@ + + --- name: Feature request about: Suggest an idea for this project diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e2d3d9d..3000218 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,7 @@ + + ### All Submissions: * [ ] Have you followed the guidelines in our Contributing document? @@ -13,7 +17,7 @@ You can erase any parts of this template not applicable to your Pull Request. 1. [ ] Did you add tests for the new features that cover all major code paths? 2. [ ] Does your submission pass tests? -3. [ ] Have you lint your code locally before submission? +3. [ ] Have you linted your code locally before submission? **What is the new behavior (if this is a feature change)?** diff --git a/.github/dependabot.yml b/.github/dependabot.yml index aa08260..8452a70 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,4 +1,4 @@ -# Copyright the DMorph contributors. +# SPDX-FileCopyrightText: 2025 The DMorph contributors. # SPDX-License-Identifier: MPL-2.0 # # To get started with Dependabot version updates, you'll need to specify which diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f5a4974..1d92d97 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,10 +1,10 @@ -# Copyright the DMorph contributors. +# SPDX-FileCopyrightText: 2025 The DMorph contributors. # SPDX-License-Identifier: MPL-2.0 # # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # -# You may wish to alter this file to override the set of languages analyzed, +# You may wish to alter this file to override the set of languages analyzed # or to provide custom queries or build logic. # # ******** NOTE ******** @@ -22,7 +22,7 @@ on: schedule: - cron: '28 4 * * 2' -# Declare default permissions as read only. +# Declare default permissions as read-only. permissions: read-all jobs: @@ -82,7 +82,7 @@ jobs: # 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 + # 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. diff --git a/.github/workflows/compliance.yml b/.github/workflows/compliance.yml new file mode 100644 index 0000000..53b5cac --- /dev/null +++ b/.github/workflows/compliance.yml @@ -0,0 +1,153 @@ +# SPDX-FileCopyrightText: 2025 The DMorph contributors. +# SPDX-License-Identifier: MPL-2.0 + +name: Compliance Checks + +on: + push: + branches: + - master + pull_request: + branches: + - master + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + REUSE: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 1 + + - name: REUSE Compliance Check + uses: fsfe/reuse-action@bb774aa972c2a89ff34781233d275075cbddf542 #v5.0.0 + + CheckSignedOffCommit: + if: > + github.event_name == 'push' && + !startsWith(github.actor, 'dependabot') && + github.event.pusher.name != 'web-flow' && + github.event.pusher.name != 'github-actions[bot]' && + github.event.pusher.name != 'github-merge-queue[bot]' + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 0 + + - name: Determine pushed commits + id: range + run: | + set -euo pipefail + + # Use GitHub-provided SHAs to build the range for this push + BEFORE="${{ github.event.before }}" + AFTER="${{ github.sha }}" + + if [ "$BEFORE" = "0000000000000000000000000000000000000000" ] + then + # New branch or force push without previous SHA + git rev-list --no-merges "$AFTER" > shas.txt + else + git rev-list --no-merges "$BEFORE".."$AFTER" > shas.txt + fi + + - name: Check for Signed-off-by + run: | + set -euo pipefail + missing="" + + while read -r sha + do + [ -n "$sha" ] || continue + msg=`git log --format=%B -n 1 "$sha"` + + if ! printf '%s' "$msg" | grep -Eqi '^[[:space:]]*Signed[- ]off[- ]by:' + then + echo "Commit $sha missing Signed-off-by" + missing="true" + fi + done < shas.txt + + if [ "$missing" = "true" ] + then + echo "DCO check failed on push" + exit 1 + fi + + echo "All pushed commits are signed" + + CheckSignedOffPullRequest: + if: github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'bypass-dco') + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 0 + + - name: Get PR commits + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + gh --version + jq --version + + # Fetch all commits of the PR with pagination and extract SHAs + gh api -H "Accept: application/vnd.github+json" --paginate \ + repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/commits \ + | jq -r '.[].sha' > shas.txt + + - name: Check for Signed-off-by + run: | + set -euo pipefail + missing="" + + while read -r sha + do + [ -n "$sha" ] || continue + msg=`git log --format=%B -n 1 "$sha"` + + if ! printf '%s' "$msg" | grep -Eqi '^[[:space:]]*Signed[- ]off[- ]by:' + then + echo "Commit $sha missing Signed-off-by" + missing="true" + fi + done < shas.txt + + if [ "$missing" = "true" ] + then + echo "DCO check failed"; exit 1 + fi + + echo "All commits are signed" \ No newline at end of file diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index c5aa9c1..97a31b6 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -1,4 +1,4 @@ -# Copyright the DMorph contributors. +# SPDX-FileCopyrightText: 2025 The DMorph contributors. # SPDX-License-Identifier: MPL-2.0 # # Dependency Review Action diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..754b2db --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,113 @@ +# SPDX-FileCopyrightText: 2025 The DMorph contributors. +# SPDX-License-Identifier: MPL-2.0 + +name: Release + +on: + release: + types: + - published + +permissions: read-all + +concurrency: + group: release-${{ github.event.release.tag_name }} + cancel-in-progress: true + +jobs: + Build: + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 0 + ref: ${{ github.event.release.tag_name }} + + - name: Generate source archive + shell: bash + run: | + set -euo pipefail + + TAG=`echo "${{ github.event.release.tag_name }}" | sed 's/\//-/g'` + git archive \ + --format=tar.gz \ + --prefix="dmorph-src-${TAG}/" \ + --output="dmorph-src-${TAG}.tar.gz" \ + "${{ github.event.release.tag_name }}" + + - name: Upload Release (via GitHub CLI) + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + run: | + set -euo pipefail + gh release upload "${{ github.event.release.tag_name }}" dmorph-src-*.tar.gz --clobber + + ChecksumReleaseAssets: + needs: Build + runs-on: ubuntu-latest + name: Checksum Release Assets + outputs: + hashBase64File: ${{ steps.hashes.outputs.handle }} + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 1 + + - name: Download all release assets via GitHub CLI + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + mkdir -p release-assets + cd release-assets + # gets all assets of the release + gh release download "${{ github.event.release.tag_name }}" --clobber + echo "Downloaded assets:" + ls -lah + + - name: Generate Checksums + working-directory: release-assets + run: | + set -euo pipefail + + # Robustly hash all regular files in this directory, then base64 and write via tee. + LC_ALL=C find . -maxdepth 1 -type f -printf '%P\0' \ + | sort -z \ + | xargs -0 sha256sum -- \ + | base64 -w0 \ + > check.sha256 + + - name: Upload Checksums + id: hashes + uses: slsa-framework/slsa-github-generator/actions/generator/generic/create-base64-subjects-from-file@f7dd8c54c2067bafc12ca7a55595d5ee9b75204a # 2.1.0 + with: + path: release-assets/check.sha256 + + AssetProvenance: + permissions: + actions: read # Needed for detection of GitHub Actions environment. + id-token: write # Needed for provenance signing and ID + contents: write # Needed for release uploads + needs: ChecksumReleaseAssets + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 #must have semver! + with: + base64-subjects-as-file: | + ${{ needs.ChecksumReleaseAssets.outputs.hashBase64File }} + upload-assets: true + upload-tag-name: "${{ github.event.release.tag_name }}" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 3e6afbe..b1ffae1 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -1,7 +1,7 @@ -# Copyright the DMorph contributors. +# SPDX-FileCopyrightText: 2025 The DMorph contributors. # SPDX-License-Identifier: MPL-2.0 # -# This workflow uses actions that are not certified by GitHub. They are provided +# This workflow uses actions not certified by GitHub. They are provided # by a third-party and are governed by separate terms of service, privacy # policy, and support documentation. @@ -9,16 +9,15 @@ name: Scorecard supply-chain security on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection - branch_protection_rule: - - # To guarantee, Maintained check is occasionally updated. See + branch_protection_rule: {} + # To guarantee Maintained-check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - cron: '36 3 * * 2' push: branches: [ "master" ] -# Declare default permissions as read only. +# Declare default permissions as read-only. permissions: read-all jobs: @@ -26,7 +25,7 @@ jobs: name: Scorecard analysis runs-on: ubuntu-latest permissions: - # Needed to upload the results to code-scanning dashboard. + # Needed to upload the results to the code-scanning dashboard. security-events: write # Needed to publish results and get a badge (see publish_results below). id-token: write diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 0d254c4..434d143 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -1,4 +1,4 @@ -# Copyright the DMorph contributors. +# SPDX-FileCopyrightText: 2025 The DMorph contributors. # SPDX-License-Identifier: MPL-2.0 name: Security @@ -11,7 +11,7 @@ on: branches: - master -# Declare default permissions as read only. +# Declare default permissions as read-only. permissions: read-all jobs: @@ -42,11 +42,37 @@ jobs: with: sarif_file: 'trivy-results.sarif' + GolangciLint: + runs-on: ubuntu-latest + permissions: + security-events: write + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 1 + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 + with: + version: latest + args: --output.sarif.path=golangci-lint-results.sarif + + - name: Upload golangci-lint results to GitHub Security tab + uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + with: + sarif_file: golangci-lint-results.sarif + VulnerabilityCheck: strategy: matrix: go-version: - - "1.24" + - "stable" runs-on: ubuntu-latest permissions: security-events: write @@ -69,9 +95,11 @@ jobs: output-file: govulncheck-results.sarif - name: PrintSarif + id: PrintSarif run: | cat govulncheck-results.sarif - if [ grep results govulncheck-results.serif ] + + if grep results govulncheck-results.sarif then echo "hasResults=true" >> $GITHUB_OUTPUT else diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 99814c1..34bff57 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -# Copyright the DMorph contributors. +# SPDX-FileCopyrightText: 2025 The DMorph contributors. # SPDX-License-Identifier: MPL-2.0 name: Tests @@ -11,11 +11,15 @@ on: branches: - master -# Declare default permissions as read only. +# Declare default permissions as read-only. permissions: read-all jobs: FormatCheck: + strategy: + matrix: + go-version: + - "stable" runs-on: ubuntu-latest steps: - name: Harden Runner @@ -23,21 +27,24 @@ jobs: with: egress-policy: audit + - name: Install Go + uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + with: + go-version: ${{matrix.go-version}} + - name: Checkout uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 1 - - name: Install Go - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 - with: - go-version-file: go.mod - check-latest: true - - name: FormatCheck run: if [ `go fmt ./... | wc -l` -gt 0 ] ; then echo "Found unformatted code" ; exit 1 ; else exit 0 ; fi StaticCheck: + strategy: + matrix: + go-version: + - "stable" runs-on: ubuntu-latest steps: - name: Harden Runner @@ -45,46 +52,51 @@ jobs: with: egress-policy: audit + - name: Install Go + uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + with: + go-version: ${{matrix.go-version}} + - name: Checkout uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 1 - - name: Install Go - id: install_go - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 - with: - go-version-file: go.mod - check-latest: true - - name: StaticCheck uses: dominikh/staticcheck-action@024238d2898c874f26d723e7d0ff4308c35589a2 # v1.4.0 with: version: latest install-go: false - cache-key: ${{ steps.install_go.output.go-version }} + cache-key: ${{matrix.go-version}} Test: - runs-on: ubuntu-latest + strategy: + matrix: + go-version: + - "stable" + platform: + #- macos-latest + - ubuntu-latest + #- windows-latest + runs-on: ${{matrix.platform}} steps: - name: Harden Runner uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: egress-policy: audit + - name: Install Go + uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + with: + go-version: ${{matrix.go-version}} + - name: Checkout uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 1 - - name: Install Go - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 - with: - go-version-file: go.mod - check-latest: true - - name: Test - run: go run gotest.tools/gotestsum@latest --junitfile junit.xml -- -v `go list ./...` --covermode=count --coverpkg=./... --coverprofile=coverage.txt + run: go tool gotestsum --junitfile junit.xml -- -race -v `go list ./...` --covermode=atomic --coverpkg=./... --coverprofile=coverage.txt - name: Upload test results to Codecov if: ${{ !cancelled() }} @@ -102,31 +114,40 @@ jobs: # This action is mainly composed of snippets of github.com/jidicula/go-fuzz-action # FuzzTest: -# runs-on: ubuntu-latest +# strategy: +# matrix: +# go-version: +# - "stable" +# platform: +# #- macos-latest +# - ubuntu-latest +# #- windows-latest +# packages: +# - ./ +# runs-on: ${{ matrix.platform }} # steps: # - name: Harden Runner # uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 # with: # egress-policy: audit # +# - name: Install Go +# uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 +# with: +# go-version: ${{matrix.go-version}} +# # - name: Checkout # uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 # with: # fetch-depth: 1 # -# - name: Install Go -# uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 -# with: -# go-version-file: go.mod -# check-latest: true -# # - name: Run Fuzz Test # shell: bash # run: go test ${{ matrix.packages }} -fuzz="Fuzz" -fuzztime="30s" -fuzzminimizetime="10s" # # - name: Upload fuzz failure seed corpus as run artifact # if: failure() -# uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 +# uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 # with: # name: testdata # path: testdata diff --git a/.gitignore b/.gitignore index 3747187..ff7b2ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -# Copyright the DMorph contributors. +# SPDX-FileCopyrightText: 2025 The DMorph contributors. # SPDX-License-Identifier: MPL-2.0 -.idea \ No newline at end of file +.idea +.DS_Store \ No newline at end of file diff --git a/.golangci.yaml b/.golangci.yaml index 916b64e..a3c54af 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,4 +1,4 @@ -# Copyright the dmorph contributors. +# SPDX-FileCopyrightText: 2025 The DMorph contributors. # SPDX-License-Identifier: MPL-2.0 # Configuration file for golangci-lint @@ -71,9 +71,6 @@ linters: ignored-numbers: - "2" - paralleltest: - ignore-missing: true - perfsprint: errorf: false diff --git a/.markdownlint.json.license b/.markdownlint.json.license new file mode 100644 index 0000000..4d1ff0d --- /dev/null +++ b/.markdownlint.json.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2025 The DMorph contributors. +SPDX-License-Identifier: MPL-2.0 diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..6c9a57e --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,8 @@ + + +Authors +======= + +- @AlphaOne1 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 580c4fd..555aa8c 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,7 +1,11 @@ -dmorph Community Code of Conduct + + +DMorph Community Code of Conduct ================================= -Like the technical community as a whole, the *dmorph* team and community is +Like the technical community as a whole, the *DMorph* team and community is made up of a mixture of professionals and volunteers from all over the world, working on every aspect of the mission—including mentorship, teaching and connecting people. @@ -31,9 +35,9 @@ mailing list, and other forums such as Skype, Google+ Hangouts, etc. no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It's important to remember that a community where people feel - uncomfortable or threatened is not productive. Members of the *dmorph* + uncomfortable or threatened is not productive. Members of the *DMorph* community should be respectful when dealing with other members as well as with - people outside the *dmorph* community and with user groups/conferences, + people outside the *DMorph* community and with user groups/conferences, user group/conference organizers. * __Be careful in the words that you choose.__ Remember that sexist, racist, and @@ -43,14 +47,14 @@ mailing list, and other forums such as Skype, Google+ Hangouts, etc. appropriate for the community. __When we disagree, we try to understand why.__ Disagreements, both social and -technical, happen all the time and *dmorph* is no exception. It is important +technical, happen all the time and *DMorph* is no exception. It is important that we resolve disagreements and differing views constructively. Remember that -we're different. The strength of *dmorph* comes from its varied community, +we're different. The strength of *DMorph* comes from its varied community, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint -doesn't mean that they're wrong. Don't forget that it is human to err and -blaming each other doesn't get us anywhere, rather offer to help resolving -issues and to help learn from mistakes. +doesn't mean that they're wrong. Don't forget that it is human to make errors, +and blaming each other doesn't get us anywhere. Rather, offer to help resolve +issues and learn from mistakes. *Original text courtesy of the [Speak Up! project](http://web.archive.org/web/20141109123859/http://speakup.io/coc.html).* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c6dd3f8..25ec50b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,7 @@ + + Contributing ============ @@ -17,7 +21,7 @@ request that contributors create to first discuss any new ideas. Your ideas and suggestions are welcome! Please ensure that the tests are passing when submitting a pull request. If -you're adding new features to ActiveAdmin, please include tests. +you're adding new features or bugfixes to *DMorph*, please include tests. Where do I go from here? @@ -41,14 +45,14 @@ Make sure you're using a recent Go version. You can run the test suite from the base folder using the following command: ```bash -go test ./... +go test -race ./... ``` ### Implement your fix or feature At this point, you're ready to make your changes. Feel free to ask for help. -Be sure to have run the go fmt tool, to have a unified code style: +Be sure to have run the `go fmt` tool to have a unified code style: ```bash go fmt ./... @@ -57,11 +61,11 @@ go fmt ./... ### Test your feature -After you implemented your feature, add tests that cover all major code paths. A -test coverage of 100% is not always possible. We acknowledge, that there are hard -to trigger conditions, that you might check for, but are not producible in a test -suite, but aim for the best. At least every code path of normal, input triggered -use, should be covered. +After implementing your feature, add tests that cover all major code paths. A +test coverage of 100% is not always possible. We acknowledge that there are hard +to trigger conditions that you might check for, but are not producible in a test +suite, but aim for the best. At least every code path of normal, input-triggered +use should be covered. ### Document your feature @@ -70,14 +74,14 @@ All the good intentions go to waste, if nobody can enjoy the fruits of this labo due to non-existent (or bad, or wrong) documentation. Please take care that you include: -- a course description of your nea feature +- a concise description of your new feature - generate new or update (in case) the existing examples -- update the CHANGELOG.md +- update CHANGELOG.md -The CHANGELOG document contains the changes of the next major contains all the -changes of the current major version since x.0.0. On major release, the CHANGELOG -can be emptied as the older changes are still visible in the history of the version -control system. +The CHANGELOG document contains the changes of the next release and all the +changes of the current major version since x.0.0. On a major release, the +CHANGELOG can be emptied as the older changes are still visible in the history +of the version control system. ### Create a Pull Request @@ -86,19 +90,46 @@ At this point, if your changes look good and tests are passing, you are ready to create a pull request. GitHub Actions will run the test suite against the latest Go version. There are -tests that most likely did not run in the developers machine (CodeQL, Trivy). These -tests may produce warnings. Take those warnings serious even if they seem harmless. -Too many harmless warnings could possibly overlay really serious ones, so all -warnings are to be resolved. +tests that most likely did not run on the developers machine (CodeQL, Trivy). +These tests may produce warnings. Take those warnings seriously even if they +seem harmless. Too many harmless warnings could possibly overlay really serious +ones, so all warnings are to be resolved. Merging a PR (maintainers only) ------------------------------- -A Pull Request can only be merged into master by a maintainer if: +A maintainer can only merge a Pull Request into master if: - CI is passing, - approved by another maintainer - and is up to date with the default branch. -Any maintainer is allowed to merge a PR if all of these conditions ae met. +In addition to these automatic checks, the following conditions have to be met: + +- Changelog: The changelog has the sections with the changes of the past releases, these + are immutable less for corrections. There is at least one section explicating the next + release. All visible changes have to be included in the changelog. Further security + fixes have to be included here. + +Any maintainer is allowed to merge a PR if all of these conditions have been met. + + +Developer Certificate of Origin (DCO) +------------------------------------- + +This project enforces the [Developer Certificate of Origin (DCO)](https://developercertificate.org) +(a copy is included in [DCO.txt](DCO.txt)). +Every commit must be signed off, i.e., the commit message contains a line like: + +Signed-off-by: Your Name + +Use `git commit -s` to add this automatically (this DCO “Signed-off-by” is a +legal attestation and is different from cryptographic commit signing such as +GPG/SSH). Pull requests and pushes without proper sign-offs will fail the +compliance checks. You can alias `git commit` command to always include the +flag as follows: + +```shell +git config --global alias.ci 'commit -s' +``` diff --git a/DCO.txt b/DCO.txt new file mode 100644 index 0000000..d616011 --- /dev/null +++ b/DCO.txt @@ -0,0 +1,34 @@ +SPDX-FileCopyrightText: Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +SPDX-License-Identifier: LicenseRef-DCO + +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I have the right + to submit it under the open source license indicated in the file; or + +(b) The contribution is based upon previous work that, to the best of my + knowledge, is covered under an appropriate open source license and I have + the right under that license to submit that work with modifications, whether + created in whole or in part by me, under the same open source license + (unless I am permitted to submit under a different license), as indicated in + the file; or + +(c) The contribution was provided directly to me by some other person who + certified (a), (b) or (c) and I have not modified it. + +(d) I understand and agree that this project and the contribution are public and + that a record of the contribution (including all personal information I + submit with it, including my sign-off) is maintained indefinitely and may be + redistributed consistent with this project or the open source license(s) + involved. diff --git a/GOVERNANCE.md b/GOVERNANCE.md new file mode 100644 index 0000000..2107676 --- /dev/null +++ b/GOVERNANCE.md @@ -0,0 +1,60 @@ + + +*dmorph* Project Governance +============================= + +The *dmorph* project uses a governance model commonly described as Benevolent +Dictator For Life (BDFL). This document outlines our implementation of this +model. + + +Terms +----- + +*user*: anyone who interacts with the project. + +*contributor*: anyone who has contributed to the project in any way and to any +degree. Contributions may include code, documentation, reviews, manual testing, +etc. All contributors are listed in the project's metadata +(e.g. [AUTHORS.md](AUTHORS.md)). + +*core contributor*: anyone who has contributed repeatedly and significantly to +the project. Core contributors are recognized by GitHub “Member” badges. + +*Benevolent Dictator For Life (BDFL)*: the person who makes decisions when +consensus cannot be reached. The project’s current BDFL is Alexander Adam +(@AlphaOne1). + + +Decision Making Process +----------------------- + +Contributors try to reach consensus via discussion. When consensus cannot be +reached in a reasonable time, the BDFL takes the following steps: + + 1. Creates an issue in the project’s GitHub repository labeled governance with + a suitable title. + + 2. Waits at least 48 hours for contributors to add comments to the issue. + + 3. Adds a comment with the final decision and closes the issue. + + +Changing Governance +------------------- + +The BDFL may choose a replacement at any point and cede authority to them. If +this happens, the decision must be announced and recorded in the manner +described above for decisions in the absence of consensus. + +The new BDFL must clearly state that they accept their new responsibilities. + +The time at which the new BDFL assumes their responsibilities must be clearly +stated. + +The BDFL may alternatively decide to move to a Steering Committee governance +model, in which case this document must be replaced with a new description of +roles and responsibilities. diff --git a/LICENSES/CC-BY-4.0.txt b/LICENSES/CC-BY-4.0.txt new file mode 100644 index 0000000..4ea99c2 --- /dev/null +++ b/LICENSES/CC-BY-4.0.txt @@ -0,0 +1,395 @@ +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/LICENSES/LicenseRef-DCO.txt b/LICENSES/LicenseRef-DCO.txt new file mode 100644 index 0000000..ad8158f --- /dev/null +++ b/LICENSES/LicenseRef-DCO.txt @@ -0,0 +1,31 @@ +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I have the right + to submit it under the open source license indicated in the file; or + +(b) The contribution is based upon previous work that, to the best of my + knowledge, is covered under an appropriate open source license and I have + the right under that license to submit that work with modifications, whether + created in whole or in part by me, under the same open source license + (unless I am permitted to submit under a different license), as indicated in + the file; or + +(c) The contribution was provided directly to me by some other person who + certified (a), (b) or (c) and I have not modified it. + +(d) I understand and agree that this project and the contribution are public and + that a record of the contribution (including all personal information I + submit with it, including my sign-off) is maintained indefinitely and may be + redistributed consistent with this project or the open source license(s) + involved. \ No newline at end of file diff --git a/LICENSES/MPL-2.0.txt b/LICENSES/MPL-2.0.txt new file mode 100644 index 0000000..a612ad9 --- /dev/null +++ b/LICENSES/MPL-2.0.txt @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md index aa7abaa..26a2519 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ + +

Logo
@@ -49,6 +53,12 @@ OpenSSF Scorecard + + REUSE compliance + diff --git a/SECURITY.md b/SECURITY.md index a17526e..c94c443 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,3 +1,7 @@ + + Security Policy =============== diff --git a/dialect_csvq.go b/dialect_csvq.go index 4d40d19..618fe82 100644 --- a/dialect_csvq.go +++ b/dialect_csvq.go @@ -1,4 +1,4 @@ -// Copyright the DMorph contributors. +// SPDX-FileCopyrightText: 2025 The DMorph contributors. // SPDX-License-Identifier: MPL-2.0 package dmorph diff --git a/dialect_db2.go b/dialect_db2.go index de181fc..49a1459 100644 --- a/dialect_db2.go +++ b/dialect_db2.go @@ -1,4 +1,4 @@ -// Copyright the DMorph contributors. +// SPDX-FileCopyrightText: 2025 The DMorph contributors. // SPDX-License-Identifier: MPL-2.0 package dmorph diff --git a/dialect_mssql.go b/dialect_mssql.go index 6d1b2a1..0650679 100644 --- a/dialect_mssql.go +++ b/dialect_mssql.go @@ -1,4 +1,4 @@ -// Copyright the DMorph contributors. +// SPDX-FileCopyrightText: 2025 The DMorph contributors. // SPDX-License-Identifier: MPL-2.0 package dmorph diff --git a/dialect_mysql.go b/dialect_mysql.go index 53e44c0..085b1ab 100644 --- a/dialect_mysql.go +++ b/dialect_mysql.go @@ -1,4 +1,4 @@ -// Copyright the DMorph contributors. +// SPDX-FileCopyrightText: 2025 The DMorph contributors. // SPDX-License-Identifier: MPL-2.0 package dmorph diff --git a/dialect_oracle.go b/dialect_oracle.go index 1fafd6f..8b1de71 100644 --- a/dialect_oracle.go +++ b/dialect_oracle.go @@ -1,4 +1,4 @@ -// Copyright the DMorph contributors. +// SPDX-FileCopyrightText: 2025 The DMorph contributors. // SPDX-License-Identifier: MPL-2.0 package dmorph diff --git a/dialect_postgres.go b/dialect_postgres.go index f577e14..a43e87a 100644 --- a/dialect_postgres.go +++ b/dialect_postgres.go @@ -1,4 +1,4 @@ -// Copyright the DMorph contributors. +// SPDX-FileCopyrightText: 2025 The DMorph contributors. // SPDX-License-Identifier: MPL-2.0 package dmorph diff --git a/dialect_sqlite.go b/dialect_sqlite.go index f37d469..8245bd6 100644 --- a/dialect_sqlite.go +++ b/dialect_sqlite.go @@ -1,4 +1,4 @@ -// Copyright the DMorph contributors. +// SPDX-FileCopyrightText: 2025 The DMorph contributors. // SPDX-License-Identifier: MPL-2.0 package dmorph diff --git a/dialects.go b/dialects.go index b65c0ab..4a1d302 100644 --- a/dialects.go +++ b/dialects.go @@ -1,4 +1,4 @@ -// Copyright the DMorph contributors. +// SPDX-FileCopyrightText: 2025 The DMorph contributors. // SPDX-License-Identifier: MPL-2.0 package dmorph @@ -24,7 +24,7 @@ func (b BaseDialect) EnsureMigrationTableExists(ctx context.Context, db *sql.DB, tx, err := db.BeginTx(ctx, nil) if err != nil { - return err + return wrapIfError("could not start transaction", err) } // Safety net for unexpected panics @@ -58,7 +58,7 @@ func (b BaseDialect) AppliedMigrations(ctx context.Context, db *sql.DB, tableNam rows, rowsErr := db.QueryContext(ctx, fmt.Sprintf(b.AppliedTemplate, tableName)) if rowsErr != nil { - return nil, rowsErr + return nil, wrapIfError("could not get applied migrations", rowsErr) } defer func() { _ = rows.Close() }() @@ -81,5 +81,5 @@ func (b BaseDialect) RegisterMigration(ctx context.Context, tx *sql.Tx, id strin _, err := tx.ExecContext(ctx, fmt.Sprintf(b.RegisterTemplate, tableName), sql.Named("id", id)) - return err + return wrapIfError("could not register migration", err) } diff --git a/dialects_test.go b/dialects_test.go index 2055759..8dfb756 100644 --- a/dialects_test.go +++ b/dialects_test.go @@ -1,9 +1,10 @@ -// Copyright the DMorph contributors. +// SPDX-FileCopyrightText: 2025 The DMorph contributors. // SPDX-License-Identifier: MPL-2.0 package dmorph_test import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -15,6 +16,8 @@ import ( // TestDialectStatements verifies that each database dialect has valid and // sufficiently complete SQL statement templates. func TestDialectStatements(t *testing.T) { + t.Parallel() + // we cannot run tests against all databases, but at least we can test // that the statements for the databases are somehow filled tests := []struct { @@ -30,45 +33,53 @@ func TestDialectStatements(t *testing.T) { {name: "SQLite", caller: dmorph.DialectSQLite}, } - for k, v := range tests { - d := v.caller() - - if len(d.CreateTemplate) < 10 { - t.Errorf("%v: create template is too short for %v", k, v.name) - } - assert.Contains(t, d.CreateTemplate, "%s", - "no table name placeholder in create template for", v.name) - - if len(d.AppliedTemplate) < 10 { - t.Errorf("%v: applied template is too short for %v", k, v.name) - } - assert.Contains(t, d.AppliedTemplate, "%s", - "no table name placeholder in applied template for", v.name) - - if len(d.RegisterTemplate) < 10 { - t.Errorf("%v: register template is too short for %v", k, v.name) - } - assert.Contains(t, d.RegisterTemplate, "%s", - "no table name placeholder in register template for", v.name) + for k, test := range tests { + t.Run(fmt.Sprintf("TestDialectStatements-%d", k), func(t *testing.T) { + t.Parallel() + + dialect := test.caller() + + if len(dialect.CreateTemplate) < 10 { + t.Errorf("create template is too short for %v", test.name) + } + assert.Contains(t, dialect.CreateTemplate, "%s", + "no table name placeholder in create template for", test.name) + + if len(dialect.AppliedTemplate) < 10 { + t.Errorf("applied template is too short for %v", test.name) + } + assert.Contains(t, dialect.AppliedTemplate, "%s", + "no table name placeholder in applied template for", test.name) + + if len(dialect.RegisterTemplate) < 10 { + t.Errorf("register template is too short for %v", test.name) + } + assert.Contains(t, dialect.RegisterTemplate, "%s", + "no table name placeholder in register template for", test.name) + }) } } // TestCallsOnClosedDB verifies that methods fail as expected when called on a closed database connection. func TestCallsOnClosedDB(t *testing.T) { + t.Parallel() + db := openTempSQLite(t) require.NoError(t, db.Close()) - assert.Error(t, + require.Error(t, dmorph.DialectSQLite().EnsureMigrationTableExists(t.Context(), db, "irrelevant"), "expected error on closed database") _, err := dmorph.DialectSQLite().AppliedMigrations(t.Context(), db, "irrelevant") - assert.Error(t, err, "expected error on closed database") + require.Error(t, err, "expected error on closed database") } // TestEnsureMigrationTableExistsSQLError tests the EnsureMigrationTableExists function // for handling SQL errors during execution. func TestEnsureMigrationTableExistsSQLError(t *testing.T) { + t.Parallel() + dialect := dmorph.BaseDialect{ CreateTemplate: ` CRATE TABLE test ( @@ -85,6 +96,8 @@ func TestEnsureMigrationTableExistsSQLError(t *testing.T) { // TestEnsureMigrationTableExistsCommitError tests the behavior of EnsureMigrationTableExists // when a commit error occurs. func TestEnsureMigrationTableExistsCommitError(t *testing.T) { + t.Parallel() + dialect := dmorph.BaseDialect{ CreateTemplate: ` CREATE TABLE t0 ( diff --git a/dmorph_logo.svg b/dmorph_logo.svg index ce9a890..586d9d7 100644 --- a/dmorph_logo.svg +++ b/dmorph_logo.svg @@ -1,4 +1,7 @@ + diff --git a/exports_internal_test.go b/exports_internal_test.go index 8893598..76a29e8 100644 --- a/exports_internal_test.go +++ b/exports_internal_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2025 The DMorph contributors. +// SPDX-License-Identifier: MPL-2.0 + package dmorph import ( @@ -7,10 +10,12 @@ import ( // The exported names in this file are only used for internal testing and are not part of the public API. +//nolint:gochecknoglobals // these are used for testing and not visible or used otherwise var ( TapplyStepsStream = applyStepsStream TmigrationFromFileFS = migrationFromFileFS TmigrationOrder = migrationOrder + TwrapIfError = wrapIfError ) func (m *Morpher) TapplyMigrations(ctx context.Context, db *sql.DB, lastMigration string) error { diff --git a/file_migration.go b/file_migration.go index 69f84f4..a2e400a 100644 --- a/file_migration.go +++ b/file_migration.go @@ -1,4 +1,4 @@ -// Copyright the DMorph contributors. +// SPDX-FileCopyrightText: 2025 The DMorph contributors. // SPDX-License-Identifier: MPL-2.0 package dmorph @@ -13,6 +13,7 @@ import ( "io/fs" "log/slog" "os" + "path/filepath" "strings" ) @@ -39,10 +40,10 @@ func WithMigrationFromFile(name string) MorphOption { morpher.Migrations = append(morpher.Migrations, FileMigration{ Name: name, migrationFunc: func(ctx context.Context, tx *sql.Tx, migration string) error { - m, mErr := os.Open(migration) + m, mErr := os.Open(filepath.Clean(migration)) if mErr != nil { - return mErr + return wrapIfError("could not open file "+migration, mErr) } defer func() { _ = m.Close() }() @@ -82,7 +83,7 @@ func WithMigrationsFromFS(d fs.FS) MorphOption { } } - return err + return wrapIfError("could not read directory", err) } } @@ -95,7 +96,7 @@ func migrationFromFileFS(name string, dir fs.FS, log *slog.Logger) FileMigration m, mErr := dir.Open(migration) if mErr != nil { - return mErr + return wrapIfError("could not open file migration", mErr) } defer func() { _ = m.Close() }() @@ -165,5 +166,5 @@ func applyStepsStream(ctx context.Context, tx *sql.Tx, r io.Reader, migrationID } } - return scanner.Err() + return wrapIfError("scanner error", scanner.Err()) } diff --git a/file_migration_test.go b/file_migration_test.go index e9de046..0b13e6a 100644 --- a/file_migration_test.go +++ b/file_migration_test.go @@ -1,4 +1,4 @@ -// Copyright the DMorph contributors. +// SPDX-FileCopyrightText: 2025 The DMorph contributors. // SPDX-License-Identifier: MPL-2.0 package dmorph_test @@ -16,6 +16,8 @@ import ( ) func TestWithMigrationFromFile(t *testing.T) { + t.Parallel() + db := openTempSQLite(t) runErr := dmorph.Run(t.Context(), @@ -27,6 +29,8 @@ func TestWithMigrationFromFile(t *testing.T) { } func TestWithMigrationFromFileError(t *testing.T) { + t.Parallel() + db := openTempSQLite(t) runErr := dmorph.Run(t.Context(), @@ -41,6 +45,8 @@ func TestWithMigrationFromFileError(t *testing.T) { // TestMigrationFromFileFSError validates that migrationFromFileFS returns an error // when the specified file does not exist. func TestMigrationFromFileFSError(t *testing.T) { + t.Parallel() + dir := os.DirFS("testData") mig := dmorph.TmigrationFromFileFS("nonexistent", dir, slog.Default()) @@ -52,6 +58,8 @@ func TestMigrationFromFileFSError(t *testing.T) { // TestApplyStepsStreamError tests error handling in applyStepsStream. func TestApplyStepsStreamError(t *testing.T) { + t.Parallel() + db := openTempSQLite(t) buf := bytes.Buffer{} @@ -76,7 +84,7 @@ func TestApplyStepsStreamError(t *testing.T) { err = dmorph.TapplyStepsStream(t.Context(), tx, &buf, "test", slog.Default()) - assert.Error(t, err, "expected error") + require.Error(t, err, "expected error") _ = tx.Rollback() } diff --git a/go.mod b/go.mod index 4278b66..caa24f1 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ -// Copyright the DMorph contributors. +// SPDX-FileCopyrightText: 2025 The DMorph contributors. // SPDX-License-Identifier: MPL-2.0 module github.com/AlphaOne1/dmorph -go 1.24.0 +go 1.25 require ( github.com/stretchr/testify v1.11.1 @@ -11,17 +11,31 @@ require ( ) require ( + github.com/bitfield/gotestdox v0.2.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dnephin/pflag v1.0.7 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect - golang.org/x/sys v0.34.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/term v0.35.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/tools v0.36.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/gotestsum v1.13.0 // indirect modernc.org/libc v1.66.3 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect ) + +tool gotest.tools/gotestsum diff --git a/go.sum b/go.sum index 1cfdb36..4f6a72e 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,26 @@ +github.com/bitfield/gotestdox v0.2.2 h1:x6RcPAbBbErKLnapz1QeAlf3ospg8efBsedU93CDsnE= +github.com/bitfield/gotestdox v0.2.2/go.mod h1:D+gwtS0urjBrzguAkTM2wodsTQYFHdpx8eqRJ3N+9pY= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk= +github.com/dnephin/pflag v1.0.7/go.mod h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= @@ -14,23 +29,34 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= -golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= -golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= -golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/gotestsum v1.13.0 h1:+Lh454O9mu9AMG1APV4o0y7oDYKyik/3kBOiCqiEpRo= +gotest.tools/gotestsum v1.13.0/go.mod h1:7f0NS5hFb0dWr4NtcsAsF0y1kzjEFfAil0HiBQJE03Q= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= diff --git a/go.sum.license b/go.sum.license new file mode 100644 index 0000000..4d1ff0d --- /dev/null +++ b/go.sum.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2025 The DMorph contributors. +SPDX-License-Identifier: MPL-2.0 diff --git a/migration.go b/migration.go index 523d076..3139f2c 100644 --- a/migration.go +++ b/migration.go @@ -1,6 +1,7 @@ -// Copyright the DMorph contributors. +// SPDX-FileCopyrightText: 2025 The DMorph contributors. // SPDX-License-Identifier: MPL-2.0 +// Package dmorph provides a simple database migration framework. package dmorph import ( @@ -131,22 +132,22 @@ func WithTableName(tableName string) func(*Morpher) error { // It ensures that the newly created Morpher has migrations and a database dialect configured. // If no migration table name is given, the default MigrationTableName is used instead. func NewMorpher(options ...MorphOption) (*Morpher, error) { - m := &Morpher{ + morpher := &Morpher{ TableName: MigrationTableName, Log: slog.Default(), } for _, option := range options { - if err := option(m); err != nil { + if err := option(morpher); err != nil { return nil, err } } - if validErr := m.IsValid(); validErr != nil { + if validErr := morpher.IsValid(); validErr != nil { return nil, validErr } - return m, nil + return morpher, nil } // IsValid checks if the Morpher contains all the required information to run. diff --git a/migration_test.go b/migration_test.go index 8bc25bb..5ea76bb 100644 --- a/migration_test.go +++ b/migration_test.go @@ -1,4 +1,4 @@ -// Copyright the DMorph contributors. +// SPDX-FileCopyrightText: 2025 The DMorph contributors. // SPDX-License-Identifier: MPL-2.0 package dmorph_test @@ -7,6 +7,7 @@ import ( "context" "database/sql" "embed" + "fmt" "io/fs" "log/slog" "os" @@ -29,7 +30,7 @@ func prepareDB() (string, error) { dbFile, dbFileErr := os.CreateTemp("", "") if dbFileErr != nil { - return "", dbFileErr + return "", dmorph.TwrapIfError("could not create temporary db file", dbFileErr) //nolint:wrapcheck } result = dbFile.Name() @@ -55,6 +56,8 @@ func openTempSQLite(t *testing.T) *sql.DB { // TestMigration tests the happy flow. func TestMigration(t *testing.T) { + t.Parallel() + db := openTempSQLite(t) migrationsDir, migrationsDirErr := fs.Sub(testMigrationsDir, "testData") @@ -71,6 +74,8 @@ func TestMigration(t *testing.T) { // TestMigrationUpdate tests the happy flow of updating on existing migrations. func TestMigrationUpdate(t *testing.T) { + t.Parallel() + db := openTempSQLite(t) migrationsDir, migrationsDirErr := fs.Sub(testMigrationsDir, "testData") @@ -98,11 +103,13 @@ func (m TestMigrationImpl) Key() string { return "TestMigration" } func (m TestMigrationImpl) Migrate(ctx context.Context, tx *sql.Tx) error { _, err := tx.ExecContext(ctx, "CREATE TABLE t0 (id INTEGER PRIMARY KEY)") - return err + return dmorph.TwrapIfError("could not migrate", err) //nolint:wrapcheck } // TestWithMigrations tests the adding of migrations using WithMigrations. func TestWithMigrations(t *testing.T) { + t.Parallel() + db := openTempSQLite(t) runErr := dmorph.Run(t.Context(), @@ -116,6 +123,8 @@ func TestWithMigrations(t *testing.T) { // TestMigrationUnableToCreateMorpher tests to use the Run function without any // useful parameter. func TestMigrationUnableToCreateMorpher(t *testing.T) { + t.Parallel() + runErr := dmorph.Run(t.Context(), nil) assert.Error(t, runErr, "morpher should not have run") @@ -123,6 +132,8 @@ func TestMigrationUnableToCreateMorpher(t *testing.T) { // TestMigrationTooOld tests what happens if the applied migrations are too old. func TestMigrationTooOld(t *testing.T) { + t.Parallel() + db := openTempSQLite(t) migrationsDir, migrationsDirErr := fs.Sub(testMigrationsDir, "testData") @@ -146,6 +157,8 @@ func TestMigrationTooOld(t *testing.T) { // TestMigrationUnrelated0 tests what happens if the applied migrations are unrelated to existing ones. func TestMigrationUnrelated0(t *testing.T) { + t.Parallel() + db := openTempSQLite(t) migrationsDir, migrationsDirErr := fs.Sub(testMigrationsDir, "testData") @@ -169,6 +182,8 @@ func TestMigrationUnrelated0(t *testing.T) { // TestMigrationUnrelated1 tests what happens if the applied migrations are unrelated to existing ones. func TestMigrationUnrelated1(t *testing.T) { + t.Parallel() + db := openTempSQLite(t) migrationsDir, migrationsDirErr := fs.Sub(testMigrationsDir, "testData") @@ -193,6 +208,8 @@ func TestMigrationUnrelated1(t *testing.T) { // TestMigrationAppliedUnordered tests the case, that somehow the migrations in the // database are registered not in the order of their keys. func TestMigrationAppliedUnordered(t *testing.T) { + t.Parallel() + db := openTempSQLite(t) migrationsDir, migrationsDirErr := fs.Sub(testMigrationsDir, "testData") @@ -221,6 +238,8 @@ func TestMigrationAppliedUnordered(t *testing.T) { // TestMigrationOrder checks that the migrations ordering function works as expected. func TestMigrationOrder(t *testing.T) { + t.Parallel() + tests := []struct { m0 dmorph.Migration m1 dmorph.Migration @@ -244,14 +263,20 @@ func TestMigrationOrder(t *testing.T) { } for k, v := range tests { - res := dmorph.TmigrationOrder(v.m0, v.m1) + t.Run(fmt.Sprintf("TestMigrationOrder %v", k), func(t *testing.T) { + t.Parallel() - assert.Equal(t, v.order, res, "order of migrations is wrong for test %v", k) + res := dmorph.TmigrationOrder(v.m0, v.m1) + + assert.Equal(t, v.order, res, "order of migrations is wrong for test %v", k) + }) } } // TestMigrationIsValid checks the validity checks for migrations. func TestMigrationIsValid(t *testing.T) { + t.Parallel() + tests := []struct { m dmorph.Morpher err error @@ -299,31 +324,39 @@ func TestMigrationIsValid(t *testing.T) { } for k, v := range tests { - err := v.m.IsValid() + t.Run(fmt.Sprintf("TestMigrationIsValid %v", k), func(t *testing.T) { + t.Parallel() + + err := v.m.IsValid() - assert.ErrorIs(t, err, v.err, "error is wrong for test %v", k) + assert.ErrorIs(t, err, v.err, "error is wrong for test %v", k) + }) } } // TestMigrationWithLogger validates the creation of a Morpher with a logger and ensures // the logger is applied correctly. func TestMigrationWithLogger(t *testing.T) { - l := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + t.Parallel() + + newLog := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelWarn, })) morpher, err := dmorph.NewMorpher( dmorph.WithDialect(dmorph.DialectSQLite()), dmorph.WithMigrationFromFile("testData/01_base_table.sql"), - dmorph.WithLog(l), + dmorph.WithLog(newLog), ) - assert.NoError(t, err, "morpher could not be created") - assert.Equal(t, l, morpher.Log, "logger was not set correctly") + require.NoError(t, err, "morpher could not be created") + assert.Equal(t, newLog, morpher.Log, "logger was not set correctly") } // TestMigrationWithoutMigrations ensures that creating a Morpher instance without migrations results in an error. func TestMigrationWithoutMigrations(t *testing.T) { + t.Parallel() + _, err := dmorph.NewMorpher( dmorph.WithDialect(dmorph.DialectSQLite()), ) @@ -334,19 +367,23 @@ func TestMigrationWithoutMigrations(t *testing.T) { // TestMigrationWithTableNameValid verifies the correct creation of a Morpher // with a valid custom table name configuration. func TestMigrationWithTableNameValid(t *testing.T) { + t.Parallel() + morpher, err := dmorph.NewMorpher( dmorph.WithDialect(dmorph.DialectSQLite()), dmorph.WithMigrationFromFile("testData/01_base_table.sql"), dmorph.WithTableName("dimorphodon"), ) - assert.NoError(t, err, "morpher could not be created") + require.NoError(t, err, "morpher could not be created") assert.Equal(t, "dimorphodon", morpher.TableName, "table name was not set correctly") } // TestMigrationWithTableNameInvalidSize verifies that creating a Morpher // with an invalid table name size produces an error. func TestMigrationWithTableNameInvalidSize(t *testing.T) { + t.Parallel() + _, err := dmorph.NewMorpher( dmorph.WithDialect(dmorph.DialectSQLite()), dmorph.WithMigrationFromFile("testData/01_base_table.sql"), @@ -359,6 +396,8 @@ func TestMigrationWithTableNameInvalidSize(t *testing.T) { // TestMigrationWithTableNameInvalidChars ensures that creating a Morpher // fails when the table name contains invalid characters. func TestMigrationWithTableNameInvalidChars(t *testing.T) { + t.Parallel() + _, err := dmorph.NewMorpher( dmorph.WithDialect(dmorph.DialectSQLite()), dmorph.WithMigrationFromFile("testData/01_base_table.sql"), @@ -370,6 +409,8 @@ func TestMigrationWithTableNameInvalidChars(t *testing.T) { // TestMigrationRunInvalid verifies that running a Morpher with invalid configuration results in an error. func TestMigrationRunInvalid(t *testing.T) { + t.Parallel() + morpher := dmorph.Morpher{} runErr := morpher.Run(t.Context(), nil) @@ -380,10 +421,12 @@ func TestMigrationRunInvalid(t *testing.T) { // TestMigrationRunInvalidCreate tests the behavior of running a migration // with an invalid CreateTemplate in the dialect. func TestMigrationRunInvalidCreate(t *testing.T) { + t.Parallel() + db := openTempSQLite(t) dialect := dmorph.DialectSQLite() - dialect.CreateTemplate = "utter nonsense" + dialect.CreateTemplate = "utter nonsense 0" morpher, morpherErr := dmorph.NewMorpher( dmorph.WithDialect(dialect), @@ -398,10 +441,12 @@ func TestMigrationRunInvalidCreate(t *testing.T) { // TestMigrationRunInvalidApplied tests the failure scenario where the AppliedTemplate of the dialect is invalid. func TestMigrationRunInvalidApplied(t *testing.T) { + t.Parallel() + db := openTempSQLite(t) dialect := dmorph.DialectSQLite() - dialect.AppliedTemplate = "utter nonsense" + dialect.AppliedTemplate = "utter nonsense 1" morpher, morpherErr := dmorph.NewMorpher( dmorph.WithDialect(dialect), @@ -416,6 +461,8 @@ func TestMigrationRunInvalidApplied(t *testing.T) { // TestMigrationApplyInvalidDB verifies that applying migrations to an invalid or closed database results in an error. func TestMigrationApplyInvalidDB(t *testing.T) { + t.Parallel() + db := openTempSQLite(t) morpher, morpherErr := dmorph.NewMorpher( @@ -431,6 +478,8 @@ func TestMigrationApplyInvalidDB(t *testing.T) { // TestMigrationApplyUnableRegister tests the behavior when the migration registration fails due to an invalid template. func TestMigrationApplyUnableRegister(t *testing.T) { + t.Parallel() + db := openTempSQLite(t) morpher, morpherErr := dmorph.NewMorpher( @@ -442,7 +491,7 @@ func TestMigrationApplyUnableRegister(t *testing.T) { d, dialectOK := morpher.Dialect.(dmorph.BaseDialect) require.True(t, dialectOK, "dialect is not a BaseDialect") - d.RegisterTemplate = "utter nonsense" + d.RegisterTemplate = "utter nonsense 2" morpher.Dialect = d assert.Error(t, @@ -453,6 +502,8 @@ func TestMigrationApplyUnableRegister(t *testing.T) { // TestMigrationApplyUnableCommit tests the scenario where a migration application fails // due to inability to commit a transaction. func TestMigrationApplyUnableCommit(t *testing.T) { + t.Parallel() + db := openTempSQLite(t) morpher, morpherErr := dmorph.NewMorpher( diff --git a/testData/01_base_table.sql b/testData/01_base_table.sql index cdafbb5..c1f26f2 100644 --- a/testData/01_base_table.sql +++ b/testData/01_base_table.sql @@ -1,4 +1,4 @@ --- Copyright the DMorph contributors. +-- SPDX-FileCopyrightText: 2025 The DMorph contributors. -- SPDX-License-Identifier: MPL-2.0 CREATE TABLE tab0 ( diff --git a/testData/02_addon_table.sql b/testData/02_addon_table.sql index 62a857d..66b412d 100644 --- a/testData/02_addon_table.sql +++ b/testData/02_addon_table.sql @@ -1,4 +1,4 @@ --- Copyright the DMorph contributors. +-- SPDX-FileCopyrightText: 2025 The DMorph contributors. -- SPDX-License-Identifier: MPL-2.0 CREATE TABLE tab1 ( diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..703097f --- /dev/null +++ b/utils.go @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2025 The Dmorph contributors. +// SPDX-License-Identifier: MPL-2.0 + +package dmorph + +import ( + "fmt" +) + +// wrapIfError wraps an existing error with the provided text. +// It returns nil if `err` is nil, or the original `err` if `text` is empty. +// Otherwise, it returns a new error with the format "text: original_error". +func wrapIfError(text string, err error) error { + if err == nil { + return nil + } + + if text == "" { + return err + } + + return fmt.Errorf("%s: %w", text, err) +} diff --git a/utils_internal_test.go b/utils_internal_test.go new file mode 100644 index 0000000..a860d16 --- /dev/null +++ b/utils_internal_test.go @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2025 The DMorph contributors. +// SPDX-License-Identifier: MPL-2.0 + +package dmorph_test + +import ( + "errors" + "fmt" + "testing" + + "github.com/AlphaOne1/dmorph" +) + +func TestWrapError(t *testing.T) { + t.Parallel() + + tests := []struct { + err error + text string + wantErr bool + wantErrMsg string + }{ + { // 0 + text: "wrap this", + err: errors.New("original error"), + wantErr: true, + wantErrMsg: "wrap this: original error", + }, + { // 1 + text: "wrap that: %w", + err: errors.New("original error"), + wantErr: true, + wantErrMsg: "wrap that: %w: original error", + }, + { // 2 + text: "wrap this", + err: nil, + wantErr: false, + }, + { // 3 + text: "", + err: errors.New("error case"), + wantErr: true, + wantErrMsg: "error case", + }, + { // 4 + text: "", + err: nil, + wantErr: false, + }, + } + + for testIndex, test := range tests { + t.Run(fmt.Sprintf("WrapError-%d", testIndex), func(t *testing.T) { + t.Parallel() + + got := dmorph.TwrapIfError(test.text, test.err) + + if (got != nil) != test.wantErr { + t.Errorf(`got error "%v", but wanted "%v"`, got, test.wantErr) + } + + if test.wantErr && got.Error() != test.wantErrMsg { + t.Errorf(`got error "%v" but wanted "%v"`, got.Error(), test.wantErrMsg) + } + }) + } +}