diff --git a/.github/actions/setup-demo-deps/action.yml b/.github/actions/setup-demo-deps/action.yml new file mode 100644 index 0000000000..3985364f60 --- /dev/null +++ b/.github/actions/setup-demo-deps/action.yml @@ -0,0 +1,61 @@ +name: "Setup Demo Dependencies" +description: "Install dependencies required by the LLGo demo suite" + +runs: + using: "composite" + steps: + - name: Install cargs demo library + shell: bash + run: | + set -euo pipefail + + case "${RUNNER_OS}" in + Linux) + demo_pkg="cargs_linux_amd64.zip" + ;; + macOS) + case "$(uname -m)" in + x86_64) + demo_pkg="cargs_darwin_amd64.zip" + ;; + arm64) + demo_pkg="cargs_darwin_arm64.zip" + ;; + *) + echo "Unsupported macOS architecture: $(uname -m)" >&2 + exit 1 + ;; + esac + ;; + *) + echo "Unsupported runner OS: ${RUNNER_OS}" >&2 + exit 1 + ;; + esac + + libs_dir="${GITHUB_WORKSPACE}/_demo/c/cargs/libs" + mkdir -p "${libs_dir}" + wget -P "${libs_dir}" "https://github.com/xgo-dev/llpkg/releases/download/cargs/v1.0.0/${demo_pkg}" + unzip -o "${libs_dir}/${demo_pkg}" -d "${libs_dir}" + + for tmpl in "${libs_dir}"/lib/pkgconfig/*.pc.tmpl; do + pc_file="${tmpl%.tmpl}" + sed "s|{{.Prefix}}|${libs_dir}|g" "${tmpl}" > "${pc_file}" + done + + echo "PKG_CONFIG_PATH=${libs_dir}/lib/pkgconfig:${PKG_CONFIG_PATH:-}" >> "${GITHUB_ENV}" + + - name: Install Python demo dependencies + shell: bash + run: | + set -euo pipefail + + pip3.12 install --break-system-packages numpy torch + + pcdir="${HOME}/pc" + mkdir -p "${pcdir}" + libdir="$(pkg-config --variable=libdir python-3.12-embed)" + ln -sf "${libdir}/pkgconfig/python-3.12-embed.pc" "${pcdir}/python3-embed.pc" + + echo "PKG_CONFIG_PATH=${pcdir}:${PKG_CONFIG_PATH:-}" >> "${GITHUB_ENV}" + echo "LLGO_FULL_RPATH=true" >> "${GITHUB_ENV}" diff --git a/.github/actions/setup-deps/action.yml b/.github/actions/setup-deps/action.yml index 7a0669c8e9..8c5e0847fe 100644 --- a/.github/actions/setup-deps/action.yml +++ b/.github/actions/setup-deps/action.yml @@ -5,18 +5,231 @@ inputs: description: "LLVM version to install" required: true default: "19" + install-llvm: + description: "Whether to install LLVM" + required: false + default: "true" runs: using: "composite" steps: + - name: Set up Windows MSYS2 + if: runner.os == 'Windows' && inputs.install-llvm == 'true' + id: msys2 + # CLANG64 provides a native Windows LLVM build. It is only the host + # library used by github.com/xgo-dev/llvm; LLGo's target configuration + # independently selects the requested Go target ABI. + uses: msys2/setup-msys2@v2 + with: + msystem: CLANG64 + path-type: inherit + update: true + + - name: Install Windows dependencies + if: runner.os == 'Windows' && inputs.install-llvm == 'true' + shell: msys2 {0} + run: | + set -euo pipefail + + llvm_major="${{ inputs.llvm-version }}" + case "$llvm_major" in + 19) + llvm_version=19.1.7 + package_version=19.1.7-1 + ;; + *) + echo "unsupported Windows LLVM version: $llvm_major" >&2 + exit 1 + ;; + esac + + repo=https://repo.msys2.org/mingw/clang64 + prefix=mingw-w64-clang-x86_64 + packages=( + "clang-$package_version" + "clang-libs-$package_version" + "compiler-rt-$package_version" + "llvm-$package_version" + "llvm-libs-$package_version" + "lld-$package_version" + "libc++-$package_version" + "libunwind-$package_version" + "gettext-runtime-0.22.5-2" + "libffi-3.4.6-1" + "libiconv-1.17-4" + "libxml2-2.12.9-2" + "xz-5.6.3-3" + "zlib-1.3.1-1" + "zstd-1.5.6-2" + ) + urls=() + for package in "${packages[@]}"; do + urls+=("$repo/$prefix-$package-any.pkg.tar.zst") + done + + # LLVM 19 packages predate MSYS2's compiler-runtime dependency rename + # from gcc-libs to cc-libs. libc++ 19 already supplies the runtime, so + # satisfy only the renamed package metadata while installing the + # archived, mutually compatible package set. + assumed_cc_runtime="$prefix-cc-libs=$llvm_version" + pacman --noconfirm -U \ + --assume-installed "$assumed_cc_runtime" \ + "${urls[@]}" + pacman --noconfirm -S --needed \ + --assume-installed "$assumed_cc_runtime" \ + "$prefix-pkgconf" + + for package in clang clang-libs compiler-rt llvm llvm-libs lld libc++ libunwind; do + installed="$(pacman -Q "$prefix-$package" | awk '{print $2}')" + if [[ "$installed" != "$package_version" ]]; then + echo "expected $package $package_version, got $installed" >&2 + exit 1 + fi + done + + # The released LLVM binding discovers its host flags through + # versioned pkg-config metadata. MSYS2 does not ship llvm.pc, so derive + # it from the authoritative llvm-config installation instead of + # leaking global CGO flags into every package in the build. + cflags="$(llvm-config --cflags)" + ldflags="$(llvm-config --ldflags --libs all --system-libs)" + cflags="${cflags//$'\r'/}" + cflags="${cflags//$'\n'/ }" + ldflags="${ldflags//$'\r'/}" + ldflags="${ldflags//$'\n'/ }" + # Install the metadata in CLANG64's standard pkg-config tree. This + # keeps discovery stable for both MSYS2 shells and native Go/CGO + # subprocesses without passing shell-specific temporary paths between + # workflow steps. + pc_dir="$MINGW_PREFIX/lib/pkgconfig" + mkdir -p "$pc_dir" + printf '%s\n' \ + "Name: LLVM $llvm_major" \ + "Description: LLVM $llvm_major host compiler and linker flags" \ + "Version: $llvm_version" \ + "Cflags: $cflags" \ + "Libs: $ldflags" \ + > "$pc_dir/llvm-$llvm_major.pc" + + { + echo "CC=clang" + echo "CXX=clang++" + echo "CGO_ENABLED=1" + } >> "$GITHUB_ENV" + + if [[ "$(llvm-config --version)" != "$llvm_version" ]]; then + echo "expected LLVM $llvm_version, got $(llvm-config --version)" >&2 + exit 1 + fi + pkg-config --modversion "llvm-$llvm_major" + + - name: Configure Windows SDK and MSVC tools + if: runner.os == 'Windows' && inputs.install-llvm == 'true' + shell: pwsh + env: + MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }} + run: | + $ErrorActionPreference = "Stop" + + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + # LLVM 19 predates the Visual Studio 18 STL compatibility floor. Keep + # this pinned lane on VS 2022 instead of selecting a newer toolset + # merely because the runner image also happens to contain one. + $installPath = & $vswhere -latest -products * -version '[17.0,18.0)' ` + -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 ` + -property installationPath + if (-not $installPath) { + throw "Visual Studio 2022 C++ tools required by LLVM 19 were not found" + } + Import-Module "$installPath\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" + Enter-VsDevShell -VsInstallPath $installPath ` + -SkipAutomaticLocation ` + -DevCmdArguments "-arch=x64 -host_arch=x64" + + # Clang emits the MSVC ABI but uses the SDK/UCRT selected by the + # Visual Studio developer environment. Persist only the target search + # variables and native tools needed by later composite-action callers. + foreach ($name in @( + "INCLUDE", + "LIB", + "LIBPATH", + "UCRTVersion", + "UniversalCRTSdkDir", + "VCINSTALLDIR", + "VCToolsInstallDir", + "WindowsSdkDir", + "WindowsSDKVersion" + )) { + $value = [Environment]::GetEnvironmentVariable($name) + if ($value) { + Add-Content -Encoding utf8 $env:GITHUB_ENV "$name=$value" + } + } + @("cl.exe", "lib.exe", "link.exe", "rc.exe", "mt.exe") | + ForEach-Object { Split-Path (Get-Command $_).Source } | + Select-Object -Unique | + ForEach-Object { Add-Content -Encoding utf8 $env:GITHUB_PATH $_ } + + # MSYS2 stores compiler-rt under its GNU archive name. Clang's MSVC + # target searches the equivalent target-specific COFF layout, so make + # the same archive visible there without changing its ABI or contents. + $clang = Join-Path $env:MSYS2_LOCATION "clang64\bin\clang.exe" + if (-not (Test-Path $clang)) { + throw "LLVM 19 clang was not found at $clang" + } + $resourceDir = (& $clang -print-resource-dir).Trim() + $gnuBuiltins = Join-Path $resourceDir "lib\windows\libclang_rt.builtins-x86_64.a" + if (-not (Test-Path $gnuBuiltins)) { + throw "LLVM 19 compiler-rt archive was not found at $gnuBuiltins" + } + $msvcRuntimeDir = Join-Path $resourceDir "lib\x86_64-pc-windows-msvc" + New-Item -ItemType Directory -Force $msvcRuntimeDir | Out-Null + Copy-Item -Force $gnuBuiltins (Join-Path $msvcRuntimeDir "clang_rt.builtins.lib") + - name: Install macOS dependencies if: runner.os == 'macOS' shell: bash + env: + HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 run: | brew update - brew install llvm@${{inputs.llvm-version}} lld@${{inputs.llvm-version}} bdw-gc openssl libffi libuv - brew link --overwrite llvm@${{inputs.llvm-version}} lld@${{inputs.llvm-version}} libffi - echo "$(brew --prefix llvm@${{inputs.llvm-version}})/bin" >> $GITHUB_PATH + + # Install LLVM if requested + if [[ "${{ inputs.install-llvm }}" == "true" ]]; then + llvm_formula="llvm@${{inputs.llvm-version}}" + lld_formula="lld@${{inputs.llvm-version}}" + + # GitHub macOS runners may pre-link another LLVM/LLD version (for example llvm@18), + # which makes lld@ fail during Homebrew's automatic link step. + while IFS= read -r formula; do + case "${formula}" in + llvm|llvm@*|lld|lld@*) + if [[ "${formula}" != "${llvm_formula}" && "${formula}" != "${lld_formula}" ]]; then + brew unlink "${formula}" || true + fi + ;; + esac + done < <(brew list --formula) + + brew install "${llvm_formula}" "${lld_formula}" + brew link --force --overwrite "${llvm_formula}" "${lld_formula}" + llvm_bin="$(brew --prefix "${llvm_formula}")/bin" + lld_bin="$(brew --prefix "${lld_formula}")/bin" + echo "${llvm_bin}" >> "$GITHUB_PATH" + + # Print resolved toolchain versions for CI diagnostics. + echo "LLVM/LLD versions:" + brew list --versions "${llvm_formula}" "${lld_formula}" + clang_version="$("${llvm_bin}/clang" --version | head -n 1)" + lld_version="$("${lld_bin}/ld.lld" --version | head -n 1)" + echo "clang: ${clang_version}" + echo "ld.lld: ${lld_version}" + echo "linked ld.lld path: $(command -v ld.lld || true)" + fi + + # Install common dependencies + brew install bdw-gc openssl libffi libuv + brew link --overwrite libffi # Install optional deps for demos. # @@ -24,18 +237,29 @@ runs: opt_deps=( cjson # for github.com/goplus/lib/c/cjson sqlite # for github.com/goplus/lib/c/sqlite - python@3.12 # for github.com/goplus/lib/py ) brew install "${opt_deps[@]}" + + brew install python@3.12 || true # for github.com/goplus/lib/py + brew link --overwrite python@3.12 + - name: Install Ubuntu dependencies if: runner.os == 'Linux' shell: bash run: | - echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-${{inputs.llvm-version}} main" | sudo tee /etc/apt/sources.list.d/llvm.list - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - - sudo apt-get update - sudo apt-get install -y llvm-${{inputs.llvm-version}}-dev clang-${{inputs.llvm-version}} libclang-${{inputs.llvm-version}}-dev lld-${{inputs.llvm-version}} libunwind-${{inputs.llvm-version}}-dev libc++-${{inputs.llvm-version}}-dev pkg-config libgc-dev libssl-dev zlib1g-dev libffi-dev libcjson-dev libuv1-dev - echo "PATH=/usr/lib/llvm-${{inputs.llvm-version}}/bin:$PATH" >> $GITHUB_ENV + # Install LLVM if requested + if [[ "${{ inputs.install-llvm }}" == "true" ]]; then + echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-${{inputs.llvm-version}} main" | sudo tee /etc/apt/sources.list.d/llvm.list + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + sudo apt-get update + sudo apt-get install -y llvm-${{inputs.llvm-version}}-dev clang-${{inputs.llvm-version}} libclang-${{inputs.llvm-version}}-dev lld-${{inputs.llvm-version}} libunwind-${{inputs.llvm-version}}-dev libc++-${{inputs.llvm-version}}-dev + echo "PATH=/usr/lib/llvm-${{inputs.llvm-version}}/bin:$PATH" >> $GITHUB_ENV + else + sudo apt-get update + fi + + # Install common dependencies + sudo apt-get install -y pkg-config libgc-dev libssl-dev zlib1g-dev libffi-dev libcjson-dev libuv1-dev # Install optional deps for demos. # diff --git a/.github/actions/setup-embed-deps/action.yml b/.github/actions/setup-embed-deps/action.yml new file mode 100644 index 0000000000..86a3de225c --- /dev/null +++ b/.github/actions/setup-embed-deps/action.yml @@ -0,0 +1,37 @@ +name: "Setup Embedded Dependencies" +description: "Install dependencies required for embedded QEMU tests" + +runs: + using: "composite" + steps: + - name: Install macOS embedded dependencies + if: runner.os == 'macOS' + shell: bash + env: + HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 + run: | + brew update + brew install sdl2 + + - name: Install Ubuntu embedded dependencies + if: runner.os == 'Linux' + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y libsdl2-2.0-0 libslirp0 + + - name: Install ESP QEMU toolchains + shell: bash + run: | + chmod +x .github/workflows/install-esp-qemu.sh + QEMU_DIR=".cache/qemu" + .github/workflows/install-esp-qemu.sh "$QEMU_DIR" + echo "${PWD}/${QEMU_DIR}/bin" >> $GITHUB_PATH + + - name: Verify ESP QEMU installation + shell: bash + run: | + which qemu-system-riscv32 + which qemu-system-xtensa + qemu-system-riscv32 --version + qemu-system-xtensa --version diff --git a/.github/actions/setup-go/action.yml b/.github/actions/setup-go/action.yml index 95e452477e..1ce97f4bd5 100644 --- a/.github/actions/setup-go/action.yml +++ b/.github/actions/setup-go/action.yml @@ -1,51 +1,42 @@ name: "Setup Go" -description: "Setup Go environment by downloading and extracting from go.dev" +description: "Set up and verify the requested Go toolchain" inputs: go-version: - description: "The Go version to download and use" - required: true + description: "The exact Go version to install" + required: false + # Keep this pin synchronized with the primary CI and release smoke-test + # matrices. Upgrade it in a dedicated toolchain-update PR. + default: "1.26.5" runs: using: "composite" steps: - - name: Download and setup Go + - name: Set up Go + uses: actions/setup-go@v7 + with: + go-version: ${{ inputs.go-version }} + cache: false + + - name: Verify Go installation shell: bash run: | - set -e - GO_VERSION="${{ inputs.go-version }}" - GO_VERSION="${GO_VERSION#go}" # Remove 'go' prefix if present - - # Determine OS and architecture - if [[ "$RUNNER_OS" == "macOS" ]]; then - OS="darwin" - ARCH="arm64" - else - OS="linux" - ARCH="amd64" - fi - - DOWNLOAD_URL="https://go.dev/dl/go${GO_VERSION}.${OS}-${ARCH}.tar.gz" - echo "Downloading Go from: ${DOWNLOAD_URL}" - - # Create temporary directory for download - TMP_DIR=$(mktemp -d) - curl -L "${DOWNLOAD_URL}" -o "${TMP_DIR}/go.tar.gz" - - # Remove existing Go installation if any - sudo rm -rf /usr/local/go + set -euo pipefail - # Extract to /usr/local - sudo tar -C /usr/local -xzf "${TMP_DIR}/go.tar.gz" + requested="${{ inputs.go-version }}" + requested="${requested#go}" + actual="$(go env GOVERSION)" + actual="${actual#go}" - # Clean up - rm -rf "${TMP_DIR}" - - # Add to PATH - echo "/usr/local/go/bin" >> $GITHUB_PATH - echo "$HOME/go/bin" >> $GITHUB_PATH + if [[ ! "$requested" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Go version must be an exact stable release, got ${requested}" >&2 + exit 1 + fi + if [[ "$actual" != "$requested" ]]; then + echo "Expected Go ${requested}, got go${actual}" >&2 + exit 1 + fi - - name: Verify Go installation - shell: bash - run: | - # Verify installation - echo "Verifying Go installation..." + echo "Requested Go: ${requested}" + echo "Resolved Go: go${actual}" + echo "Go binary: $(command -v go)" go version + go env GOROOT diff --git a/.github/actions/setup-goreleaser/action.yml b/.github/actions/setup-goreleaser/action.yml index d4ef866559..feb2cff60f 100644 --- a/.github/actions/setup-goreleaser/action.yml +++ b/.github/actions/setup-goreleaser/action.yml @@ -1,37 +1,47 @@ name: "Setup GoReleaser" description: "Setup GoReleaser environment" inputs: - darwin-cache-key: - description: "Darwin sysroot cache key" - required: true linux-cache-key: description: "Linux sysroot cache key" required: true + esp-clang-cache-path: + description: "ESP Clang cache path (internal use)" + required: false + default: | + .sysroot/darwin/amd64/crosscompile/clang + .sysroot/darwin/arm64/crosscompile/clang + .sysroot/linux/amd64/crosscompile/clang + .sysroot/linux/arm64/crosscompile/clang runs: using: "composite" steps: - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: 1.24.x - - name: Restore Darwin sysroot cache - id: cache-darwin-sysroot - uses: actions/cache/restore@v4 - with: - path: .sysroot/darwin.tar.gz - key: ${{ inputs.darwin-cache-key }} + uses: ./.github/actions/setup-go - name: Restore Linux sysroot cache id: cache-linux-sysroot - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: path: .sysroot/linux.tar.gz key: ${{ inputs.linux-cache-key }} - - name: Populate Darwin sysroot - run: tar -xzvf .sysroot/darwin.tar.gz -C .sysroot - shell: bash - name: Populate Linux sysroot run: tar -xzvf .sysroot/linux.tar.gz -C .sysroot shell: bash + - name: Restore ESP Clang cache + id: cache-esp-clang + uses: actions/cache/restore@v5 + with: + path: ${{ inputs.esp-clang-cache-path }} + key: esp-clang-${{ hashFiles('.github/workflows/download_esp_clang.sh') }} + - name: Download ESP Clang (if cache miss) + if: steps.cache-esp-clang.outputs.cache-hit != 'true' + run: bash .github/workflows/download_esp_clang.sh + shell: bash + - name: Save ESP Clang cache + if: steps.cache-esp-clang.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: ${{ inputs.esp-clang-cache-path }} + key: esp-clang-${{ hashFiles('.github/workflows/download_esp_clang.sh') }} - name: Check file run: tree .sysroot shell: bash diff --git a/.github/actions/test-helloworld/action.yml b/.github/actions/test-helloworld/action.yml index 9ce49ef911..9020e7887b 100644 --- a/.github/actions/test-helloworld/action.yml +++ b/.github/actions/test-helloworld/action.yml @@ -38,7 +38,7 @@ runs: Hello, LLGo! Hello, LLGo! Hello LLGo by cpp/std.Str" - OUTPUT=$(llgo run . 2>&1) + OUTPUT=$(llgo run . 2>&1 | tee /dev/stderr) if echo "$OUTPUT" | grep -qF "$EXPECTED"; then echo "Basic test passed" else @@ -50,4 +50,14 @@ runs: exit 1 fi - #TODO(zzy): Test embed targets, need dispatch target dir + cd ../.. + mkdir -p _test/emb && cd _test/emb + cat > main.go << 'EOL' + package main + + func main() { + } + EOL + llgo build -v -target esp32-coreboard-v2 -o demo.out . + test -f demo.out.elf && echo "ESP32 cross-compilation test passed: demo.out.elf generated" + exit $? diff --git a/.github/codecov.yml b/.github/codecov.yml index 778c88a480..d6e56ebb1c 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,15 +1,15 @@ -coverage: - ignore: - - "chore" - - "cmd" - - "cl/cltest" - - "internal/build" - - "internal/llgen" - - "internal/mockable" - - "internal/packages" - - "internal/typepatch" - - "internal/github" - - "internal/firmware" - - "internal/flash" - - "internal/monitor" - - "xtool" +ignore: + - "benchmark" + - "chore" + - "cmd" + - "cl/cltest" + - "internal/llgen" + - "internal/mockable" + - "internal/packages" + - "internal/typepatch" + - "internal/github" + - "internal/firmware" + - "internal/flash" + - "internal/monitor" + - "test/go" + - "xtool" diff --git a/.github/llgo-benchmark.yml b/.github/llgo-benchmark.yml new file mode 100644 index 0000000000..b3e722ce99 --- /dev/null +++ b/.github/llgo-benchmark.yml @@ -0,0 +1,57 @@ +version: 1 +id: llgo-baseline +title: LLGo baseline benchmarks +site-path: benchmark/baseline +include: + - "^BenchmarkProgram/" + - "^Benchmark(MergeCompilerFlags|MergeLinkerFlags|LookupPCRandom)$" + - "^Benchmark(RuntimeGetG|Global(Read|Write))$" + - "^Benchmark(DirectCall|InterfaceCall|Defer|Goroutine)$" + - "^BenchmarkChannel(Buffered|Handoff)$" +groups: + programs: "^Program/" + compiler: "^(MergeCompilerFlags|MergeLinkerFlags|LookupPCRandom)$" + core: + match: + - "^(RuntimeGetG|Global(Read|Write))$" + - "^(DirectCall|InterfaceCall|Defer|Goroutine)$" + - "^Channel(Buffered|Handoff)$" +views: + programs: + title: Program measurements + select: + groups: "^programs$" + metrics: "^(file-bytes|text-bytes|build-ns|run-ns)$" + table: + rows: [platform, benchmark] + columns: [metric] + missing: error + empty: error + dimensions: + benchmark: + title: Workload + trim-prefix: BenchmarkProgram/ + metrics: + file-bytes: + title: File size + format: bytes + text-bytes: + title: Text size + format: bytes + build-ns: + title: Build + format: duration-ns + run-ns: + title: Run + format: duration-ns + core: + title: Core language and compiler benchmarks + select: + groups: "^(compiler|core)$" + metrics: "^ns/op$" + table: + rows: [platform, benchmark] + columns: [metric] + collapsed: true + missing: error + empty: error diff --git a/.github/scripts/classify_benchmark_changes.py b/.github/scripts/classify_benchmark_changes.py new file mode 100644 index 0000000000..dafaefc314 --- /dev/null +++ b/.github/scripts/classify_benchmark_changes.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python3 +"""Classify LLGo repository changes for benchmark workflow decisions. + +Each changed path has one primary category. A commit can therefore contain +multiple categories, while workflows can make decisions from stable boolean +outputs such as ``compiler=true``. +""" + +from __future__ import annotations + +import argparse +import json +from dataclasses import dataclass +from pathlib import PurePosixPath +import subprocess +import sys +from typing import Iterable, Sequence + + +CATEGORIES = ( + "compiler", + "runtime", + "stdlib", + "test", + "benchmark", + "example", + "docs", + "ci", + "tooling", + "other", +) + +TEST_DIRS = { + "_cmptest", + "test", + "cl/cltest", + "cmd/llgo/lldbtest", + "internal/filecheck", + "internal/littest", + "internal/llgen", + "runtime/_test", + "runtime/_patch/_test", + "runtime/internal/test", + "ssa/ssatest", +} + +COMPILER_DIRS = { + "cl", + "cmd/internal/base", + "cmd/internal/build", + "cmd/internal/clean", + "cmd/internal/compile", + "cmd/internal/compilerhash", + "cmd/internal/flags", + "cmd/internal/get", + "cmd/internal/help", + "cmd/internal/install", + "cmd/internal/lldb", + "cmd/internal/monitor", + "cmd/internal/run", + "cmd/internal/test", + "cmd/internal/version", + "internal", + "ltoplugin", + "ssa", + "targets", + "xtool", +} + + +@dataclass(frozen=True) +class Change: + status: str + paths: tuple[str, ...] + + +def _under(path: str, directory: str) -> bool: + return path == directory or path.startswith(directory + "/") + + +def _under_any(path: str, directories: Iterable[str]) -> bool: + return any(_under(path, directory) for directory in directories) + + +def classify_path(raw_path: str) -> str: + """Return the primary category for a repository-relative path.""" + path = PurePosixPath(raw_path.replace("\\", "/")).as_posix() + if path.startswith("./"): + path = path[2:] + name = PurePosixPath(path).name + + # Put purpose-specific files before their containing source tree. For + # example, runtime/foo_test.go is a test rather than a runtime change. + if _under(path, ".github") or name in {".goreleaser.yml", ".goreleaser.yaml"}: + return "ci" + if ( + _under_any(path, {"doc", "docs", "LICENSES"}) + or name.lower().endswith((".md", ".markdown", ".rst")) + or name in {"LICENSE", "THIRD_PARTY_NOTICES.md"} + ): + return "docs" + if ( + name.endswith("_test.go") + or "testdata" in PurePosixPath(path).parts + or _under_any(path, TEST_DIRS) + or path.startswith("cl/_test") + ): + return "test" + if _under(path, "benchmark"): + return "benchmark" + if _under_any(path, {"_demo", "examples"}): + return "example" + + # runtime/_patch and runtime/internal/lib mirror or replace Go standard + # library packages. Keep them distinct from LLGo's runtime support. + if _under_any(path, {"runtime/_patch", "runtime/internal/lib"}): + return "stdlib" + if _under(path, "runtime"): + return "runtime" + + if path in {"go.mod", "go.sum"} or path.startswith("cmd/llgo/"): + return "compiler" + if _under_any(path, COMPILER_DIRS): + return "compiler" + + if _under_any(path, {"_xtool", "chore", "dev"}) or path == "install.sh": + return "tooling" + return "other" + + +def parse_name_status_z(data: bytes) -> list[Change]: + """Parse ``git diff --name-status -z`` output.""" + fields = data.decode("utf-8", errors="surrogateescape").split("\0") + if fields and not fields[-1]: + fields.pop() + + changes: list[Change] = [] + index = 0 + while index < len(fields): + status = fields[index] + index += 1 + path_count = 2 if status.startswith(("R", "C")) else 1 + if index + path_count > len(fields): + raise ValueError(f"incomplete git name-status record for {status!r}") + paths = tuple(fields[index : index + path_count]) + index += path_count + changes.append(Change(status=status, paths=paths)) + return changes + + +def _git(*args: str) -> bytes: + return subprocess.run( + ["git", *args], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ).stdout + + +def _valid_commit(revision: str) -> bool: + if not revision or set(revision) == {"0"}: + return False + return subprocess.run( + ["git", "cat-file", "-e", f"{revision}^{{commit}}"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ).returncode == 0 + + +def changes_from_git(base: str, head: str) -> list[Change]: + if not _valid_commit(head): + raise ValueError(f"head is not a local Git commit: {head}") + if not _valid_commit(base): + fallback = f"{head}^" + if not _valid_commit(fallback): + raise ValueError(f"base is not a local Git commit: {base}") + print( + f"warning: base {base or ''} is unavailable; using {fallback}", + file=sys.stderr, + ) + base = fallback + return parse_name_status_z( + _git("diff", "--name-status", "-z", "--find-renames", base, head) + ) + + +def build_report(changes: Sequence[Change]) -> dict[str, object]: + files: list[dict[str, str]] = [] + seen: set[str] = set() + for change in changes: + for path in change.paths: + if path in seen: + continue + seen.add(path) + files.append( + {"path": path, "status": change.status, "category": classify_path(path)} + ) + + by_category = { + category: [entry["path"] for entry in files if entry["category"] == category] + for category in CATEGORIES + } + return { + "schemaVersion": 1, + "categories": {category: bool(by_category[category]) for category in CATEGORIES}, + "filesByCategory": by_category, + "files": files, + } + + +def write_github_output(path: str, report: dict[str, object]) -> None: + categories = report["categories"] + assert isinstance(categories, dict) + selected = [name for name in CATEGORIES if categories[name]] + files = report["files"] + assert isinstance(files, list) + with open(path, "a", encoding="utf-8") as output: + for category in CATEGORIES: + output.write(f"{category}={'true' if categories[category] else 'false'}\n") + output.write(f"categories={','.join(selected)}\n") + output.write(f"changed_files={len(files)}\n") + + +def _markdown_cell(value: str) -> str: + return value.replace("|", "\\|").replace("\n", " ") + + +def write_github_summary(path: str, report: dict[str, object]) -> None: + files_by_category = report["filesByCategory"] + assert isinstance(files_by_category, dict) + with open(path, "a", encoding="utf-8") as summary: + summary.write("## LLGo change classification\n\n") + summary.write("| Category | Files | Paths |\n") + summary.write("| --- | ---: | --- |\n") + for category in CATEGORIES: + paths = files_by_category[category] + if paths: + shown = ", ".join(f"`{_markdown_cell(item)}`" for item in paths[:12]) + if len(paths) > 12: + shown += f", and {len(paths) - 12} more" + summary.write(f"| {category} | {len(paths)} | {shown} |\n") + compiler = bool(report["categories"]["compiler"]) + summary.write( + "\nBinary-size and compile-time benchmarks: " + + ("**triggered**" if compiler else "**not triggered**") + + ".\n" + ) + + +def print_text(report: dict[str, object]) -> None: + files_by_category = report["filesByCategory"] + assert isinstance(files_by_category, dict) + for category in CATEGORIES: + paths = files_by_category[category] + if not paths: + continue + print(f"{category} ({len(paths)}):") + for path in paths: + print(f" {path}") + + +def parse_args(argv: Sequence[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("paths", nargs="*", help="paths to classify instead of a Git diff") + parser.add_argument("--base", help="base Git revision") + parser.add_argument("--head", default="HEAD", help="head Git revision (default: HEAD)") + parser.add_argument("--format", choices=("text", "json"), default="text") + parser.add_argument("--github-output", help="append reusable outputs to this file") + parser.add_argument("--github-summary", help="append a Markdown summary to this file") + args = parser.parse_args(argv) + if args.paths and args.base: + parser.error("paths and --base cannot be used together") + if not args.paths and not args.base: + parser.error("provide paths or --base") + return args + + +def main(argv: Sequence[str] | None = None) -> int: + args = parse_args(sys.argv[1:] if argv is None else argv) + try: + changes = ( + [Change(status="M", paths=(path,)) for path in args.paths] + if args.paths + else changes_from_git(args.base, args.head) + ) + report = build_report(changes) + except (OSError, subprocess.CalledProcessError, ValueError) as error: + print(f"classification failed: {error}", file=sys.stderr) + return 2 + + if args.format == "json": + json.dump(report, sys.stdout, indent=2, sort_keys=True) + print() + else: + print_text(report) + if args.github_output: + write_github_output(args.github_output, report) + if args.github_summary: + write_github_summary(args.github_summary, report) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.github/scripts/test_classify_benchmark_changes.py b/.github/scripts/test_classify_benchmark_changes.py new file mode 100644 index 0000000000..9685498f0e --- /dev/null +++ b/.github/scripts/test_classify_benchmark_changes.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 + +import importlib.util +from pathlib import Path +import sys +import tempfile +import unittest + + +SCRIPT = Path(__file__).with_name("classify_benchmark_changes.py") +SPEC = importlib.util.spec_from_file_location("classify_benchmark_changes", SCRIPT) +assert SPEC and SPEC.loader +classifier = importlib.util.module_from_spec(SPEC) +sys.modules[SPEC.name] = classifier +SPEC.loader.exec_module(classifier) + + +class ClassifyPathTests(unittest.TestCase): + def test_representative_categories(self): + cases = { + "cl/compile.go": "compiler", + "cmd/llgo/main.go": "compiler", + "internal/build/build.go": "compiler", + "targets/device/riscv64.json": "compiler", + "go.mod": "compiler", + "runtime/abi/abi.go": "runtime", + "runtime/go.mod": "runtime", + "runtime/_patch/runtime/runtime.go": "stdlib", + "runtime/internal/lib/reflect/value.go": "stdlib", + "test/std/fmt.go": "test", + "cl/compile_test.go": "test", + "runtime/runtime_test.go": "test", + "runtime/_patch/_test/skipall/main.go": "test", + "cl/_testgo/foo.go": "test", + "cl/_testdata/foo.go": "test", + "benchmark/binary_size/README.txt": "benchmark", + "_demo/go/hello.go": "example", + "README.md": "docs", + "internal/gohex/LICENSE": "docs", + ".github/workflows/ci.yml": "ci", + "chore/gentests/main.go": "tooling", + "_xtool/astdump/main.go": "tooling", + "CODEOWNERS": "other", + } + for path, expected in cases.items(): + with self.subTest(path=path): + self.assertEqual(classifier.classify_path(path), expected) + + def test_mixed_report_only_sets_present_categories(self): + report = classifier.build_report( + [ + classifier.Change("M", ("README.md",)), + classifier.Change("M", ("cl/compile.go",)), + classifier.Change("M", ("cl/compile_test.go",)), + ] + ) + self.assertTrue(report["categories"]["compiler"]) + self.assertTrue(report["categories"]["docs"]) + self.assertTrue(report["categories"]["test"]) + self.assertFalse(report["categories"]["runtime"]) + + +class GitNameStatusTests(unittest.TestCase): + def test_rename_checks_old_and_new_paths(self): + changes = classifier.parse_name_status_z( + b"R100\0cl/old.go\0doc/new.md\0D\0runtime/old.go\0" + ) + report = classifier.build_report(changes) + self.assertEqual( + report["filesByCategory"]["compiler"], ["cl/old.go"] + ) + self.assertEqual(report["filesByCategory"]["docs"], ["doc/new.md"]) + self.assertEqual( + report["filesByCategory"]["runtime"], ["runtime/old.go"] + ) + + def test_incomplete_record_is_rejected(self): + with self.assertRaises(ValueError): + classifier.parse_name_status_z(b"R100\0only-one-path\0") + + +class OutputTests(unittest.TestCase): + def test_github_output_is_reusable(self): + report = classifier.build_report( + [classifier.Change("M", ("cl/compile.go",))] + ) + with tempfile.NamedTemporaryFile(mode="r+", encoding="utf-8") as output: + classifier.write_github_output(output.name, report) + output.seek(0) + values = dict(line.rstrip().split("=", 1) for line in output) + self.assertEqual(values["compiler"], "true") + self.assertEqual(values["runtime"], "false") + self.assertEqual(values["categories"], "compiler") + self.assertEqual(values["changed_files"], "1") + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/workflows/benchmark-publish.yml b/.github/workflows/benchmark-publish.yml new file mode 100644 index 0000000000..00125d8a2d --- /dev/null +++ b/.github/workflows/benchmark-publish.yml @@ -0,0 +1,20 @@ +name: Publish baseline benchmarks + +on: + workflow_run: + workflows: [Baseline benchmarks] + types: [completed] + +permissions: + actions: read + contents: write + issues: write + pull-requests: write + +jobs: + publish: + if: github.event.workflow_run.conclusion == 'success' + uses: xgo-dev/setup-benchmark-go-action/.github/workflows/publish.yml@v1.0.6 + with: + run_id: ${{ github.event.workflow_run.id }} + config_path: .github/llgo-benchmark.yml diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 0000000000..21b8397594 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,100 @@ +name: Baseline benchmarks + +on: + push: + branches: + - main + pull_request: + branches: ["**"] + workflow_dispatch: + +permissions: + contents: read + +jobs: + benchmark: + name: benchmark (${{ matrix.display }}) + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-24.04 + id: linux + display: Linux + - os: macos-latest + id: macos + display: macOS + runs-on: ${{ matrix.os }} + timeout-minutes: 20 + env: + GOMAXPROCS: "2" + LLGO_ROOT: ${{ github.workspace }} + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Determine pull request merge-base + if: github.event_name == 'pull_request' + id: merge-base + run: | + git fetch https://github.com/${{ github.event.pull_request.base.repo.full_name }}.git ${{ github.event.pull_request.base.ref }} + base_sha=$(git merge-base FETCH_HEAD ${{ github.event.pull_request.head.sha }}) + echo "sha=$base_sha" >> "$GITHUB_OUTPUT" + echo "Computed pull request merge-base: $base_sha (head: ${{ github.event.pull_request.head.sha }})" + + - name: Check out pull request base benchmark source + if: github.event_name == 'pull_request' + uses: actions/checkout@v7 + with: + repository: ${{ github.event.pull_request.base.repo.full_name }} + ref: ${{ steps.merge-base.outputs.sha }} + path: .benchmark/source + persist-credentials: false + + - name: Install dependencies + uses: ./.github/actions/setup-deps + with: + llvm-version: 19 + + - name: Set up Go + uses: ./.github/actions/setup-go + + - name: Measure pull request base + if: github.event_name == 'pull_request' + run: | + benchmark/baseline/run.sh \ + "$GITHUB_WORKSPACE/.benchmark/source" \ + "$GITHUB_WORKSPACE/.benchmark/base-llgo" \ + "$GITHUB_WORKSPACE/.benchmark/base-results" + + - name: Check out pull request head benchmark source + if: github.event_name == 'pull_request' + uses: actions/checkout@v7 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: .benchmark/source + persist-credentials: false + + - name: Measure current revision + run: | + source_root="$GITHUB_WORKSPACE" + if [[ "$GITHUB_EVENT_NAME" == pull_request ]]; then + source_root="$GITHUB_WORKSPACE/.benchmark/source" + fi + benchmark/baseline/run.sh \ + "$source_root" \ + "$GITHUB_WORKSPACE/.benchmark/llgo" \ + "$GITHUB_WORKSPACE/.benchmark/results" + + - name: Record benchmark result + uses: xgo-dev/setup-benchmark-go-action@v1.0.6 + with: + config: .github/llgo-benchmark.yml + benchmark-file: .benchmark/results/benchmark.txt + baseline-benchmark-file: >- + ${{ github.event_name == 'pull_request' && + '.benchmark/base-results/benchmark.txt' || '' }} + platform-id: ${{ matrix.id }} + platform-label: ${{ matrix.display }} diff --git a/.github/workflows/build-cache.yml b/.github/workflows/build-cache.yml new file mode 100644 index 0000000000..62429be59d --- /dev/null +++ b/.github/workflows/build-cache.yml @@ -0,0 +1,66 @@ +# Build cache tests for llgo +# Tests various build configurations to ensure cache works correctly + +name: Build Cache + +on: + push: + branches: + - main + pull_request: + branches: ["**"] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + build-cache: + timeout-minutes: 30 + strategy: + matrix: + os: [macos-latest, ubuntu-latest] + llvm: [19] + runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v7 + + - name: Install dependencies + uses: ./.github/actions/setup-deps + with: + llvm-version: ${{matrix.llvm}} + + - name: Set up Go + uses: ./.github/actions/setup-go + + - name: Install wamr (for wasm tests) + if: startsWith(matrix.os, 'macos') + run: | + git clone --branch WAMR-2.4.4 --depth 1 https://github.com/bytecodealliance/wasm-micro-runtime.git + mkdir wasm-micro-runtime/product-mini/platforms/darwin/build + cd wasm-micro-runtime/product-mini/platforms/darwin/build + cmake -D WAMR_BUILD_EXCE_HANDLING=1 -D WAMR_BUILD_FAST_INTERP=0 -DWAMR_BUILD_SHARED_MEMORY=1 -DWAMR_BUILD_LIB_WASI_THREADS=1 -DWAMR_BUILD_LIB_PTHREAD=1 -DCMAKE_BUILD_TYPE=Debug -DWAMR_BUILD_DEBUG_INTERP=1 .. + make -j8 + echo "$PWD" >> $GITHUB_PATH + + - name: Install wamr (for wasm tests on Linux) + if: startsWith(matrix.os, 'ubuntu') + run: | + git clone --branch WAMR-2.4.4 --depth 1 https://github.com/bytecodealliance/wasm-micro-runtime.git + mkdir wasm-micro-runtime/product-mini/platforms/linux/build + cd wasm-micro-runtime/product-mini/platforms/linux/build + cmake -D WAMR_BUILD_EXCE_HANDLING=1 -D WAMR_BUILD_FAST_INTERP=0 -DWAMR_BUILD_SHARED_MEMORY=1 -DWAMR_BUILD_LIB_WASI_THREADS=1 -DWAMR_BUILD_LIB_PTHREAD=1 -DCMAKE_BUILD_TYPE=Debug -DWAMR_BUILD_DEBUG_INTERP=1 .. + make -j8 + echo "$PWD" >> $GITHUB_PATH + + - name: Install llgo (dev mode) + run: | + go install -tags=dev ./cmd/llgo + echo "LLGO_ROOT=$GITHUB_WORKSPACE" >> $GITHUB_ENV + + - name: Install esptool.py (for ESP32-C3 tests on Linux) + if: startsWith(matrix.os, 'ubuntu') + run: pip3 install --break-system-packages esptool==5.1.0 + + - name: Run build cache tests + run: bash test/buildcache/test.sh diff --git a/.github/workflows/doc-link-checker.yml b/.github/workflows/doc-link-checker.yml new file mode 100644 index 0000000000..a79a748962 --- /dev/null +++ b/.github/workflows/doc-link-checker.yml @@ -0,0 +1,43 @@ +name: Doc Link Checker + +on: + push: + branches: + - main + paths: + - "README.md" + pull_request: + branches: ["**"] + paths: + - "README.md" + schedule: + # Run daily at 00:00 UTC + - cron: "0 0 * * *" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + doc_verify: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v7 + + - name: Set up Node.js + uses: actions/setup-node@v7 + with: + node-version: "20" + + - name: Install embedme + run: npm install -g embedme + + - name: Verify README.md embedded code + run: embedme --verify README.md + + - name: Link Checker + id: lychee + uses: lycheeverse/lychee-action@v2 + with: + args: --max-concurrency 3 --retry-wait-time 15 README.md diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 6b4ec81c44..6021b8234c 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -2,9 +2,8 @@ name: Docs on: push: - branches: - - "**" - - "!dependabot/**" + branches: + - main pull_request: branches: ["**"] @@ -13,45 +12,19 @@ concurrency: cancel-in-progress: true jobs: - doc_verify: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v5 - - - name: Set up Node.js - uses: actions/setup-node@v5 - with: - node-version: "20" - - - name: Install embedme - run: npm install -g embedme - - - name: Verify README.md embedded code - run: embedme --verify README.md - - - name: Link Checker - id: lychee - uses: lycheeverse/lychee-action@v2 - with: - args: --max-concurrency 3 --retry-wait-time 15 README.md - remote_install: - continue-on-error: true timeout-minutes: 30 strategy: matrix: os: - macos-latest - - ubuntu-24.04 + - ubuntu-latest runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Set up Go uses: ./.github/actions/setup-go - with: - go-version: "1.24.2" - name: Install dependencies on macOS if: startsWith(matrix.os, 'macos') @@ -74,21 +47,18 @@ jobs: source doc/_readme/scripts/run.sh local_install: - continue-on-error: true timeout-minutes: 30 strategy: matrix: os: - macos-latest - - ubuntu-24.04 + - ubuntu-latest runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version: "1.23" + uses: ./.github/actions/setup-go - name: Install dependencies on macOS if: startsWith(matrix.os, 'macos') @@ -126,21 +96,18 @@ jobs: source doc/_readme/scripts/run.sh local_install_full: - continue-on-error: true timeout-minutes: 30 strategy: matrix: os: - macos-latest - - ubuntu-24.04 + - ubuntu-latest runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version: "1.23" + uses: ./.github/actions/setup-go - name: Install dependencies on macOS if: startsWith(matrix.os, 'macos') diff --git a/.github/workflows/download_esp_clang.sh b/.github/workflows/download_esp_clang.sh new file mode 100755 index 0000000000..8f0d99a60e --- /dev/null +++ b/.github/workflows/download_esp_clang.sh @@ -0,0 +1,78 @@ +#!/bin/bash +set -e + +ESP_CLANG_VERSION="19.1.2_20250905-3" +BASE_URL="https://github.com/goplus/espressif-llvm-project-prebuilt/releases/download/${ESP_CLANG_VERSION}" +LLVM_LICENSE="LICENSES/XGo-LLVM-Apache-2.0-WITH-LLVM-exception.txt" + +get_esp_clang_platform() { + local platform="$1" + local os="${platform%-*}" + local arch="${platform##*-}" + + case "${os}" in + "darwin") + case "${arch}" in + "amd64") echo "x86_64-apple-darwin" ;; + "arm64") echo "aarch64-apple-darwin" ;; + *) echo "Error: Unsupported darwin architecture: ${arch}" >&2; exit 1 ;; + esac + ;; + "linux") + case "${arch}" in + "amd64") echo "x86_64-linux-gnu" ;; + "arm64") echo "aarch64-linux-gnu" ;; + *) echo "Error: Unsupported linux architecture: ${arch}" >&2; exit 1 ;; + esac + ;; + *) + echo "Error: Unsupported OS: ${os}" >&2 + exit 1 + ;; + esac +} + +get_filename() { + local platform="$1" + local platform_suffix=$(get_esp_clang_platform "${platform}") + echo "clang-esp-${ESP_CLANG_VERSION}-${platform_suffix}.tar.xz" +} + +download_and_extract() { + local platform="$1" + local os="${platform%-*}" + local arch="${platform##*-}" + local filename=$(get_filename "${platform}") + local download_url="${BASE_URL}/${filename}" + + echo "Downloading ESP Clang for ${platform}..." + echo " URL: ${download_url}" + + mkdir -p ".sysroot/${os}/${arch}/crosscompile/clang" + curl -fsSL "${download_url}" | tar -xJ -C ".sysroot/${os}/${arch}/crosscompile/clang" --strip-components=1 + + if [[ ! -f ".sysroot/${os}/${arch}/crosscompile/clang/bin/clang++" ]]; then + echo "Error: clang++ not found in ${platform} toolchain" + exit 1 + fi + + # The upstream archive currently contains only a short license pointer in + # include/llvm/Support. Keep the complete LLVM license with the toolchain + # that GoReleaser places in LLGo release archives. + install -m 0644 "${LLVM_LICENSE}" ".sysroot/${os}/${arch}/crosscompile/clang/LICENSE-LLVM.txt" + + echo "${platform} ESP Clang ready in .sysroot/${os}/${arch}/crosscompile/clang" +} + +echo "Downloading ESP Clang toolchain version ${ESP_CLANG_VERSION}..." + +if [[ ! -f "${LLVM_LICENSE}" ]]; then + echo "Error: complete LLVM license not found at ${LLVM_LICENSE}" >&2 + exit 1 +fi + +for platform in "darwin-amd64" "darwin-arm64" "linux-amd64" "linux-arm64"; do + download_and_extract "${platform}" +done + +echo "ESP Clang toolchain completed successfully!" diff --git a/.github/workflows/fmt.yml b/.github/workflows/fmt.yml index b29aa06621..a02cd22a53 100644 --- a/.github/workflows/fmt.yml +++ b/.github/workflows/fmt.yml @@ -2,9 +2,8 @@ name: Format Check on: push: - branches: - - "**" - - "!dependabot/**" + branches: + - main pull_request: branches: ["**"] @@ -17,21 +16,36 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Set up Go uses: ./.github/actions/setup-go - with: - go-version: "1.24.2" - name: Check formatting run: | - for dir in . runtime; do - pushd $dir - if [ -n "$(go fmt ./... | grep -v xgo_autogen.go)" ]; then - echo "Some files are not properly formatted. Please run 'go fmt ./...'" - exit 1 + set -euo pipefail + + check_dir() { + local dir="$1" + pushd "$dir" >/dev/null + # gofmt won't traverse directories that start with '_' or 'testdata', + # so mirror dev/local_ci.sh and scan every Go file explicitly. + fmt_output="$( + find . -name '*.go' -type f ! -name 'xgo_autogen.go' -print0 \ + | xargs -0 gofmt -l \ + | sed 's|^\\./||' \ + || true + )" + popd >/dev/null + + if [ -n "$fmt_output" ]; then + printf 'Detected gofmt differences in %s:\\n%s\\n' "$dir" "$fmt_output" + return 1 fi - popd + } + + for dir in . runtime; do + check_dir "$dir" done + echo "All files are properly formatted." diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index df6ff534b1..c2bc55dec2 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -5,9 +5,8 @@ name: Go on: push: - branches: - - "**" - - "!dependabot/**" + branches: + - main pull_request: branches: ["**"] @@ -17,22 +16,24 @@ concurrency: jobs: test: - continue-on-error: true - timeout-minutes: 30 + timeout-minutes: 60 strategy: matrix: os: - macos-latest - - ubuntu-24.04 + - ubuntu-latest llvm: [19] runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Install dependencies uses: ./.github/actions/setup-deps with: llvm-version: ${{matrix.llvm}} + - name: Install embedded dependencies + uses: ./.github/actions/setup-embed-deps + - name: Clang information run: | echo $PATH @@ -41,21 +42,172 @@ jobs: - name: Set up Go uses: ./.github/actions/setup-go - with: - go-version: "1.24.2" + + - name: Install further optional dependencies for demos + run: | + py_deps=( + numpy # for github.com/goplus/lib/py/numpy + torch # for github.com/goplus/lib/py/torch + ) + pip3.12 install --break-system-packages "${py_deps[@]}" + # Align python3-embed with python-3.12-embed to avoid ABI mismatches. + pcdir=$HOME/pc + mkdir -p "$pcdir" + libdir=$(pkg-config --variable=libdir python-3.12-embed) + ln -s "$libdir/pkgconfig/python-3.12-embed.pc" "$pcdir/python3-embed.pc" + echo "PKG_CONFIG_PATH=$pcdir:${PKG_CONFIG_PATH}" >> $GITHUB_ENV + echo "LLGO_FULL_RPATH=true" >> $GITHUB_ENV + + - name: Set LLGO_ROOT + run: echo "LLGO_ROOT=$GITHUB_WORKSPACE" >> $GITHUB_ENV - name: Build run: go build -v ./... - - name: Test - if: ${{!startsWith(matrix.os, 'macos')}} - run: go test ./... - + # Both platforms upload coverage: OS-specific paths (ELF vs Mach-O + # emission, per-OS runtime shims) are otherwise invisible to + # codecov/patch and fail it on lines only the other OS executes. - name: Test with coverage - if: startsWith(matrix.os, 'macos') - run: go test -coverprofile="coverage.txt" -covermode=atomic ./... + # 45m: the caller-info acceptance suite (test/go) legitimately grew + # the covered run past the old 30m budget on macOS runners. + run: | + set -euo pipefail + + # test/go intentionally contains compiler edge cases that make the + # Go 1.26.5 printf analyzer panic. Keep the normal go test vet gate + # for every other package, and disable vet only for that package. + go list ./... \ + | grep -v '^github.com/xgo-dev/llgo/test/go$' \ + | xargs go test -timeout 45m -coverprofile="coverage-main.txt" -covermode=atomic \ + -bench '^BenchmarkGo126' -benchtime=1x + go test -timeout 45m -vet=off -coverprofile="coverage-test-go.txt" -covermode=atomic ./test/go + + head -n 1 coverage-main.txt > coverage.txt + tail -n +2 coverage-main.txt >> coverage.txt + tail -n +2 coverage-test-go.txt >> coverage.txt + + - name: Test with embedded emulator env + env: + LLGO_EMBED_TESTS: "1" + run: go test -v -timeout 60m ./cl -run '^TestRunEmbedEmulator$' + + - name: Check std symbol coverage + run: bash doc/_readme/scripts/check_std_cover.sh - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v7 + with: + token: ${{secrets.CODECOV_TOKEN}} + + dev-lto-globaldce: + name: Dev LTO GlobalDCE + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Install dependencies + uses: ./.github/actions/setup-deps + with: + llvm-version: 19 + + - name: Set up Go + uses: ./.github/actions/setup-go + + - name: Setup demo dependencies + uses: ./.github/actions/setup-demo-deps + + - name: Set LLGO_ROOT + run: echo "LLGO_ROOT=$GITHUB_WORKSPACE" >> $GITHUB_ENV + + - name: Build llgo with dev tag + run: go install -tags=dev ./cmd/llgo + + - name: Build LTO plugin + if: runner.os == 'Linux' + run: | + set -euo pipefail + + llvm_config="$(command -v llvm-config || command -v llvm-config-19)" + cmake -S ltoplugin -B ltoplugin/build \ + -DLLVM_DIR="$("${llvm_config}" --cmakedir)" \ + -DCMAKE_BUILD_TYPE=Release + cmake --build ltoplugin/build --config Release + + echo "LLGO_LTO_PLUGIN=$GITHUB_WORKSPACE/ltoplugin/build/LLGOLTOPlugin.so" >> "$GITHUB_ENV" + + - name: Run dev LTO GlobalDCE tests and demos with coverage + run: | + set -euo pipefail + + go test -tags=dev -timeout 30m -covermode=atomic \ + -coverprofile=coverage-dev-globaldce-unit.txt \ + -run '^TestDevLTOGlobalDCE' \ + ./ssa ./internal/build ./internal/crosscompile ./cmd/internal/flags ./internal/cabi + go tool cover -func=coverage-dev-globaldce-unit.txt + + go test -tags=dev -timeout 30m -covermode=atomic \ + -coverpkg=github.com/xgo-dev/llgo/ssa,github.com/xgo-dev/llgo/internal/build,github.com/xgo-dev/llgo/internal/crosscompile \ + -coverprofile=coverage-dev-lto-globaldce-cl.txt \ + -run '^TestRunAndTestFromTestlto/globaldce_' \ + ./cl + go tool cover -func=coverage-dev-lto-globaldce-cl.txt + + go test -tags=dev -timeout 30m -covermode=atomic \ + -coverpkg=github.com/xgo-dev/llgo/ssa,github.com/xgo-dev/llgo/internal/build,github.com/xgo-dev/llgo/internal/crosscompile \ + -coverprofile=coverage-dev-lto-globaldce-symbols.txt \ + -run '^TestBuildAndCheckSymbolsFromTestlto/globaldce_' \ + ./cl + go tool cover -func=coverage-dev-lto-globaldce-symbols.txt + + go test -tags=dev -timeout 30m -covermode=atomic \ + -coverpkg=github.com/xgo-dev/llgo/ssa,github.com/xgo-dev/llgo/internal/build,github.com/xgo-dev/llgo/internal/crosscompile \ + -coverprofile=coverage-dev-lto-plugin-cl.txt \ + -run '^(TestRunAndTestFromTestltoLTOPlugin|TestBuildAndCheckSymbolsFromTestltoLTOPlugin)' \ + ./cl + go tool cover -func=coverage-dev-lto-plugin-cl.txt + + LLGO_DEMO_LLGORUN_FLAGS="-lto=full -globaldce" \ + bash .github/workflows/test_demo.sh + + - name: Upload dev LTO GlobalDCE coverage reports to Codecov + continue-on-error: true + uses: codecov/codecov-action@v7 with: token: ${{secrets.CODECOV_TOKEN}} + files: coverage-dev-globaldce-unit.txt,coverage-dev-lto-globaldce-cl.txt,coverage-dev-lto-globaldce-symbols.txt,coverage-dev-lto-plugin-cl.txt + flags: dev-lto-globaldce + + # Tests deletion of unreachable Go methods. + # See https://github.com/xgo-dev/llgo/issues/1853. + dev-go-methoddrop: + name: Dev Go Method Drop + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Install dependencies + uses: ./.github/actions/setup-deps + with: + llvm-version: 19 + + - name: Set up Go + uses: ./.github/actions/setup-go + + - name: Setup demo dependencies + uses: ./.github/actions/setup-demo-deps + + - name: Set LLGO_ROOT + run: echo "LLGO_ROOT=$GITHUB_WORKSPACE" >> $GITHUB_ENV + + - name: Build llgo with dev tag + run: go install -tags=dev ./cmd/llgo + + - name: Run Go method drop tests + run: go test -tags=dev -timeout 30m -run '^TestBuildAndCheckSymbolsFromTestdrop$' ./cl + + - name: Run Go deadcode-drop demos + run: | + LLGO_DEMO_LLGORUN_FLAGS="-deadcodedrop" \ + bash .github/workflows/test_demo.sh diff --git a/.github/workflows/goroot.yml b/.github/workflows/goroot.yml new file mode 100644 index 0000000000..ec9af260da --- /dev/null +++ b/.github/workflows/goroot.yml @@ -0,0 +1,234 @@ +name: GOROOT + +on: + workflow_dispatch: + schedule: + # 02:00 Asia/Shanghai (18:00 UTC on the previous day). + - cron: "0 18 * * *" + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + goroot: + name: GOROOT (${{ matrix.lane }}, ${{ matrix.os }}, Go ${{ matrix.go-version }}, shard ${{ matrix.shard-index }}/4) + timeout-minutes: 180 + env: + GOPROXY: https://proxy.golang.org,direct + LLGO_GOROOT_HEARTBEAT_SECONDS: "60" + LLGO_GOROOT_VERBOSE: "1" + strategy: + fail-fast: false + matrix: + # These are reproducibility pins, not floating series selectors. + # Upgrade them together in a dedicated toolchain-update PR. + os: [macos-latest, ubuntu-latest] + go-version: ["1.25.0", "1.26.5"] + shard-index: ["0", "1", "2", "3"] + include: + - go-version: "1.25.0" + lane: compatibility + - go-version: "1.26.5" + lane: primary + # Keep both supported runtime generations on Linux and macOS. Go 1.25 + # is intentionally omitted because it is not a compatibility target. + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v7 + + - name: Install dependencies + uses: ./.github/actions/setup-deps + with: + llvm-version: 19 + + - name: Set up Go + uses: ./.github/actions/setup-go + with: + go-version: ${{ matrix.go-version }} + + - name: Show toolchain versions + run: | + go version + go env GOPROXY GOMODCACHE + clang --version + + - name: Download Go modules + timeout-minutes: 10 + run: go mod download + + - name: Run GOROOT runner + run: | + min_swap_free_mib=512 + if [[ "$RUNNER_OS" == "macOS" ]]; then + # macOS manages swap dynamically; a low instantaneous free value + # does not mean the runner cannot make forward progress. + min_swap_free_mib=0 + fi + set +e + bash dev/test_goroot.sh -- \ + -directive-mode ci \ + -min-swap-free-mib "$min_swap_free_mib" \ + -progress 60s \ + -shard-index "${{ matrix.shard-index }}" \ + -shard-total "4" 2>&1 | tee "$RUNNER_TEMP/goroot.log" + runner_status=${PIPESTATUS[0]} + set -e + exit "$runner_status" + + - name: Summarize GOROOT shard + if: always() + env: + MATRIX_OS: ${{ matrix.os }} + GO_VERSION: ${{ matrix.go-version }} + SHARD_INDEX: ${{ matrix.shard-index }} + run: | + log="$RUNNER_TEMP/goroot.log" + report_dir="$RUNNER_TEMP/goroot-report" + mkdir -p "$report_dir" + touch "$log" + + platform=linux/amd64 + if [[ "$MATRIX_OS" == "macos-latest" ]]; then + platform=darwin/arm64 + fi + + selected=$(sed -nE 's/.* shard=[^ ]+ cases=([0-9]+) directive_mode=.*/\1/p' "$log" | tail -1) + observed=$(grep -Ec -- '--- (PASS|FAIL|SKIP): TestGoRootRunCases/' "$log" || true) + failed=$(grep -Ec -- '--- FAIL: TestGoRootRunCases/' "$log" || true) + skipped=$(grep -Ec -- '--- SKIP: TestGoRootRunCases/' "$log" || true) + passed=$((observed - failed - skipped)) + if [[ -z "$selected" ]]; then + selected='?' + fi + + report_id="${platform//\//-}-${GO_VERSION}-${SHARD_INDEX}" + printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \ + "$platform" "$GO_VERSION" "$SHARD_INDEX" "$selected" \ + "$observed" "$passed" "$failed" "$skipped" \ + >"$report_dir/summary-$report_id.tsv" + sed -nE 's|.*--- FAIL: TestGoRootRunCases/([^ ]+).*|\1|p' "$log" | + while IFS= read -r case_path; do + printf '%s\t%s\t%s\t%s\n' \ + "$platform" "$GO_VERSION" "$SHARD_INDEX" "$case_path" + done >"$report_dir/failures-$report_id.tsv" + + { + echo "### GOROOT $platform · Go $GO_VERSION · shard $SHARD_INDEX/4" + echo + echo '| Selected | Observed | Passed | Failed | Skipped |' + echo '|---:|---:|---:|---:|---:|' + echo "| $selected | $observed | $passed | $failed | $skipped |" + echo + echo '_Passed means the runner classification succeeded; expected xfail/not-applicable failures and classified flakes are counted as Passed._' + echo + echo '**Failed case paths**' + if [[ "$failed" -eq 0 ]]; then + echo + echo '- none' + else + # shellcheck disable=SC2016 # The backreference is expanded by sed. + sed -nE 's|.*--- FAIL: TestGoRootRunCases/([^ ]+).*|- `\1`|p' "$log" + fi + } >>"$GITHUB_STEP_SUMMARY" + + - name: Upload GOROOT shard statistics + if: always() + uses: actions/upload-artifact@v7 + with: + name: goroot-stat-${{ matrix.os }}-${{ matrix.go-version }}-${{ matrix.shard-index }} + path: ${{ runner.temp }}/goroot-report + if-no-files-found: error + retention-days: 7 + + goroot-summary: + name: GOROOT summary + if: always() + needs: goroot + runs-on: ubuntu-latest + steps: + - name: Download shard statistics + uses: actions/download-artifact@v8 + with: + pattern: goroot-stat-* + path: reports + merge-multiple: true + + - name: Publish GOROOT run summary + env: + GOROOT_RESULT: ${{ needs.goroot.result }} + run: | + summary_tsv="$RUNNER_TEMP/goroot-summary.tsv" + failure_tsv="$RUNNER_TEMP/goroot-failures.tsv" + find reports -type f -name 'summary-*.tsv' -exec cat {} + >"$summary_tsv" + find reports -type f -name 'failures-*.tsv' -exec cat {} + >"$failure_tsv" + + report_count=$(wc -l <"$summary_tsv" | tr -d ' ') + write_row() { + local label=$1 + local platform=$2 + local version=${3:-} + local expected_shards=$4 + awk -F '\t' \ + -v label="$label" \ + -v platform="$platform" \ + -v version="$version" \ + -v expected="$expected_shards" ' + $1 == platform && (version == "" || $2 == version) { + shards++ + if ($4 ~ /^[0-9]+$/) { + selected += $4 + } else { + selected_unknown = 1 + } + observed += $5 + passed += $6 + failed += $7 + skipped += $8 + } + END { + selected_text = selected_unknown ? "?" : selected + printf "| %s | %d/%d | %s | %d | %d | %d | %d |\n", \ + label, shards, expected, selected_text, observed, passed, failed, skipped + } + ' "$summary_tsv" + } + + { + echo '## GOROOT run summary' + echo + echo "Received $report_count/16 shard reports." + echo + echo '| Platform / toolchain | Shards | Selected | Observed | Passed | Failed | Skipped |' + echo '|---|---:|---:|---:|---:|---:|---:|' + write_row 'Darwin · Go 1.25.0' darwin/arm64 1.25.0 4 + write_row 'Darwin · Go 1.26.5' darwin/arm64 1.26.5 4 + write_row '**Darwin total**' darwin/arm64 '' 8 + write_row 'Linux · Go 1.25.0' linux/amd64 1.25.0 4 + write_row 'Linux · Go 1.26.5' linux/amd64 1.26.5 4 + write_row '**Linux total**' linux/amd64 '' 8 + echo + echo '_Passed means the runner classification succeeded; expected xfail/not-applicable failures and classified flakes are counted as Passed._' + echo + echo '### Failed case paths' + if [[ -s "$failure_tsv" ]]; then + sort -u "$failure_tsv" | + while IFS=$'\t' read -r platform version shard case_path; do + echo "- \`$case_path\` — $platform, Go $version, shard $shard/4" + done + else + echo '- none' + fi + } >>"$GITHUB_STEP_SUMMARY" + + if [[ "$report_count" -ne 16 ]]; then + echo "error: expected 16 shard reports, got $report_count" >&2 + exit 1 + fi + if [[ "$GOROOT_RESULT" != success ]]; then + echo "error: one or more GOROOT shards did not succeed" >&2 + exit 1 + fi diff --git a/.github/workflows/install-esp-qemu.sh b/.github/workflows/install-esp-qemu.sh new file mode 100755 index 0000000000..9ab7a4379e --- /dev/null +++ b/.github/workflows/install-esp-qemu.sh @@ -0,0 +1,67 @@ +#!/bin/bash +set -euo pipefail + +# Installation directory (from argument or default) +INSTALL_DIR="${1:-.cache/qemu}" + +# Detect platform +OS=$(uname -s | tr '[:upper:]' '[:lower:]') +ARCH=$(uname -m) + +# Map architecture names +case "$ARCH" in + x86_64|amd64) + ARCH="x86_64" + ;; + aarch64|arm64) + ARCH="aarch64" + ;; + *) + echo "Unsupported architecture: $ARCH" + exit 1 + ;; +esac + +# Map OS names +case "$OS" in + darwin) + PLATFORM="${ARCH}-apple-darwin" + ;; + linux) + PLATFORM="${ARCH}-linux-gnu" + ;; + *) + echo "Unsupported OS: $OS" + exit 1 + ;; +esac + +RELEASE_TAG="esp-develop-9.2.2-20250817" +VERSION="esp_develop_9.2.2_20250817" +PACKAGES=( + "qemu-riscv32-softmmu-${VERSION}-${PLATFORM}.tar.xz" + "qemu-xtensa-softmmu-${VERSION}-${PLATFORM}.tar.xz" +) + +echo "Detected platform: $PLATFORM" +echo "Installing to: ${INSTALL_DIR}" + +# Download and extract +rm -rf "$INSTALL_DIR" +mkdir -p "$INSTALL_DIR" + +for filename in "${PACKAGES[@]}"; do + url="https://github.com/espressif/qemu/releases/download/${RELEASE_TAG}/${filename}" + echo "Downloading: $url" + curl -fsSL "$url" | tar -xJ -C "$INSTALL_DIR" --strip-components=1 +done + +# Verify installation +for exe in qemu-system-riscv32 qemu-system-xtensa; do + if [ ! -x "${INSTALL_DIR}/bin/${exe}" ]; then + echo "Error: ${exe} not found after extraction" + exit 1 + fi +done + +echo "ESP QEMU (riscv32 + xtensa) installed successfully to: ${INSTALL_DIR}" diff --git a/.github/workflows/llgo.yml b/.github/workflows/llgo.yml index 72950ddef6..d37781368e 100644 --- a/.github/workflows/llgo.yml +++ b/.github/workflows/llgo.yml @@ -5,9 +5,8 @@ name: LLGo on: push: - branches: - - "**" - - "!dependabot/**" + branches: + - main pull_request: branches: ["**"] @@ -16,78 +15,48 @@ concurrency: cancel-in-progress: true jobs: - download-model: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - name: Download model file - run: | - mkdir -p ./_demo/llama2-c - wget -P ./_demo/llama2-c https://huggingface.co/karpathy/tinyllamas/resolve/main/stories15M.bin - - - name: Upload model as artifact - uses: actions/upload-artifact@v4 - with: - name: llama2-model - path: ./_demo/llama2-c/stories15M.bin - retention-days: 1 - llgo: - needs: download-model - continue-on-error: true - timeout-minutes: 30 + name: llgo (${{ matrix.lane }}, ${{ matrix.os }}, LLVM ${{ matrix.llvm }}, Go ${{ matrix.go }}) + continue-on-error: ${{ matrix.lane == 'compatibility' }} + timeout-minutes: 60 strategy: matrix: - os: - - macos-latest - - ubuntu-24.04 - llvm: [19] - go: ["1.21.13", "1.22.12", "1.23.6", "1.24.2"] + # Compatibility results are tied to these exact patch releases. + # Keep the supported Go 1.25 and 1.26 endpoints on macOS so user + # projects exercise both runtime generations there. macOS Intel is + # covered by the release artifact smoke test, avoiding a duplicate + # 35-minute demo job. + include: + - os: ubuntu-latest + llvm: 19 + go: "1.25.0" + lane: compatibility + - os: ubuntu-latest + llvm: 19 + go: "1.26.5" + lane: primary + - os: macos-latest + llvm: 19 + go: "1.25.0" + lane: compatibility + - os: macos-latest + llvm: 19 + go: "1.26.5" + lane: primary runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Install dependencies uses: ./.github/actions/setup-deps with: llvm-version: ${{matrix.llvm}} - - name: Download model artifact - uses: actions/download-artifact@v5 - with: - name: llama2-model - path: ./_demo/llama2-c/ - - name: Download platform-specific demo libs - run: | - if ${{ startsWith(matrix.os, 'macos') }}; then - DEMO_PKG="cargs_darwin_arm64.zip" - else - DEMO_PKG="cargs_linux_amd64.zip" - fi - - mkdir -p ./_demo/cargs/libs - cd ./_demo/cargs/libs - wget https://github.com/goplus/llpkg/releases/download/cargs/v1.0.0/${DEMO_PKG} - unzip ${DEMO_PKG} - - # Process pc template files - replace {{.Prefix}} with actual path - ACTUAL_PREFIX="$(pwd)" - for tmpl in lib/pkgconfig/*.pc.tmpl; do - pc_file="${tmpl%.tmpl}" - sed "s|{{.Prefix}}|${ACTUAL_PREFIX}|g" "$tmpl" > "$pc_file" - done - - echo "PKG_CONFIG_PATH=${ACTUAL_PREFIX}/lib/pkgconfig:${PKG_CONFIG_PATH}" >> $GITHUB_ENV - - name: Install further optional dependencies for demos - run: | - py_deps=( - numpy # for github.com/goplus/lib/py/numpy - torch # for github.com/goplus/lib/py/torch - ) - pip3.12 install --break-system-packages "${py_deps[@]}" + - name: Install embedded dependencies + uses: ./.github/actions/setup-embed-deps + - name: Setup demo dependencies + uses: ./.github/actions/setup-demo-deps - name: Set up Go for build uses: ./.github/actions/setup-go - with: - go-version: "1.24.2" - name: Install run: | @@ -95,7 +64,7 @@ jobs: echo "LLGO_ROOT=$GITHUB_WORKSPACE" >> $GITHUB_ENV - name: Set up Go for testing - uses: actions/setup-go@v6 + uses: ./.github/actions/setup-go with: go-version: ${{matrix.go}} @@ -104,7 +73,7 @@ jobs: echo "Testing demo without RPATH (should fail)..." export LLGO_FULL_RPATH=false pkg-config --libs cargs - if (cd ./_demo/cargs && llgo run .); then + if (cd ./_demo/c/cargs && llgo run .); then echo "ERROR: cargs demo should have failed without RPATH!" exit 1 else @@ -112,18 +81,39 @@ jobs: fi - name: Test demos + run: bash .github/workflows/test_demo.sh + + - name: Test demos (embedded target) + run: bash .github/workflows/test_demo.sh --embedded + + - name: Test C header generation run: | - # TODO(lijie): force python3-embed to be linked with python-3.12-embed - # Currently, python3-embed is python-3.13-embed, doesn't work with pytorch - # Will remove this after pytorch is fixed. - pcdir=$HOME/pc - mkdir -p $pcdir - libdir=$(pkg-config --variable=libdir python-3.12-embed) - echo "libdir: $libdir" - ln -s $libdir/pkgconfig/python-3.12-embed.pc $pcdir/python3-embed.pc - export PKG_CONFIG_PATH=$pcdir:${PKG_CONFIG_PATH} - export LLGO_FULL_RPATH=true - bash .github/workflows/test_demo.sh + echo "Testing C header generation in different build modes..." + cd _demo/go/export + chmod +x test.sh + ./test.sh + + - name: Test export with different symbol names on embedded targets + run: | + echo "Testing //export with different symbol names on embedded targets..." + cd _demo/embed/export + chmod +x verify_export.sh + ./verify_export.sh + + - name: Test ESP serial smoke (build + emulator) + run: | + echo "Testing ESP32/ESP32-C3 build + emulator smoke..." + cd _demo/embed + chmod +x test-esp-serial-startup.sh + ./test-esp-serial-startup.sh + + - name: Test ESP32-C3 startup regression + run: | + echo "Testing ESP32-C3 startup regressions..." + pip3 install --break-system-packages esptool==5.1.0 + cd _demo/embed + chmod +x test_esp32c3_startup.sh + ./test_esp32c3_startup.sh - name: _xtool build tests run: | @@ -133,25 +123,52 @@ jobs: - name: Show test result run: cat result.md - - name: LLDB tests - if: ${{startsWith(matrix.os, 'macos')}} + - name: Install LLDB for integration tests + if: ${{ matrix.os == 'ubuntu-latest' && matrix.lane == 'primary' }} + run: sudo apt-get install -y lldb-${{matrix.llvm}} + + - name: LLDB integration tests + if: ${{ matrix.lane == 'primary' }} run: | echo "Test lldb with llgo plugin on ${{matrix.os}} with LLVM ${{matrix.llvm}}" - bash _lldb/runtest.sh -v + bash cmd/llgo/lldbtest/runtest.sh -v + + - name: DWARF standard tests + if: ${{ startsWith(matrix.os, 'macos') && matrix.os != 'macos-15-intel' }} + run: go test -timeout 15m ./internal/build -run '^TestStandardDWARF$' -count=1 -v test: - continue-on-error: true - timeout-minutes: 30 + name: test (${{ matrix.lane }}, ${{ matrix.os }}, LLVM ${{ matrix.llvm }}, Go ${{ matrix.go }}, shard ${{ matrix.shard }}) + continue-on-error: ${{ matrix.lane == 'compatibility' }} + timeout-minutes: ${{ startsWith(matrix.os, 'macos') && 45 || 30 }} strategy: matrix: + # Keep compatibility and primary toolchains pinned to exact patches. os: - macos-latest - - ubuntu-24.04 + - ubuntu-latest llvm: [19] - go: ["1.24.2"] + go: ["1.25.0", "1.26.5"] + # In-command package parallelism lets Ubuntu use two shards while + # retaining headroom for the serial std build-mode checks. + shard: ["0", "1"] + include: + - go: "1.25.0" + lane: compatibility + - go: "1.26.5" + lane: primary + exclude: + # The full demo lane above exercises Go 1.25 user-project/runtime + # compatibility on macOS. Keep the much larger per-package + # compatibility matrix on Ubuntu and use one parallel primary shard + # on macOS. + - os: macos-latest + go: "1.25.0" + - os: macos-latest + shard: "1" runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Install dependencies uses: ./.github/actions/setup-deps with: @@ -166,8 +183,6 @@ jobs: - name: Set up Go for build uses: ./.github/actions/setup-go - with: - go-version: "1.24.2" - name: Install run: | @@ -175,34 +190,83 @@ jobs: echo "LLGO_ROOT=$GITHUB_WORKSPACE" >> $GITHUB_ENV - name: Set up Go for testing - uses: actions/setup-go@v6 + uses: ./.github/actions/setup-go with: go-version: ${{matrix.go}} - - name: run llgo test + env: + SHARD_INDEX: ${{ matrix.shard }} + SHARD_TOTAL: ${{ startsWith(matrix.os, 'macos') && '1' || '2' }} + TEST_JOBS: ${{ startsWith(matrix.os, 'macos') && '3' || '4' }} run: | - llgo test ./... + set -euo pipefail + + pkgs=() + while IFS= read -r pkg; do + pkgs+=("${pkg}") + done < <(go list -tags=llgo ./test/... | sort) + + selected=() + for i in "${!pkgs[@]}"; do + if (( i % SHARD_TOTAL == SHARD_INDEX )); then + selected+=("${pkgs[$i]}") + fi + done + + echo "Shard: ${SHARD_INDEX}/${SHARD_TOTAL}, selected: ${#selected[@]} package(s)" + if [ "${#selected[@]}" -eq 0 ]; then + echo "No packages in this shard." + exit 0 + fi + printf ' %s\n' "${selected[@]}" + + std_pkgs=() + for pkg in "${selected[@]}"; do + if [[ "${{ matrix.os }}" == ubuntu-latest && "${{ matrix.go }}" == 1.26.5 && "${pkg}" == */test/std/* ]]; then + std_pkgs+=("${pkg}") + fi + done + + echo "==> llgo test -p=${TEST_JOBS} (${#selected[@]} packages)" + SECONDS=0 + llgo test -p="${TEST_JOBS}" -timeout=20m -bench='^BenchmarkGo126' -benchtime=1x "${selected[@]}" + echo "==> llgo test done (${SECONDS}s)" + + if [[ "${#std_pkgs[@]}" -ne 0 ]]; then + dev/test_std_buildmodes.sh "${std_pkgs[@]}" + fi hello: - continue-on-error: true + name: hello (${{ matrix.lane }}, ${{ matrix.os }}, LLVM ${{ matrix.llvm }}, Go ${{ matrix.go }}) + continue-on-error: ${{ matrix.lane == 'compatibility' }} timeout-minutes: 30 strategy: matrix: - os: [ubuntu-24.04, macos-latest] - llvm: [19] - go: ["1.21.13", "1.22.12", "1.23.6", "1.24.2"] + include: + - os: ubuntu-latest + llvm: 19 + go: "1.25.0" + lane: compatibility + - os: ubuntu-latest + llvm: 19 + go: "1.26.5" + lane: primary + # Keep the Go 1.26 user-module compatibility matrix on both host + # platforms; release artifact smoke tests alone only cover go 1.26. + - os: macos-latest + llvm: 19 + go: "1.26.5" + lane: primary runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Install dependencies uses: ./.github/actions/setup-deps with: llvm-version: ${{matrix.llvm}} - - name: Set up Go 1.23 for building llgo + - name: Set up Go for building llgo uses: ./.github/actions/setup-go - with: - go-version: "1.24.2" - name: Install llgo run: | @@ -215,43 +279,45 @@ jobs: go-version: ${{matrix.go}} - name: Test Hello World with go.mod 1.21 - if: startsWith(matrix.go, '1.21') || startsWith(matrix.go, '1.22') || startsWith(matrix.go, '1.23') || startsWith(matrix.go, '1.24') + if: startsWith(matrix.go, '1.25') || startsWith(matrix.go, '1.26') uses: ./.github/actions/test-helloworld with: go-version: ${{matrix.go}} mod-version: "1.21" - name: Test Hello World with go.mod 1.22 - if: startsWith(matrix.go, '1.22') || startsWith(matrix.go, '1.23') || startsWith(matrix.go, '1.24') + if: startsWith(matrix.go, '1.25') || startsWith(matrix.go, '1.26') uses: ./.github/actions/test-helloworld with: go-version: ${{matrix.go}} mod-version: "1.22" - - name: Test Hello World with go.mod 1.23 - if: startsWith(matrix.go, '1.23') || startsWith(matrix.go, '1.24') + - name: Test Hello World with go.mod 1.24 + if: startsWith(matrix.go, '1.25') || startsWith(matrix.go, '1.26') uses: ./.github/actions/test-helloworld with: go-version: ${{matrix.go}} - mod-version: "1.23" + mod-version: "1.24" - - name: Test Hello World with go.mod 1.24 - if: startsWith(matrix.go, '1.24') + - name: Test Hello World with go.mod 1.26 + if: startsWith(matrix.go, '1.26') uses: ./.github/actions/test-helloworld with: go-version: ${{matrix.go}} - mod-version: "1.24" + mod-version: "1.26" cross-compile: - continue-on-error: true timeout-minutes: 30 strategy: matrix: - os: [macos-latest] + # WASI output is host-independent; keep this expensive WAMR build on + # Ubuntu. Native Darwin coverage remains in the primary LLGo lanes and + # release artifact smoke tests. + os: [ubuntu-latest] llvm: [19] runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Install dependencies uses: ./.github/actions/setup-deps with: @@ -259,14 +325,12 @@ jobs: - name: Set up Go for building llgo uses: ./.github/actions/setup-go - with: - go-version: "1.24.2" - name: Install wamr run: | - git clone https://github.com/bytecodealliance/wasm-micro-runtime.git - mkdir wasm-micro-runtime/product-mini/platforms/darwin/build - cd wasm-micro-runtime/product-mini/platforms/darwin/build + git clone --branch WAMR-2.4.4 --depth 1 https://github.com/bytecodealliance/wasm-micro-runtime.git + mkdir wasm-micro-runtime/product-mini/platforms/linux/build + cd wasm-micro-runtime/product-mini/platforms/linux/build cmake -D WAMR_BUILD_EXCE_HANDLING=1 -D WAMR_BUILD_FAST_INTERP=0 -DWAMR_BUILD_SHARED_MEMORY=1 -DWAMR_BUILD_LIB_WASI_THREADS=1 -DWAMR_BUILD_LIB_PTHREAD=1 -DCMAKE_BUILD_TYPE=Debug -DWAMR_BUILD_DEBUG_INTERP=1 .. make -j8 echo "$PWD" >> $GITHUB_PATH @@ -278,9 +342,9 @@ jobs: - name: Test Cross Compilation (wasm) shell: bash - working-directory: _demo + working-directory: _demo/c run: | - echo "Testing cross-compilation wasm with Go 1.24.2" + echo "Testing cross-compilation wasm with $(go env GOVERSION)" # Compile for wasm architecture GOOS=wasip1 GOARCH=wasm llgo build -o hello -tags=nogc -v ./helloc @@ -290,3 +354,42 @@ jobs: # Run the wasm binary using llgo_wasm iwasm --stack-size=819200000 --heap-size=800000000 hello.wasm + + wasm-runtime: + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + go: ["1.25.0", "1.26.5"] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: Install dependencies + uses: ./.github/actions/setup-deps + with: + llvm-version: 19 + + - name: Set up Emscripten + uses: emscripten-core/setup-emsdk@v15 + with: + version: "4.0.21" + + - name: Set up Go for building llgo + uses: ./.github/actions/setup-go + + - name: Install llgo + run: | + go install ./... + echo "LLGO_ROOT=$GITHUB_WORKSPACE" >> $GITHUB_ENV + + - name: Set up Go for testing + uses: ./.github/actions/setup-go + with: + go-version: ${{matrix.go}} + + - name: Build standard runtime for wasm + shell: bash + run: | + GOOS=js GOARCH=wasm llgo build -o "$RUNNER_TEMP/runtime-js.wasm" ./internal/build/testdata/wasm-runtime + GOOS=wasip1 GOARCH=wasm llgo build -o "$RUNNER_TEMP/runtime-wasip1.wasm" ./internal/build/testdata/wasm-runtime + file "$RUNNER_TEMP/runtime-js.wasm" "$RUNNER_TEMP/runtime-wasip1.wasm" diff --git a/.github/workflows/model-demo.yml b/.github/workflows/model-demo.yml new file mode 100644 index 0000000000..89af920fcc --- /dev/null +++ b/.github/workflows/model-demo.yml @@ -0,0 +1,42 @@ +name: Model Demo + +on: + schedule: + - cron: "0 18 * * *" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + llama2: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v7 + + - name: Install dependencies + uses: ./.github/actions/setup-deps + with: + llvm-version: 19 + + - name: Set up Go + uses: ./.github/actions/setup-go + + - name: Install llgo + run: | + go install ./... + echo "LLGO_ROOT=$GITHUB_WORKSPACE" >> $GITHUB_ENV + + - name: Download llama2 model + run: | + mkdir -p ./_demo/c/llama2-c + curl --fail --location --retry 5 --retry-delay 60 --retry-all-errors \ + --output ./_demo/c/llama2-c/stories15M.bin \ + https://huggingface.co/karpathy/tinyllamas/resolve/main/stories15M.bin + + - name: Test llama2 demo + run: | + cd ./_demo/c/llama2-c + llgo run . diff --git a/.github/workflows/notify-benchmarks.yml b/.github/workflows/notify-benchmarks.yml new file mode 100644 index 0000000000..5d377841db --- /dev/null +++ b/.github/workflows/notify-benchmarks.yml @@ -0,0 +1,110 @@ +name: Notify benchmarks + +on: + # A push to main is the authoritative post-merge state, including squash and + # rebase merges. The classifier below decides whether the change affects the + # compiler and should start the expensive benchmarks. + push: + branches: [main] + pull_request: + paths: + - .github/scripts/classify_benchmark_changes.py + - .github/scripts/test_classify_benchmark_changes.py + - .github/workflows/notify-benchmarks.yml + # Compatibility is intentionally release-based: publishing a stable release + # or prerelease records one durable open-source test result for that tag. + release: + types: [published] + +permissions: + contents: read + +jobs: + classifier-tests: + if: github.event_name == 'pull_request' + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + - name: Test benchmark change classifier + run: python3 .github/scripts/test_classify_benchmark_changes.py + + dispatch: + if: github.repository == 'xgo-dev/llgo' && github.event_name != 'pull_request' + runs-on: ubuntu-24.04 + env: + # Set this repository variable to the temporary personal repository + # before the migration, then remove it to use xgo-dev/benchmarks. + BENCHMARKS_REPOSITORY: ${{ vars.BENCHMARKS_REPOSITORY || 'xgo-dev/benchmarks' }} + BENCHMARKS_DISPATCH_TOKEN: ${{ secrets.BENCHMARKS_DISPATCH_TOKEN }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + steps: + - name: Check out LLGo history + if: github.event_name == 'push' + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Classify changes + if: github.event_name == 'push' + id: changes + env: + BASE_SHA: ${{ github.event.before }} + HEAD_SHA: ${{ github.sha }} + run: | + python3 .github/scripts/classify_benchmark_changes.py \ + --base "$BASE_SHA" \ + --head "$HEAD_SHA" \ + --format json \ + --github-output "$GITHUB_OUTPUT" \ + --github-summary "$GITHUB_STEP_SUMMARY" + + - name: Request benchmarks for this LLGo revision + if: github.event_name == 'release' || steps.changes.outputs.compiler == 'true' + run: | + set -euo pipefail + if [[ -z "$BENCHMARKS_DISPATCH_TOKEN" ]]; then + echo "BENCHMARKS_DISPATCH_TOKEN is not configured" >&2 + exit 1 + fi + + if [[ "$GITHUB_EVENT_NAME" == "release" ]]; then + if [[ -z "$RELEASE_TAG" ]] || ! git check-ref-format "refs/tags/$RELEASE_TAG"; then + echo "invalid release tag: ${RELEASE_TAG:-missing}" >&2 + exit 1 + fi + tag_ref="refs/tags/$RELEASE_TAG" + tag_rows="$(git ls-remote "https://github.com/${GITHUB_REPOSITORY}.git" \ + "$tag_ref" "$tag_ref^{}")" + llgo_commit="$(awk -v ref="$tag_ref^{}" '$2 == ref { print $1; exit }' <<<"$tag_rows")" + if [[ -z "$llgo_commit" ]]; then + llgo_commit="$(awk -v ref="$tag_ref" '$2 == ref { print $1; exit }' <<<"$tag_rows")" + fi + event_type=llgo-tag-released + else + llgo_commit="$GITHUB_SHA" + event_type=llgo-main-updated + fi + if [[ ! "$llgo_commit" =~ ^[0-9a-f]{40}$ ]]; then + echo "invalid LLGo commit for ${RELEASE_TAG:-$GITHUB_REF}: ${llgo_commit:-missing}" >&2 + exit 1 + fi + + payload="$(jq -cn \ + --arg event_type "$event_type" \ + --arg source_repository "$GITHUB_REPOSITORY" \ + --arg llgo_repository "$GITHUB_REPOSITORY" \ + --arg llgo_commit "$llgo_commit" \ + --arg llgo_tag "$RELEASE_TAG" \ + --arg source_run_url "https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \ + '{event_type: $event_type, client_payload: {source_repository: $source_repository, llgo_repository: $llgo_repository, llgo_commit: $llgo_commit, llgo_tag: $llgo_tag, source_run_url: $source_run_url}}')" + + curl --fail-with-body --location --request POST \ + --header 'Accept: application/vnd.github+json' \ + --header "Authorization: Bearer ${BENCHMARKS_DISPATCH_TOKEN}" \ + --header 'X-GitHub-Api-Version: 2022-11-28' \ + "https://api.github.com/repos/${BENCHMARKS_REPOSITORY}/dispatches" \ + --data "$payload" + + - name: Skip benchmarks for non-compiler changes + if: github.event_name == 'push' && steps.changes.outputs.compiler != 'true' + run: echo "No compiler changes; benchmark dispatch is not required." diff --git a/.github/workflows/populate_darwin_sysroot.sh b/.github/workflows/populate_darwin_sysroot.sh deleted file mode 100755 index f0eaaab434..0000000000 --- a/.github/workflows/populate_darwin_sysroot.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash - -set -e - -TMPDIR="$(mktemp -d)" -export TMPDIR -trap 'rm -rf "${TMPDIR}"' EXIT - -DARWIN_AMD64_LLVM_PREFIX=.sysroot/darwin/amd64/usr/local/opt/llvm@19 -DARWIN_ARM64_LLVM_PREFIX=.sysroot/darwin/arm64/opt/homebrew/opt/llvm@19 -mkdir -p "${DARWIN_AMD64_LLVM_PREFIX}" "${DARWIN_ARM64_LLVM_PREFIX}" - -BREW_LLVM_FORMULA_JSON="$(mktemp)" -curl -fsSL https://formulae.brew.sh/api/formula/llvm@19.json > "${BREW_LLVM_FORMULA_JSON}" -BREW_LLVM_AMD64_BOTTLE_URL=$(jq -r '.bottle.stable.files.sonoma.url' "${BREW_LLVM_FORMULA_JSON}") -BREW_LLVM_ARM64_BOTTLE_URL=$(jq -r '.bottle.stable.files.arm64_sonoma.url' "${BREW_LLVM_FORMULA_JSON}") -curl -fsSL -H "Authorization: Bearer QQ==" "${BREW_LLVM_AMD64_BOTTLE_URL}" | tar -xzf - --strip-components=2 -C "${DARWIN_AMD64_LLVM_PREFIX}" -curl -fsSL -H "Authorization: Bearer QQ==" "${BREW_LLVM_ARM64_BOTTLE_URL}" | tar -xzf - --strip-components=2 -C "${DARWIN_ARM64_LLVM_PREFIX}" - -patch_homebrew_lib_dir() { - local LIB_DIR="$1" - local HOMEBREW_PREFIX="$2" - for DYLIB_FILE in "${LIB_DIR}"/*.dylib; do - if [[ -f "${DYLIB_FILE}" ]]; then - ID=$(otool -D "${DYLIB_FILE}" | grep '@@HOMEBREW_PREFIX@@' | awk '{print $1}') - if [[ -n "${ID}" ]]; then - NEW_ID=${ID/'@@HOMEBREW_PREFIX@@'/${HOMEBREW_PREFIX}} - install_name_tool -id "${NEW_ID}" "${DYLIB_FILE}" - fi - - DEPS=$(otool -L "${DYLIB_FILE}" | grep '@@HOMEBREW_PREFIX@@' | awk '{print $1}') - for DEP in ${DEPS}; do - NEW_DEP=${DEP/'@@HOMEBREW_PREFIX@@'/${HOMEBREW_PREFIX}} - install_name_tool -change "${DEP}" "${NEW_DEP}" "${DYLIB_FILE}" - done - fi - done -} -patch_homebrew_lib_dir "${DARWIN_AMD64_LLVM_PREFIX}/lib" /usr/lib -patch_homebrew_lib_dir "${DARWIN_ARM64_LLVM_PREFIX}/lib" /opt/homebrew diff --git a/.github/workflows/populate_linux_sysroot.sh b/.github/workflows/populate_linux_sysroot.sh index b41d32527a..908a0d1d26 100755 --- a/.github/workflows/populate_linux_sysroot.sh +++ b/.github/workflows/populate_linux_sysroot.sh @@ -17,12 +17,7 @@ cat > "${POPULATE_LINUX_SYSROOT_SCRIPT}" << EOF export DEBIAN_FRONTEND=noninteractive apt-get update -apt-get install -y lsb-release gnupg2 wget rsync - -echo "deb http://apt.llvm.org/\$(lsb_release -cs)/ llvm-toolchain-\$(lsb_release -cs)-19 main" | tee /etc/apt/sources.list.d/llvm.list -wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - -apt-get update -apt-get install -y llvm-19-dev +apt-get install -y build-essential zlib1g-dev rsync error() { echo -e "\$1" >&2 @@ -61,6 +56,7 @@ exclude_list+=(--exclude "/run") exclude_list+=(--exclude "/sbin") exclude_list+=(--exclude "/srv") exclude_list+=(--exclude "/sys") +exclude_list+=(--exclude "/sysroot") exclude_list+=(--exclude "/tmp") exclude_list+=(--exclude "/usr/bin") exclude_list+=(--exclude "/usr/games") @@ -80,6 +76,7 @@ exclude_list+=(--exclude "/snap") exclude_list+=(--exclude "*python*") include_list+=(--include "*.a") +include_list+=(--include "*.o") include_list+=(--include "*.so") include_list+=(--include "*.so.*") include_list+=(--include "*.h") @@ -93,6 +90,9 @@ include_list+=(--include "/lib") include_list+=(--include "/lib32") include_list+=(--include "/lib64") include_list+=(--include "/libx32") +# libstdc++ has extensionless headers such as string, optional, and type_traits. +include_list+=(--include "/usr/include/c++/***") +include_list+=(--include "/usr/include/*-linux-gnu/c++/***") include_list+=(--include "*/") do-sync() { @@ -138,11 +138,8 @@ populate_linux_sysroot() { debian:bullseye \ /populate_linux_sysroot.sh } -populate_linux_sysroot amd64 "${LINUX_AMD64_PREFIX}" & -PID1=$! -populate_linux_sysroot arm64 "${LINUX_ARM64_PREFIX}" & -PID2=$! - -# Wait for both background processes to complete -wait $PID1 || exit $? -wait $PID2 || exit $? +# Docker's classic image store keeps only one platform for a tag. Pulling the +# same tag for two platforms concurrently can replace the image while the other +# container is starting. Populate the sysroots serially to keep the tag stable. +populate_linux_sysroot amd64 "${LINUX_AMD64_PREFIX}" +populate_linux_sysroot arm64 "${LINUX_ARM64_PREFIX}" diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index fef8f053ba..e7fe6ef01e 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -2,8 +2,10 @@ name: Release Build on: push: - branches: ["**"] - tags: ["*"] + branches: + - main + tags: + - "*" pull_request: branches: ["**"] @@ -11,65 +13,40 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true +env: + # Container tags cannot use Go patch selectors. Upgrade this exact pin in + # the same periodic toolchain-update PR as the setup-go default. + GORELEASER_CROSS_IMAGE: ghcr.io/goreleaser/goreleaser-cross:v1.26.4 + jobs: setup: runs-on: ubuntu-latest outputs: - darwin-cache-key: ${{ steps.cache-keys.outputs.darwin-key }} linux-cache-key: ${{ steps.cache-keys.outputs.linux-key }} steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@v7 - name: Calculate cache keys id: cache-keys run: | - DARWIN_KEY="darwin-sysroot-${{ hashFiles('.github/workflows/populate_darwin_sysroot.sh', '.github/workflows/release-build.yml') }}-v1.0.0" LINUX_KEY="linux-sysroot-${{ hashFiles('.github/workflows/populate_linux_sysroot.sh', '.github/workflows/release-build.yml') }}-v1.0.0" - echo "darwin-key=$DARWIN_KEY" >> $GITHUB_OUTPUT echo "linux-key=$LINUX_KEY" >> $GITHUB_OUTPUT - - populate-darwin-sysroot: - runs-on: macos-latest - timeout-minutes: 30 - needs: setup - steps: - - name: Check out code - uses: actions/checkout@v5 - - name: Check Darwin sysroot cache - id: cache-darwin-sysroot - uses: actions/cache/restore@v4 - with: - path: .sysroot/darwin.tar.gz - key: ${{ needs.setup.outputs.darwin-cache-key }} - lookup-only: true - - name: Populate Darwin sysroot - if: steps.cache-darwin-sysroot.outputs.cache-hit != 'true' - run: bash .github/workflows/populate_darwin_sysroot.sh - - name: Create Darwin sysroot tarball - if: steps.cache-darwin-sysroot.outputs.cache-hit != 'true' - run: tar -czvf .sysroot/darwin.tar.gz -C .sysroot darwin - - name: Save Darwin sysroot cache - if: steps.cache-darwin-sysroot.outputs.cache-hit != 'true' - uses: actions/cache/save@v4 - with: - path: .sysroot/darwin.tar.gz - key: ${{ needs.setup.outputs.darwin-cache-key }} populate-linux-sysroot: runs-on: ubuntu-latest needs: setup timeout-minutes: 30 steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@v7 - name: Check Linux sysroot cache id: cache-linux-sysroot - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: path: .sysroot/linux.tar.gz key: ${{ needs.setup.outputs.linux-cache-key }} lookup-only: true - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 if: steps.cache-linux-sysroot.outputs.cache-hit != 'true' with: image: tonistiigi/binfmt:qemu-v7.0.0-28 @@ -81,36 +58,37 @@ jobs: run: tar -czvf .sysroot/linux.tar.gz -C .sysroot linux - name: Save Linux sysroot cache if: steps.cache-linux-sysroot.outputs.cache-hit != 'true' - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 with: path: .sysroot/linux.tar.gz key: ${{ needs.setup.outputs.linux-cache-key }} build: runs-on: ubuntu-latest - needs: [setup, populate-darwin-sysroot, populate-linux-sysroot] + needs: [setup, populate-linux-sysroot] steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@v7 - name: Set up Release uses: ./.github/actions/setup-goreleaser with: - darwin-cache-key: ${{ needs.setup.outputs.darwin-cache-key }} linux-cache-key: ${{ needs.setup.outputs.linux-cache-key }} - name: Run GoReleaser (Build & Test) env: GITHUB_TOKEN: ${{github.token}} run: | + docker run --rm --entrypoint go "${GORELEASER_CROSS_IMAGE}" version + docker run --rm "${GORELEASER_CROSS_IMAGE}" --version docker run \ --rm \ -e GITHUB_TOKEN=${GITHUB_TOKEN} \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $(pwd):/go/src/llgo \ -w /go/src/llgo \ - ghcr.io/goreleaser/goreleaser-cross:v1.22 \ - release --skip=publish,nfpm,snapcraft --snapshot --clean + "${GORELEASER_CROSS_IMAGE}" \ + release --verbose --skip=publish,nfpm,snapcraft --snapshot --clean - name: Upload Darwin AMD64 Artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: llgo-darwin-amd64 path: .dist/*darwin-amd64.tar.gz @@ -118,7 +96,7 @@ jobs: include-hidden-files: true - name: Upload Darwin ARM64 Artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: llgo-darwin-arm64 path: .dist/*darwin-arm64.tar.gz @@ -126,7 +104,7 @@ jobs: include-hidden-files: true - name: Upload Linux AMD64 Artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: llgo-linux-amd64 path: .dist/*linux-amd64.tar.gz @@ -134,7 +112,7 @@ jobs: include-hidden-files: true - name: Upload Linux ARM64 Artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: llgo-linux-arm64 path: .dist/*linux-arm64.tar.gz @@ -142,7 +120,7 @@ jobs: include-hidden-files: true - name: Upload Checksums - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: llgo-checksums path: .dist/*checksums.txt @@ -154,37 +132,39 @@ jobs: strategy: matrix: include: - - os: macos-13 + - os: macos-15-intel goos: darwin goarch: amd64 - go-version: "1.24.2" - go-mod-version: "1.24" + go-version: "1.26.5" + go-mod-version: "1.26" - os: macos-latest goos: darwin goarch: arm64 - go-version: "1.24.2" - go-mod-version: "1.24" + go-version: "1.26.5" + go-mod-version: "1.26" - os: ubuntu-latest goos: linux goarch: amd64 - go-version: "1.24.2" - go-mod-version: "1.24" + go-version: "1.26.5" + go-mod-version: "1.26" - os: ubuntu-24.04-arm goos: linux goarch: arm64 - go-version: "1.24.2" - go-mod-version: "1.24" + go-version: "1.26.5" + go-mod-version: "1.26" runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Install dependencies uses: ./.github/actions/setup-deps + with: + install-llvm: false - name: Set up Go - uses: actions/setup-go@v6 + uses: ./.github/actions/setup-go with: go-version: ${{ matrix.go-version }} - name: Download Platform Artifact - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v8 with: name: llgo-${{ matrix.goos }}-${{ matrix.goarch }} path: . @@ -205,18 +185,19 @@ jobs: mod-version: ${{ matrix.go-mod-version }} release: - needs: [setup, build, test-artifacts] + permissions: + contents: write + needs: [setup, test-artifacts, populate-linux-sysroot] runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/') steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Set up Release uses: ./.github/actions/setup-goreleaser with: - darwin-cache-key: ${{ needs.setup.outputs.darwin-cache-key }} linux-cache-key: ${{ needs.setup.outputs.linux-cache-key }} - name: Run GoReleaser (Release) env: @@ -224,11 +205,13 @@ jobs: run: | echo "Publishing release for tag: ${{ github.ref }}" echo "All artifact tests passed, proceeding with release..." + docker run --rm --entrypoint go "${GORELEASER_CROSS_IMAGE}" version + docker run --rm "${GORELEASER_CROSS_IMAGE}" --version docker run \ --rm \ -e GITHUB_TOKEN=${GITHUB_TOKEN} \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $(pwd):/go/src/llgo \ -w /go/src/llgo \ - ghcr.io/goreleaser/goreleaser-cross:v1.22 \ - release --clean --skip nfpm,snapcraft + "${GORELEASER_CROSS_IMAGE}" \ + release --clean --verbose --skip nfpm,snapcraft diff --git a/.github/workflows/stdlib-coverage.yml b/.github/workflows/stdlib-coverage.yml new file mode 100644 index 0000000000..8aa81e6151 --- /dev/null +++ b/.github/workflows/stdlib-coverage.yml @@ -0,0 +1,30 @@ +name: Stdlib Coverage + +on: + push: + branches: + - main + pull_request: + branches: ["**"] + +concurrency: + group: stdlib-coverage-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + coverage: + timeout-minutes: 15 + strategy: + matrix: + os: + - macos-latest + - ubuntu-latest + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v7 + + - name: Set up Go + uses: ./.github/actions/setup-go + + - name: Check stdlib function coverage + run: doc/_readme/scripts/check_std_cover.sh diff --git a/.github/workflows/targets.yml b/.github/workflows/targets.yml index 944a87b8d1..49ce34aa6c 100644 --- a/.github/workflows/targets.yml +++ b/.github/workflows/targets.yml @@ -1,11 +1,9 @@ - name: Targets on: push: - branches: - - "**" - - "!dependabot/**" + branches: + - main pull_request: branches: ["**"] @@ -15,17 +13,15 @@ concurrency: jobs: llgo: - continue-on-error: true timeout-minutes: 30 strategy: matrix: os: - - macos-latest - - ubuntu-24.04 + - ubuntu-latest llvm: [19] runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Install dependencies uses: ./.github/actions/setup-deps with: @@ -33,15 +29,18 @@ jobs: - name: Set up Go for build uses: ./.github/actions/setup-go - with: - go-version: "1.24.2" - name: Install run: | go install ./... echo "LLGO_ROOT=$GITHUB_WORKSPACE" >> $GITHUB_ENV - - name: Build targets + - name: Build targets (empty) + run: | + cd _demo/embed/targetsbuild + bash build.sh empty + + - name: Build targets (defer) run: | - cd _demo/targetsbuild - bash build.sh + cd _demo/embed/targetsbuild + bash build.sh defer diff --git a/.github/workflows/test_demo.sh b/.github/workflows/test_demo.sh old mode 100644 new mode 100755 index 42232e2f80..3cbd846e9b --- a/.github/workflows/test_demo.sh +++ b/.github/workflows/test_demo.sh @@ -1,23 +1,392 @@ #!/bin/bash set -e -# llgo run subdirectories under _demo and _pydemo that contain *.go files -total=0 +mode="host" +if [ "${1:-}" = "--embedded" ]; then + mode="embedded" + shift +fi + +# llgo run subdirectories under _demo that contain *.go files +jobs="${LLGO_DEMO_JOBS:-1}" +llgo_run_flags=() +if [ -n "${LLGO_DEMO_LLGORUN_FLAGS:-}" ]; then + read -r -a llgo_run_flags <<< "${LLGO_DEMO_LLGORUN_FLAGS}" +fi +if [ "${jobs}" -gt 1 ]; then + if [ "${BASH_VERSINFO[0]}" -lt 5 ] || { [ "${BASH_VERSINFO[0]}" -eq 5 ] && [ "${BASH_VERSINFO[1]}" -lt 1 ]; }; then + echo "warning: LLGO_DEMO_JOBS=${jobs} requested but bash ${BASH_VERSION} lacks 'wait -n -p'; running sequentially" >&2 + jobs=1 + fi +fi +tmp_root="$(mktemp -d)" +trap 'rm -rf "$tmp_root"' EXIT + +cases=() +if [ "$mode" = "embedded" ]; then + while IFS= read -r dir; do + cases+=("$dir") + done < <(find ./_demo/go ./_demo/c -name '*.go' -print | xargs -n1 dirname | sort -u) +else + search_dirs=(./_demo/go/* ./_demo/py/* ./_demo/c/*) + for d in "${search_dirs[@]}"; do + if [ -d "$d" ] && [ -n "$(ls "$d"/*.go 2>/dev/null)" ]; then + cases+=("$d") + fi + done +fi + +embedded_targets=() +emulator=0 +if [ "$mode" = "embedded" ]; then + emulator=1 + embedded_targets=(esp32 esp32c3-basic) +fi + +ignore_esp32=( + "./_demo/c/asmcall" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/c/asmfullcall" # panic: cannot build SSA for packages + "./_demo/c/cabisret" # timeout: emulator did not auto-exit + "./_demo/c/cargs" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/c/cexec" # link error: ld.lld undefined symbol execlp + "./_demo/c/cgofull" # fast fail: build constraints exclude all Go files + "./_demo/c/cgofull/pymod1" # fast fail: build constraints exclude all Go files + "./_demo/c/cgofull/pymod2" # fast fail: build constraints exclude all Go files + "./_demo/c/concat" # link error: ld.lld undefined symbol stderr + "./_demo/c/cppintf" # C++ compile error: libc++ reports "No thread API" + "./_demo/c/cppintf/foo" # C++ compile error: libc++ reports "No thread API" + "./_demo/c/cppmintf" # C++ compile error: libc++ reports "No thread API" + "./_demo/c/cppmintf/foo" # C++ compile error: libc++ reports "No thread API" + "./_demo/c/cppstr" # C++ compile error: libc++ reports "No thread API" + "./_demo/c/crand" # fast fail: build constraints exclude all Go files in lib/c/time + "./_demo/c/ctime" # fast fail: build constraints exclude all Go files in lib/c/time + "./_demo/c/getcwd" # timeout: emulator did not auto-exit + "./_demo/c/hello" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/c/llama2-c" # fast fail: build constraints exclude all Go files in lib/c/time + "./_demo/c/netdbdemo" # link error: ld.lld undefined symbol getaddrinfo + "./_demo/c/setjmp" # panic: cannot build SSA for packages + "./_demo/c/socket/client" # link error: ld.lld undefined symbol socket + "./_demo/c/socket/server" # link error: ld.lld undefined symbol socket + "./_demo/c/stacksave" # fast fail: build constraints exclude all Go files + "./_demo/c/syncdebug" # fast fail: build constraints exclude all Go files in pthread/sync + "./_demo/c/thread" # link error: ld.lld undefined symbol GC_pthread_create + "./_demo/go/abimethod" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/async" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/async/timeout" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/cabi" # runtime output: fatal error + "./_demo/go/cgo" # fast fail: build constraints exclude all Go files + "./_demo/go/checkfile" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/commandrun" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/createtemp-1654" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/defer" # runtime output: fatal error + "./_demo/go/embedunexport-1598" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/export" # timeout: emulator did not auto-exit + "./_demo/go/failed/stacktrace" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/gobuild" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/gobuild-1389" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/goimporter-1389" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/goroutine" # timeout: emulator did not auto-exit + "./_demo/go/gotime" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/gotoken" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/gotypes" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/issue1538" # timeout: emulator did not auto-exit + "./_demo/go/issue1538-floatcvtuint-over" # timeout: emulator did not auto-exit + "./_demo/go/logdemo" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/maphash" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/mimeheader" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/mkdirdemo" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/netip" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/osfile" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/oslookpath" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/oswritestring" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/randcrypt" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/randdemo" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/readdir" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectcallfn" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectchanof" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectconv" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectcopy" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectembed" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectempty" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectfunc" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectfnconv" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectfntype" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectifacecall" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectindirect" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectmake" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectmakefn" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectmethod" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectname-1412" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectnamedfn" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectnew" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectpointerto" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectpkgpath" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectslice" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectsliceat" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectstructof" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectvisiblefields" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/return-1605" # runtime output: fatal error + "./_demo/go/runtime" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/sync" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/syscall" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/syscallraw" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/sysexec" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/sysopen-1654" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/texttemplate" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/timedur" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/timer" # panic: internal/bytealg selected .s files require plan9asm translation +) + +ignore_esp32c3_basic=( + "./_demo/go/mkdirdemo" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/c/asmcall" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/c/asmfullcall" # panic: cannot build SSA for packages (undefined: verify) + "./_demo/go/atomicfn" #ld.lld: error: undefined symbol: __atomic_fetch_add_4 + "./_demo/c/cargs" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/c/catomic" # link error: ld.lld undefined symbol __atomic_store + "./_demo/c/cexec" # link error: ld.lld undefined symbol execlp + "./_demo/c/cgofull" # fast fail: build constraints exclude all Go files + "./_demo/c/cgofull/pymod1" # fast fail: build constraints exclude all Go files + "./_demo/c/cgofull/pymod2" # fast fail: build constraints exclude all Go files + "./_demo/c/concat" # link error: ld.lld undefined symbol stderr + "./_demo/c/cppstr" # C++ compile error: '' file not found + "./_demo/c/crand" # fast fail: build constraints exclude all Go files in lib/c/time + "./_demo/c/ctime" # fast fail: build constraints exclude all Go files in lib/c/time + "./_demo/c/getcwd" # timeout: emulator did not auto-exit + "./_demo/c/hello" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/c/llama2-c" # fast fail: build constraints exclude all Go files in lib/c/time + "./_demo/c/netdbdemo" # link error: ld.lld undefined symbol getaddrinfo + "./_demo/c/setjmp" # panic: cannot build SSA for packages (undefined SigjmpBuf/Siglongjmp) + "./_demo/c/socket/client" # link error: ld.lld undefined symbol socket + "./_demo/c/socket/server" # link error: ld.lld undefined symbol socket + "./_demo/c/stacksave" # fast fail: build constraints exclude all Go files + "./_demo/c/syncdebug" # fast fail: build constraints exclude all Go files in pthread/sync + "./_demo/c/thread" # link error: ld.lld undefined symbol GC_pthread_create + "./_demo/go/abimethod" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/async" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/async/timeout" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/checkfile" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/commandrun" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/createtemp-1654" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/cgo" # fast fail: build constraints exclude all Go files + "./_demo/go/embedunexport-1598" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/export" # link error: ld.lld undefined symbol __atomic_fetch_or_4 + "./_demo/go/failed/stacktrace" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/gobuild" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/gobuild-1389" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/goimporter-1389" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/goroutine" # timeout: emulator did not auto-exit + "./_demo/go/gotime" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/gotoken" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/gotypes" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/logdemo" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/maphash" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/mimeheader" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/netip" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/osfile" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/oslookpath" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/oswritestring" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/randcrypt" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/randdemo" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/readdir" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectcallfn" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectchanof" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectconv" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectfunc" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectfnconv" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectfntype" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectifacecall" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectindirect" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectcopy" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectembed" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectempty" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectmethod" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectmake" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectmakefn" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectname-1412" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectnamedfn" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectnew" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectpointerto" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectpkgpath" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectslice" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectsliceat" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectstructof" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/reflectvisiblefields" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/runtime" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/sync" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/sysopen-1654" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/syscall" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/syscallraw" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/sysexec" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/texttemplate" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/timedur" # panic: internal/bytealg selected .s files require plan9asm translation + "./_demo/go/timer" # panic: internal/bytealg selected .s files require plan9asm translation +) + +should_ignore() { + local dir="$1" + local target="$2" + case "$target" in + esp32) + for ignore in "${ignore_esp32[@]}"; do + if [ "$dir" = "$ignore" ]; then + return 0 + fi + done + ;; + esp32c3-basic) + for ignore in "${ignore_esp32c3_basic[@]}"; do + if [ "$dir" = "$ignore" ]; then + return 0 + fi + done + ;; + esac + return 1 +} + +is_model_demo() { + case "$1" in + ./_demo/c/llama2-c) + return 0 + ;; + esac + return 1 +} + +run_dirs=() +run_targets=() +run_labels=() + +if [ "$mode" = "embedded" ]; then + for target in "${embedded_targets[@]}"; do + for d in "${cases[@]}"; do + if should_ignore "$d" "$target"; then + echo "SKIP $d (target=$target)" + continue + fi + run_dirs+=("$d") + run_targets+=("$target") + run_labels+=("$d (target=$target)") + done + done +else + for d in "${cases[@]}"; do + if is_model_demo "$d" && [ "${LLGO_RUN_MODEL_DEMOS:-0}" != "1" ]; then + echo "SKIP $d (model demo runs in scheduled Model Demo workflow)" + continue + fi + run_dirs+=("$d") + run_targets+=("") + run_labels+=("$d") + done +fi + +total="${#run_dirs[@]}" failed=0 failed_cases="" -for d in ./_demo/* ./_pydemo/*; do - if [ -d "$d" ] && [ -n "$(ls "$d"/*.go 2>/dev/null)" ]; then - total=$((total+1)) - echo "Testing $d" - if ! (cd "$d" && llgo run .); then - echo "FAIL" + +run_case() { + local dir="$1" + local target="$2" + if [ -n "$target" ]; then + echo "Testing $dir (target=$target)" + else + echo "Testing $dir" + fi + cmd=(llgo run) + cmd+=("${llgo_run_flags[@]}") + if [ -n "$target" ]; then + cmd+=("-target=$target") + fi + if [ "$emulator" -eq 1 ]; then + cmd+=("-emulator") + fi + cmd+=(".") + if (cd "$dir" && GOWORK=off "${cmd[@]}"); then + echo "PASS" + else + echo "FAIL" + return 1 + fi +} + +if [ "$jobs" -le 1 ] || [ "$total" -le 1 ]; then + for i in "${!run_dirs[@]}"; do + d="${run_dirs[$i]}" + target="${run_targets[$i]}" + label="${run_labels[$i]}" + if ! run_case "$d" "$target"; then failed=$((failed+1)) - failed_cases="$failed_cases\n* :x: $d" + failed_cases="$failed_cases\n* :x: $label" + fi + done +else + active_pids=() + active_dirs=() + active_logs=() + idx=0 + + for i in "${!run_dirs[@]}"; do + d="${run_dirs[$i]}" + target="${run_targets[$i]}" + label="${run_labels[$i]}" + idx=$((idx+1)) + log="$tmp_root/$(printf '%04d' "$idx").log" + (run_case "$d" "$target") >"$log" 2>&1 & + pid=$! + active_pids+=("$pid") + active_dirs+=("$label") + active_logs+=("$log") + + while [ "${#active_pids[@]}" -ge "$jobs" ]; do + finished_pid="" + if wait -n -p finished_pid; then + finished_status=0 + else + finished_status=$? + fi + for i in "${!active_pids[@]}"; do + if [ "${active_pids[$i]}" = "$finished_pid" ]; then + cat "${active_logs[$i]}" + if [ "$finished_status" -ne 0 ]; then + failed=$((failed+1)) + failed_cases="$failed_cases\n* :x: ${active_dirs[$i]}" + fi + unset 'active_pids[i]' 'active_dirs[i]' 'active_logs[i]' + active_pids=("${active_pids[@]}") + active_dirs=("${active_dirs[@]}") + active_logs=("${active_logs[@]}") + break + fi + done + done + done + + while [ "${#active_pids[@]}" -gt 0 ]; do + finished_pid="" + if wait -n -p finished_pid; then + finished_status=0 else - echo "PASS" + finished_status=$? fi - fi -done + for i in "${!active_pids[@]}"; do + if [ "${active_pids[$i]}" = "$finished_pid" ]; then + cat "${active_logs[$i]}" + if [ "$finished_status" -ne 0 ]; then + failed=$((failed+1)) + failed_cases="$failed_cases\n* :x: ${active_dirs[$i]}" + fi + unset 'active_pids[i]' 'active_dirs[i]' 'active_logs[i]' + active_pids=("${active_pids[@]}") + active_dirs=("${active_dirs[@]}") + active_logs=("${active_logs[@]}") + break + fi + done + done +fi + echo "=== Done" echo "$((total-failed))/$total tests passed" diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 0000000000..a7d5144471 --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,117 @@ +name: Windows native compiler + +on: + push: + branches: + - main + pull_request: + branches: ["**"] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + native-compiler: + name: native PE/COFF (windows-amd64, LLVM 19, Go 1.26.7) + runs-on: windows-2022 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v7 + + - name: Set up Go + uses: ./.github/actions/setup-go + with: + go-version: "1.26.7" + + - name: Set up dependencies + uses: ./.github/actions/setup-deps + with: + llvm-version: 19 + + - name: Test Windows compiler host + shell: msys2 {0} + run: | + set -euo pipefail + + go version + clang --version + pkg-config --modversion llvm-19 + where.exe cl.exe + where.exe lib.exe + where.exe link.exe + + # Compile the released binding through its normal Windows build + # path, without byollvm or global CGO flag overrides. + go test -run '^$' -timeout=10m github.com/xgo-dev/llvm + go test -count=1 -timeout=10m -covermode=atomic \ + -coverprofile=coverage-windows-host.txt \ + ./internal/goarch ./internal/xtool/llvm ./internal/meta + + # Repetition verifies that the native file lock actually excludes + # concurrent cache publishers rather than merely compiling on + # Windows. The rest of crosscompile enters the matrix with R2. + go test -count=20 -timeout=10m -run 'Lock' -covermode=atomic \ + -coverprofile=coverage-windows-lock.txt \ + ./internal/crosscompile + + # These packages already compile and their test processes start on + # Windows. Their platform/runtime-dependent cases are enabled by + # the later proposal stages instead of being hidden by build tags. + go test -run '^$' -timeout=10m \ + ./ssa ./internal/build + + # R2 exercises actual PE/COFF outputs without depending on the + # Win32 runtime work staged for R4: an executable, a flat C archive, + # a DLL/import library, and both directions of MSVC interoperability. + LLGO_REQUIRE_MSVC=1 go test -count=1 -timeout=10m \ + -run '^(TestWindows(NativeArtifacts|ConsumesMSVCLibrary)|TestResolveBuildConfigDefaultsAndValidation|TestApplyBuildModeCompileFlags|TestCSharedLinkArgs|TestFullRpathArgs|TestCSharedExportArgs|TestIsArchiveInput|TestBuildOutFmtsBuildModes)$' \ + -covermode=atomic -coverprofile=coverage-windows-artifacts.txt \ + ./internal/build + + go test -count=1 -timeout=10m \ + -run '^(TestNativeToolchain|TestNativeWindows|TestCOFFLTOLevel)$' \ + -covermode=atomic -coverprofile=coverage-windows-coff-flags.txt \ + ./internal/crosscompile + + go test -count=1 -timeout=10m \ + -run '^(TestWriteResponseFile|TestWriteWindowsResponseArg|TestWriteGNUResponseArg|TestResponseFileStyle|TestUseResponseFile|TestLongWindowsCommandUsesClangResponseFile)$' \ + -covermode=atomic -coverprofile=coverage-windows-response.txt \ + ./internal/clang + + go test -count=1 -timeout=10m \ + -run '^(TestWindowsODRDefinitionsUseCOMDAT|TestUnixODRDefinitionsDoNotGainCOMDAT)$' \ + -covermode=atomic -coverprofile=coverage-windows-comdat.txt \ + ./ssa + + go test -count=1 -timeout=10m \ + -run '^(TestTargetArchAndNewTransformerArchSelection|TestMSVC.*)$' \ + -covermode=atomic -coverprofile=coverage-windows-cabi.txt \ + ./internal/cabi + + ( + cd runtime + go test -count=1 -timeout=10m \ + -covermode=atomic -coverprofile=../coverage-windows-ffi.txt \ + ./internal/ffi ./internal/clite/ffi ./internal/lib/reflect + for arch in 386 arm64; do + CGO_ENABLED=0 GOOS=windows GOARCH="$arch" go test -c \ + -o "$RUNNER_TEMP/llgo-ffi-$arch.test.exe" ./internal/ffi + done + ) + + ffi_include="$(pkg-config --variable=includedir libffi)" + clang -target x86_64-pc-windows-msvc -I"$ffi_include" -c \ + runtime/internal/clite/ffi/_wrap/libffi.c \ + -o "$RUNNER_TEMP/llgo-libffi-amd64.obj" + llvm-readobj --file-headers "$RUNNER_TEMP/llgo-libffi-amd64.obj" + + - name: Upload Windows coverage + uses: codecov/codecov-action@v7 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: coverage-windows-host.txt,coverage-windows-lock.txt,coverage-windows-artifacts.txt,coverage-windows-coff-flags.txt,coverage-windows-response.txt,coverage-windows-comdat.txt,coverage-windows-cabi.txt,coverage-windows-ffi.txt + flags: windows-native diff --git a/.github/xgopilot.yml b/.github/xgopilot.yml new file mode 100644 index 0000000000..3301366317 --- /dev/null +++ b/.github/xgopilot.yml @@ -0,0 +1,2 @@ +claude: + model: "claude-4.6-opus[1m]" diff --git a/.gitignore b/.gitignore index c388e11d3a..64244ecb39 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.dll *.so *.dylib +*.a test.db demo.ll @@ -16,14 +17,18 @@ stories*.bin err.log numpy.txt result.txt +expect.txt.new _go/ _runtime/ _tinygo/ _output/ build.dir/ +ltoplugin/build/ .vscode/ .venv/ +__pycache__/ +*.pyc # Test binary, built with `go test -c` *.test diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 4a42dbe3f7..4db7374c70 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -21,14 +21,13 @@ builds: flags: - -tags=darwin,amd64,byollvm ldflags: - - -X github.com/goplus/llgo/internal/env.buildVersion=v{{.Version}} - - -X github.com/goplus/llgo/internal/env.buildTime={{.Date}} - - -X github.com/goplus/llgo/xtool/env/llvm.ldLLVMConfigBin=/usr/local/opt/llvm@19/bin/llvm-config + - -X github.com/xgo-dev/llgo/internal/env.buildVersion=v{{.Version}} + - -X github.com/xgo-dev/llgo/internal/env.buildTime={{.Date}} env: - CC=o64-clang - CXX=o64-clang++ - - CGO_CPPFLAGS=-I{{.Env.SYSROOT_DARWIN_AMD64}}/usr/local/opt/llvm@19/include -mmacosx-version-min=10.13 -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS - - CGO_LDFLAGS=-L{{.Env.SYSROOT_DARWIN_AMD64}}/usr/local/opt/llvm@19/lib -mmacosx-version-min=10.13 -Wl,-search_paths_first -Wl,-headerpad_max_install_names -lLLVM-19 -lz -lm + - CGO_CPPFLAGS=-I{{.Env.SYSROOT_DARWIN_AMD64}}/crosscompile/clang/include -mmacosx-version-min=10.13 -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS + - CGO_LDFLAGS=-L{{.Env.SYSROOT_DARWIN_AMD64}}/crosscompile/clang/lib -mmacosx-version-min=10.13 -Wl,-search_paths_first -Wl,-headerpad_max_install_names -lLLVM-19 -lz -lm -Wl,-rpath,@executable_path/../crosscompile/clang/lib targets: - darwin_amd64 mod_timestamp: "{{.CommitTimestamp}}" @@ -38,14 +37,13 @@ builds: flags: - -tags=darwin,arm64,byollvm ldflags: - - -X github.com/goplus/llgo/internal/env.buildVersion=v{{.Version}} - - -X github.com/goplus/llgo/internal/env.buildTime={{.Date}} - - -X github.com/goplus/llgo/xtool/env/llvm.ldLLVMConfigBin=/opt/homebrew/opt/llvm@19/bin/llvm-config + - -X github.com/xgo-dev/llgo/internal/env.buildVersion=v{{.Version}} + - -X github.com/xgo-dev/llgo/internal/env.buildTime={{.Date}} env: - CC=oa64-clang - CXX=oa64-clang++ - - CGO_CPPFLAGS=-I{{.Env.SYSROOT_DARWIN_ARM64}}/opt/homebrew/opt/llvm@19/include -mmacosx-version-min=10.13 -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS - - CGO_LDFLAGS=-L{{.Env.SYSROOT_DARWIN_ARM64}}/opt/homebrew/opt/llvm@19/lib -mmacosx-version-min=10.13 -Wl,-search_paths_first -Wl,-headerpad_max_install_names -lLLVM-19 -lz -lm + - CGO_CPPFLAGS=-I{{.Env.SYSROOT_DARWIN_ARM64}}/crosscompile/clang/include -mmacosx-version-min=10.13 -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS + - CGO_LDFLAGS=-L{{.Env.SYSROOT_DARWIN_ARM64}}/crosscompile/clang/lib -mmacosx-version-min=10.13 -Wl,-search_paths_first -Wl,-headerpad_max_install_names -lLLVM-19 -lz -lm -Wl,-rpath,@executable_path/../crosscompile/clang/lib targets: - darwin_arm64 mod_timestamp: "{{.CommitTimestamp}}" @@ -55,14 +53,17 @@ builds: flags: - -tags=linux,amd64,byollvm ldflags: - - -X github.com/goplus/llgo/internal/env.buildVersion=v{{.Version}} - - -X github.com/goplus/llgo/internal/env.buildTime={{.Date}} - - -X github.com/goplus/llgo/xtool/env/llvm.ldLLVMConfigBin=/usr/lib/llvm-19/bin/llvm-config + - -X github.com/xgo-dev/llgo/internal/env.buildVersion=v{{.Version}} + - -X github.com/xgo-dev/llgo/internal/env.buildTime={{.Date}} + - "-extldflags=-Wl,-rpath,$ORIGIN/../crosscompile/clang/lib" env: - - CC=x86_64-linux-gnu-gcc - - CXX=x86_64-linux-gnu-g++ - - CGO_CPPFLAGS=--sysroot={{.Env.SYSROOT_LINUX_AMD64}} -I{{.Env.SYSROOT_LINUX_AMD64}}/usr/include/llvm-19 -I{{.Env.SYSROOT_LINUX_AMD64}}/usr/include/llvm-c-19 -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS - - CGO_LDFLAGS=--sysroot={{.Env.SYSROOT_LINUX_AMD64}} -L{{.Env.SYSROOT_LINUX_AMD64}}/usr/lib/llvm-19/lib -lLLVM-19 + # Use the packaged host Clang driver with the target's complete bullseye sysroot. + - CC={{.Env.SYSROOT_LINUX_AMD64}}/crosscompile/clang/bin/clang + - CXX={{.Env.SYSROOT_LINUX_AMD64}}/crosscompile/clang/bin/clang++ + - CGO_CPPFLAGS=--target=x86_64-linux-gnu --gcc-toolchain={{.Env.SYSROOT_LINUX_AMD64}}/usr --sysroot={{.Env.SYSROOT_LINUX_AMD64}} -I{{.Env.SYSROOT_LINUX_AMD64}}/crosscompile/clang/include -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS + - CGO_CXXFLAGS=-std=c++17 -nostdinc++ -isystem {{.Env.SYSROOT_LINUX_AMD64}}/usr/include/c++/10 -isystem {{.Env.SYSROOT_LINUX_AMD64}}/usr/include/x86_64-linux-gnu/c++/10 -isystem {{.Env.SYSROOT_LINUX_AMD64}}/usr/include/c++/10/backward + - CGO_LDFLAGS=--target=x86_64-linux-gnu --gcc-toolchain={{.Env.SYSROOT_LINUX_AMD64}}/usr --sysroot={{.Env.SYSROOT_LINUX_AMD64}} -L{{.Env.SYSROOT_LINUX_AMD64}}/crosscompile/clang/lib -L{{.Env.SYSROOT_LINUX_AMD64}}/usr/lib/gcc/x86_64-linux-gnu/10 -L{{.Env.SYSROOT_LINUX_AMD64}}/usr/lib/x86_64-linux-gnu -L{{.Env.SYSROOT_LINUX_AMD64}}/lib/x86_64-linux-gnu -lLLVM-19 -lz + - CGO_LDFLAGS_ALLOW=(--target=.*|--gcc-toolchain=.*|--sysroot.*) targets: - linux_amd64 mod_timestamp: "{{.CommitTimestamp}}" @@ -72,14 +73,17 @@ builds: flags: - -tags=linux,arm64,byollvm ldflags: - - -X github.com/goplus/llgo/internal/env.buildVersion=v{{.Version}} - - -X github.com/goplus/llgo/internal/env.buildTime={{.Date}} - - -X github.com/goplus/llgo/xtool/env/llvm.ldLLVMConfigBin=/usr/lib/llvm-19/bin/llvm-config + - -X github.com/xgo-dev/llgo/internal/env.buildVersion=v{{.Version}} + - -X github.com/xgo-dev/llgo/internal/env.buildTime={{.Date}} + - "-extldflags=-Wl,-rpath,$ORIGIN/../crosscompile/clang/lib" env: - - CC=aarch64-linux-gnu-gcc - - CXX=aarch64-linux-gnu-g++ - - CGO_CPPFLAGS=--sysroot={{.Env.SYSROOT_LINUX_ARM64}} -I{{.Env.SYSROOT_LINUX_ARM64}}/usr/include/llvm-19 -I{{.Env.SYSROOT_LINUX_ARM64}}/usr/include/llvm-c-19 -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS - - CGO_LDFLAGS=--sysroot={{.Env.SYSROOT_LINUX_ARM64}} -L{{.Env.SYSROOT_LINUX_ARM64}}/usr/lib/llvm-19/lib -lLLVM-19 + # The amd64 Clang driver is multi-target; libraries and headers stay arm64. + - CC={{.Env.SYSROOT_LINUX_AMD64}}/crosscompile/clang/bin/clang + - CXX={{.Env.SYSROOT_LINUX_AMD64}}/crosscompile/clang/bin/clang++ + - CGO_CPPFLAGS=--target=aarch64-linux-gnu --gcc-toolchain={{.Env.SYSROOT_LINUX_ARM64}}/usr --sysroot={{.Env.SYSROOT_LINUX_ARM64}} -I{{.Env.SYSROOT_LINUX_ARM64}}/crosscompile/clang/include -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS + - CGO_CXXFLAGS=-std=c++17 -nostdinc++ -isystem {{.Env.SYSROOT_LINUX_ARM64}}/usr/include/c++/10 -isystem {{.Env.SYSROOT_LINUX_ARM64}}/usr/include/aarch64-linux-gnu/c++/10 -isystem {{.Env.SYSROOT_LINUX_ARM64}}/usr/include/c++/10/backward + - CGO_LDFLAGS=--target=aarch64-linux-gnu --gcc-toolchain={{.Env.SYSROOT_LINUX_ARM64}}/usr --sysroot={{.Env.SYSROOT_LINUX_ARM64}} -L{{.Env.SYSROOT_LINUX_ARM64}}/crosscompile/clang/lib -L{{.Env.SYSROOT_LINUX_ARM64}}/usr/lib/gcc/aarch64-linux-gnu/10 -L{{.Env.SYSROOT_LINUX_ARM64}}/usr/lib/aarch64-linux-gnu -L{{.Env.SYSROOT_LINUX_ARM64}}/lib/aarch64-linux-gnu -lLLVM-19 -lz + - CGO_LDFLAGS_ALLOW=(--target=.*|--gcc-toolchain=.*|--sysroot.*) targets: - linux_arm64 mod_timestamp: "{{.CommitTimestamp}}" @@ -91,16 +95,22 @@ archives: {{- if .Arm}}v{{.Arm}}{{end}} files: - LICENSE + - LICENSES - README.md + - THIRD_PARTY_NOTICES.md - runtime - + - targets + - src: ".sysroot/{{.Os}}/{{.Arch}}/crosscompile/clang" + dst: crosscompile/clang + info: + mode: 0755 checksum: name_template: "{{.ProjectName}}{{.Version}}.checksums.txt" nfpms: - package_name: llgo vendor: goplus - homepage: https://github.com/goplus/llgo + homepage: https://github.com/xgo-dev/llgo maintainer: Aofei Sheng description: | LLGo is a Go compiler based on LLVM in order to better integrate Go with the C ecosystem including Python. It's a @@ -121,6 +131,14 @@ nfpms: {{.ProjectName}}{{.Version}}.{{.Os}}-{{.Arch}} {{- if .Arm}}v{{.Arm}}{{end}} bindir: /usr/local/bin + contents: + - src: LICENSE + dst: /usr/share/doc/llgo/LICENSE + - src: THIRD_PARTY_NOTICES.md + dst: /usr/share/doc/llgo/THIRD_PARTY_NOTICES.md + - src: LICENSES/ + dst: /usr/share/doc/llgo/LICENSES + type: tree snapcrafts: - name: llgo @@ -139,6 +157,13 @@ snapcrafts: - ... license: Apache-2.0 confinement: classic + extra_files: + - source: LICENSE + destination: usr/share/doc/llgo/LICENSE + - source: THIRD_PARTY_NOTICES.md + destination: usr/share/doc/llgo/THIRD_PARTY_NOTICES.md + - source: LICENSES + destination: usr/share/doc/llgo/LICENSES name_template: >- {{.ProjectName}}{{.Version}}.{{.Os}}-{{.Arch}} {{- if .Arm}}v{{.Arm}}{{end}} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..293b00cd99 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,183 @@ +# LLGo Project AI Assistant Guide + +This document provides essential information for AI assistants to help fix bugs and implement features in the LLGo project. + +## About LLGo + +LLGo is a Go compiler based on LLVM designed to better integrate Go with the C ecosystem, including Python and JavaScript. It's a subproject of the XGo project that aims to expand the boundaries of Go/XGo for game development, AI and data science, WebAssembly, and embedded development. + +## Project Structure + +- `cmd/llgo` - Main llgo compiler command (usage similar to `go` command) +- `cl/` - Core compiler logic that converts Go packages to LLVM IR +- `ssa/` - LLVM IR file generation using Go SSA semantics +- `internal/build/` - Build process orchestration +- `runtime/` - LLGo runtime library +- `chore/` - Development tools (llgen, llpyg, ssadump, etc.) +- `_demo/` - Example programs demonstrating C/C++ interop (`c/hello`, `c/qsort`) and Python integration (`py/callpy`, `py/numpy`) +- `_cmptest/` - Comparison tests to verify the same program gets the same output with Go and LLGo + +## Development Environment + +For detailed dependency requirements and installation instructions, see the [Dependencies](README.md#dependencies) and [How to install](README.md#how-to-install) sections in the README. + +## Testing & Validation + +The following commands and workflows are essential when fixing bugs or implementing features in the LLGo project: + +### Run all tests +```bash +go test ./... +``` + +**Note:** Some tests may fail if optional dependencies (like Python) are not properly configured. The test suite includes comprehensive tests for: +- Compiler functionality +- SSA generation +- C interop +- Python integration (requires Python development headers) + +### Write and run tests for your changes + +When adding new functionality or fixing bugs, create appropriate test cases: + +```bash +# Add your test to the relevant package's *_test.go file +# Then run tests for that package +go test ./path/to/package + +# Or run all tests +go test ./... +``` + +**Important:** The `LLGO_ROOT` environment variable must be set to the repository root when running llgo commands during development. + +### Maintain LLVM IR checks by test intent + +Do not refresh every FileCheck assertion after an IR change. First identify the +single compiler property a test is intended to protect, then keep the smallest +handwritten assertions that prove that property. Runtime output and focused IR +checks protect different contracts; avoid only the redundant full IR snapshot. + +Treat 100 FileCheck directive lines as a review threshold, not a hard limit. +Above that threshold, verify that each handwritten group protects a distinct, +named semantic contract. If the test instead needs a long contiguous IR shape, +make that region reproducible with `litgen`. + +The repository keeps a curated set of source-embedded autogenerated checks for +cases where a broad function or module shape is itself the contract. They +remain in the Go source and carry a reproducible `UTC_ARGS` note: + +```go +// LITTEST +// NOTE: Assertions have been autogenerated by chore/litgen UTC_ARGS: --function=run --check-globals=smart +``` + +Declare known platform differences with a target matrix and exact FileCheck +prefixes instead of regular-expression alternatives. The ordinary marker uses +the historical pre-ABI stage; only checks after target ABI lowering need an +explicit stage: + +```go +// LITTEST darwin/arm64 linux/amd64 +// LITTEST: POST-ABI linux/amd64 linux/arm64 +``` + +The harness cross-compiles IR for every listed target, independent of the host. +It also always checks the fixture's current effective target; listed targets are +additional coverage and are deduplicated against the current target. +FileCheck enables `CHECK`, the GOARCH prefix, and the exact target prefix (for +example `CHECK,ARM64,DARWIN-ARM64`). Put assertions shared by targets with the +same architecture under `ARM64`, `AMD64`, and similar architecture prefixes. +Fixtures that depend on cgo still require the corresponding target C toolchain +and sysroot, so keep them on plain `// LITTEST` unless CI provides those inputs. + +Refresh and verify all opted-in snapshots with: + +```bash +go run ./chore/litgen -u cl +go run ./chore/litgen -u --check cl +``` + +`litgen` will not replace a handwritten test during update-only operation. When +creating an autogenerated test, pass one or more `--function` expressions; use +`--all-functions` only for a deliberate whole-module contract. See +`dev/README.md` for the complete options and marker conventions. + +## Code Quality + +Before submitting any code updates, you must run the following formatting and validation commands: + +### Format code +```bash +go fmt ./... +``` + +**Important:** Always run `go fmt ./...` before committing code changes. This ensures consistent code formatting across the project. + +### Run static analysis +```bash +go vet ./... +``` + +**Note:** Currently reports some issues related to lock passing by value in `ssa/type_cvt.go` and a possible unsafe.Pointer misuse in `cl/builtin_test.go`. These are known issues. + + +## Common Development Tasks + +### Build the entire project +```bash +go build -v ./... +``` + +### Build llgo command specifically +```bash +go build -o llgo ./cmd/llgo +``` + +### Check llgo version +```bash +llgo version +``` + +### Install llgo for system-wide use +```bash +./install.sh +``` + +### Build development tools +```bash +go install -v ./cmd/... +go install -v ./chore/... +``` + +## Key Modules for Understanding + +- `ssa` - Generates LLVM IR using Go SSA semantics +- `cl` - Core compiler converting Go to LLVM IR +- `internal/build` - Orchestrates the compilation process + +## Debugging + +### Disable Garbage Collection +For testing purposes, you can disable GC: +```bash +LLGO_ROOT=/path/to/llgo llgo run -tags nogc . +``` + +## LLGO_ROOT Environment Variable + +**CRITICAL:** Always set `LLGO_ROOT` to the repository root when running llgo during development: + +```bash +export LLGO_ROOT=/path/to/llgo +# or +LLGO_ROOT=/path/to/llgo llgo run . +``` + +## Important Notes + +1. **Testing Requirement:** All bug fixes and features MUST include tests +2. **Demo Directory:** Examples in `_demo` are prefixed with `_` to prevent standard `go` command from trying to compile them +3. **Defer in Loops:** LLGo now supports `defer` within loops, matching Go's semantics of executing defers in LIFO order for every iteration. Be mindful of loop-heavy defer usage as it allocates per iteration. +4. **C Ecosystem Integration:** LLGo uses `go:linkname` directive to link external symbols through ABI +5. **Python Integration:** Third-party Python libraries require separate installation of library files diff --git a/LICENSE b/LICENSE index c6915742c0..55d10a65a3 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ Apache License same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved. + Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/LICENSES/BlakeSmith-AR-MIT.txt b/LICENSES/BlakeSmith-AR-MIT.txt new file mode 100644 index 0000000000..d124dd0895 --- /dev/null +++ b/LICENSES/BlakeSmith-AR-MIT.txt @@ -0,0 +1,19 @@ +Copyright (c) 2013 Blake Smith + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/LICENSES/CRC16-MIT.txt b/LICENSES/CRC16-MIT.txt new file mode 100644 index 0000000000..52b48fe298 --- /dev/null +++ b/LICENSES/CRC16-MIT.txt @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright (c) 2015 sigurn +Copyright (c) 2021 r10r + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSES/Cobra-Apache-2.0.txt b/LICENSES/Cobra-Apache-2.0.txt new file mode 100644 index 0000000000..298f0e2665 --- /dev/null +++ b/LICENSES/Cobra-Apache-2.0.txt @@ -0,0 +1,174 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/LICENSES/Cobra-pflag-BSD-3-Clause.txt b/LICENSES/Cobra-pflag-BSD-3-Clause.txt new file mode 100644 index 0000000000..63ed1cfea1 --- /dev/null +++ b/LICENSES/Cobra-pflag-BSD-3-Clause.txt @@ -0,0 +1,28 @@ +Copyright (c) 2012 Alex Ogier. All rights reserved. +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSES/Go-BSD-3-Clause.txt b/LICENSES/Go-BSD-3-Clause.txt new file mode 100644 index 0000000000..2a7cf70da6 --- /dev/null +++ b/LICENSES/Go-BSD-3-Clause.txt @@ -0,0 +1,27 @@ +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSES/Go-Serial-BSD-3-Clause.txt b/LICENSES/Go-Serial-BSD-3-Clause.txt new file mode 100644 index 0000000000..fb15c85bc8 --- /dev/null +++ b/LICENSES/Go-Serial-BSD-3-Clause.txt @@ -0,0 +1,32 @@ + +Copyright (c) 2014-2024, Cristian Maglie. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSES/Go-YAML-LICENSE.txt b/LICENSES/Go-YAML-LICENSE.txt new file mode 100644 index 0000000000..2683e4bb1f --- /dev/null +++ b/LICENSES/Go-YAML-LICENSE.txt @@ -0,0 +1,50 @@ + +This project is covered by two different licenses: MIT and Apache. + +#### MIT License #### + +The following files were ported to Go from C files of libyaml, and thus +are still covered by their original MIT license, with the additional +copyright staring in 2011 when the project was ported over: + + apic.go emitterc.go parserc.go readerc.go scannerc.go + writerc.go yamlh.go yamlprivateh.go + +Copyright (c) 2006-2010 Kirill Simonov +Copyright (c) 2006-2011 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +### Apache License ### + +All the remaining project files are covered by the Apache license: + +Copyright (c) 2011-2019 Canonical Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSES/Go-YAML-NOTICE.txt b/LICENSES/Go-YAML-NOTICE.txt new file mode 100644 index 0000000000..866d74a7ad --- /dev/null +++ b/LICENSES/Go-YAML-NOTICE.txt @@ -0,0 +1,13 @@ +Copyright 2011-2016 Canonical Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSES/GoHex-MIT.txt b/LICENSES/GoHex-MIT.txt new file mode 100644 index 0000000000..c6484378d9 --- /dev/null +++ b/LICENSES/GoHex-MIT.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Marcin Borowicz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSES/GoTTY-MIT.txt b/LICENSES/GoTTY-MIT.txt new file mode 100644 index 0000000000..e364750d2b --- /dev/null +++ b/LICENSES/GoTTY-MIT.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Yasuhiro Matsumoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSES/Goselect-MIT.txt b/LICENSES/Goselect-MIT.txt new file mode 100644 index 0000000000..13e81339cc --- /dev/null +++ b/LICENSES/Goselect-MIT.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Guillaume J. Charmes + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSES/Nordic-BSD-3-Clause.txt b/LICENSES/Nordic-BSD-3-Clause.txt new file mode 100644 index 0000000000..e754db9283 --- /dev/null +++ b/LICENSES/Nordic-BSD-3-Clause.txt @@ -0,0 +1,27 @@ +Copyright (c) 2010 - 2020, Nordic Semiconductor ASA All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of Nordic Semiconductor ASA nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSES/Qiniu-X-Apache-2.0.txt b/LICENSES/Qiniu-X-Apache-2.0.txt new file mode 100644 index 0000000000..b67d909100 --- /dev/null +++ b/LICENSES/Qiniu-X-Apache-2.0.txt @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSES/Target-Device-Notices.txt b/LICENSES/Target-Device-Notices.txt new file mode 100644 index 0000000000..dbaa81a83c --- /dev/null +++ b/LICENSES/Target-Device-Notices.txt @@ -0,0 +1,52 @@ +Third-party target and device support notices +================================================= + +The LLGo target support tree includes material generated from or derived from +hardware-vendor source descriptions and SDK support files. The original notices +remain in the corresponding source files under targets/. The following notices +apply to the marked files. + +BSD 3-Clause material +--------------------- + +Copyright 2016-2018 NXP. +Copyright 2016-2019 NXP. All rights reserved. +Copyright 2016-2020 NXP. All rights reserved. +Copyright (c) 2019-2021 Raspberry Pi (Trading) Ltd. +Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +Copyright (c) 2024 Raspberry Pi Ltd. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Apache License 2.0 material +--------------------------- + +Copyright (c) 2016 Atmel Corporation, a wholly owned subsidiary of +Microchip Technology Inc. +Copyright (c) 2018 Microchip Technology Inc. +Copyright 2021-2024 Espressif Systems (Shanghai) CO LTD. + +These marked files are licensed under the Apache License, Version 2.0. A copy +of that license is included as the repository's LICENSE file. diff --git a/LICENSES/TinyGo-BSD-3-Clause.txt b/LICENSES/TinyGo-BSD-3-Clause.txt new file mode 100644 index 0000000000..4d0fde7595 --- /dev/null +++ b/LICENSES/TinyGo-BSD-3-Clause.txt @@ -0,0 +1,33 @@ +Copyright (c) 2018-2025 The TinyGo Authors. All rights reserved. + +TinyGo includes portions of the Go standard library. +Copyright (c) 2009-2024 The Go Authors. All rights reserved. + +TinyGo includes portions of LLVM, which is under the Apache License v2.0 with +LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSES/XGo-LLVM-Apache-2.0-WITH-LLVM-exception.txt b/LICENSES/XGo-LLVM-Apache-2.0-WITH-LLVM-exception.txt new file mode 100644 index 0000000000..5715176572 --- /dev/null +++ b/LICENSES/XGo-LLVM-Apache-2.0-WITH-LLVM-exception.txt @@ -0,0 +1,278 @@ +============================================================================== +The LLVM Project is under the Apache License v2.0 with LLVM Exceptions: +============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + +============================================================================== +Software from third parties included in the LLVM Project: +============================================================================== +The LLVM Project contains third party software which is under different license +terms. All such code will be identified clearly using at least one of two +mechanisms: +1) It will be in a separate directory tree with its own `LICENSE.txt` or + `LICENSE` file at the top containing the specific license and restrictions + which apply to that software, or +2) It will contain specific license and restriction terms at the top of every + file. + +============================================================================== +Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy): +============================================================================== +University of Illinois/NCSA +Open Source License + +Copyright (c) 2003-2019 University of Illinois at Urbana-Champaign. +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. diff --git a/LICENSES/XGo-Plan9Asm-Apache-2.0.txt b/LICENSES/XGo-Plan9Asm-Apache-2.0.txt new file mode 100644 index 0000000000..717e2bd8ab --- /dev/null +++ b/LICENSES/XGo-Plan9Asm-Apache-2.0.txt @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 241a704313..7929efe0a2 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -llgo - A Go compiler based on LLVM +LLGo - A Go compiler based on LLVM ===== -[![Build Status](https://github.com/goplus/llgo/actions/workflows/go.yml/badge.svg)](https://github.com/goplus/llgo/actions/workflows/go.yml) -[![Go Report Card](https://goreportcard.com/badge/github.com/goplus/llgo)](https://goreportcard.com/report/github.com/goplus/llgo) -[![GitHub release](https://img.shields.io/github/v/tag/goplus/llgo.svg?label=release)](https://github.com/goplus/llgo/releases) -[![Coverage Status](https://codecov.io/gh/goplus/llgo/branch/main/graph/badge.svg)](https://codecov.io/gh/goplus/llgo) -[![GoDoc](https://pkg.go.dev/badge/github.com/goplus/llgo.svg)](https://pkg.go.dev/github.com/goplus/llgo) -[![Language](https://img.shields.io/badge/language-XGo-blue.svg)](https://github.com/goplus/gop) +[![Build Status](https://github.com/xgo-dev/llgo/actions/workflows/go.yml/badge.svg)](https://github.com/xgo-dev/llgo/actions/workflows/go.yml) +[![GitHub release](https://img.shields.io/github/v/tag/xgo-dev/llgo.svg?label=release)](https://github.com/xgo-dev/llgo/releases) +[![Coverage Status](https://codecov.io/gh/xgo-dev/llgo/branch/main/graph/badge.svg)](https://codecov.io/gh/xgo-dev/llgo) +[![Benchmark](https://img.shields.io/badge/benchmark-LLGo_vs_Go-yellowgreen.svg)](https://xgo-dev.github.io/benchmarks/) +[![GoDoc](https://pkg.go.dev/badge/github.com/xgo-dev/llgo.svg)](https://pkg.go.dev/github.com/xgo-dev/llgo) +[![XGo](https://img.shields.io/badge/project-XGo-blue.svg)](https://github.com/goplus/xgo) -LLGo is a Go compiler based on LLVM in order to better integrate Go with the C ecosystem including Python and JavaScript. It's a subproject of [the XGo project](https://github.com/goplus/gop). +LLGo is a Go compiler based on LLVM in order to better integrate Go with the C ecosystem, including Python and JavaScript. It's a subproject of [the XGo project](https://github.com/goplus/xgo). LLGo aims to expand the boundaries of Go/XGo, providing limitless possibilities such as: @@ -24,60 +24,47 @@ How can these be achieved? LLGo := Go * C ecosystem ``` -LLGo is compatible with C ecosystem through the language's **Application Binary Interface (ABI)**, while LLGo is compatible with Go through its **syntax (source code)**. C ecosystem includes all languages that are ABI compatible with C (eg. C/C++, Python, JavaScript, Objective-C, Swift, etc). +LLGo is compatible with the C ecosystem through the C **Application Binary Interface (ABI)**, while LLGo is compatible with Go at the **source-code level**. The C ecosystem includes languages that expose C-compatible interfaces (e.g. C/C++, Python, JavaScript, Objective-C, and Swift). -## C/C++ standard libary support +## Go support -You can import a C/C++ standard library in LLGo! +LLGo is compatible with Go 1.20+ source code and supports the complete Go 1.26 language syntax, as well as `cgo`. -* [c](https://pkg.go.dev/github.com/goplus/lib/c) -* [c/syscall](https://pkg.go.dev/github.com/goplus/lib/c/syscall) -* [c/sys](https://pkg.go.dev/github.com/goplus/lib/c/sys) -* [c/os](https://pkg.go.dev/github.com/goplus/lib/c/os) -* [c/math](https://pkg.go.dev/github.com/goplus/lib/c/math) -* [c/math/cmplx](https://pkg.go.dev/github.com/goplus/lib/c/math/cmplx) -* [c/math/rand](https://pkg.go.dev/github.com/goplus/lib/c/math/rand) -* [c/pthread](https://pkg.go.dev/github.com/goplus/lib/c/pthread) -* [c/pthread/sync](https://pkg.go.dev/github.com/goplus/lib/c/pthread/sync) -* [c/sync/atomic](https://pkg.go.dev/github.com/goplus/lib/c/sync/atomic) -* [c/time](https://pkg.go.dev/github.com/goplus/lib/c/time) -* [c/net](https://pkg.go.dev/github.com/goplus/lib/c/net) -* [cpp/std](https://pkg.go.dev/github.com/goplus/lib/cpp/std) +Compatibility is checked against applicable upstream [`GOROOT/test`](test/goroot/README.md) cases using pinned Go 1.25 and Go 1.26 toolchains. Remaining applicable differences are recorded in [`xfail.yaml`](test/goroot/xfail.yaml); gc-specific mechanisms outside LLGo's compatibility goals are documented in [`notapplicable.yaml`](test/goroot/notapplicable.yaml). -Here is a simple example: +### Runtime - +LLGo uses a different runtime from the standard Go toolchain. Native goroutines map 1:1 to OS threads with fixed native stacks, so direct C calls require no Go-to-C stack or scheduler transition, avoiding the cgo overhead that makes frequent C calls costly in standard Go. -```go -package main +The default garbage collector is conservative [BDWGC](https://www.hboehm.info/gc/) (also known as libgc). Bare-metal embedded targets instead use a TinyGo-derived conservative mark-and-sweep collector. -import "github.com/goplus/lib/c" +Garbage collection can be disabled with the `nogc` build tag. For example: -func main() { - c.Printf(c.Str("Hello world\n")) -} +```sh +llgo run -tags nogc . ``` -This is a simple example of calling the C `printf` function to print `Hello world`. Here, `c.Str` is not a function for converting a Go string to a C string, but a built-in instruction supported by `llgo` for generating a C string constant. +### Standard libraries -The `_demo` directory contains some C standard libary related demos (it start with `_` to prevent the `go` command from compiling it): +LLGo fully supports the Go standard library on supported native platforms. CI requires compatibility coverage for every public package and exported symbol in the primary Go toolchain, and runs [`test/std`](test/std/README.md) with both supported toolchains. -* [hello](_demo/hello/hello.go): call C `printf` to print `Hello world` -* [concat](_demo/concat/concat.go): call C `fprintf` with `stderr` -* [qsort](_demo/qsort/qsort.go): call C function with a callback (eg. `qsort`) +Other targets may not provide every OS service or implementation-specific runtime behavior. -To run these demos (If you haven't installed `llgo` yet, please refer to [How to install](#how-to-install)): +| Target | Current coverage | +| --- | --- | +| Native | Linux amd64/arm64 and macOS amd64/arm64 [release artifacts](https://github.com/xgo-dev/llgo/releases); primary CI on Linux amd64 and macOS arm64 | +| WebAssembly | `js/wasm` and `wasip1/wasm` builds; WASI and Emscripten CI coverage | +| Embedded | [`-target`](doc/Embedded_Cmd.md) configurations for supported boards and MCUs, with selected QEMU/emulator smoke tests | -```sh -cd # eg. cd _demo/hello -llgo run . -``` +## C/C++ support -## How to support C/C++ and Python +LLGo lets you import and call C/C++ libraries directly, without wrappers or cgo overhead. -LLGo use `go:linkname` to link an extern symbol througth its ABI: +### Interop mechanism + +LLGo uses `go:linkname` to bind a Go declaration directly to a C ABI symbol: @@ -88,7 +75,7 @@ import _ "unsafe" // for go:linkname func Sqrt(x float64) float64 ``` -You can directly integrate it into [your own code](_demo/linkname/linkname.go): +You can use this directly in your own code: @@ -105,7 +92,7 @@ func main() { } ``` -Or put it into a package (see [c/math](https://github.com/goplus/lib/tree/main/c/math/math.go)): +Or organize such bindings into a package, as [c/math](https://github.com/goplus/lib/tree/main/c/math/math.go) does: @@ -119,12 +106,91 @@ func main() { } ``` +Because calls into C compile to native calls against the C ABI, there is no Go-to-C stack or scheduler transition, so frequent C calls stay cheap. + +### C/C++ standard libraries + +LLGo provides Go bindings for the C/C++ standard library: + +| Package | Description | +| --- | --- | +| [c](https://pkg.go.dev/github.com/goplus/lib/c) | C standard library core | +| [c/syscall](https://pkg.go.dev/github.com/goplus/lib/c/syscall) | System calls | +| [c/sys](https://pkg.go.dev/github.com/goplus/lib/c/sys) | System headers | +| [c/os](https://pkg.go.dev/github.com/goplus/lib/c/os) | OS interfaces | +| [c/math](https://pkg.go.dev/github.com/goplus/lib/c/math) | Math functions | +| [c/math/cmplx](https://pkg.go.dev/github.com/goplus/lib/c/math/cmplx) | Complex math | +| [c/math/rand](https://pkg.go.dev/github.com/goplus/lib/c/math/rand) | Random number generation | +| [c/pthread](https://pkg.go.dev/github.com/goplus/lib/c/pthread) | POSIX threads | +| [c/pthread/sync](https://pkg.go.dev/github.com/goplus/lib/c/pthread/sync) | Thread synchronization | +| [c/sync/atomic](https://pkg.go.dev/github.com/goplus/lib/c/sync/atomic) | Atomic operations | +| [c/time](https://pkg.go.dev/github.com/goplus/lib/c/time) | Time functions | +| [c/net](https://pkg.go.dev/github.com/goplus/lib/c/net) | Networking | +| [cpp/std](https://pkg.go.dev/github.com/goplus/lib/cpp/std) | C++ standard library core | + +Here is a simple example calling the C `printf` function: + + + +```go +package main + +import "github.com/goplus/lib/c" + +func main() { + c.Printf(c.Str("Hello world\n")) +} +``` + +`c.Str` is not a runtime conversion from a Go string to a C string — it is a built-in instruction that `llgo` recognizes and compiles directly into a C string constant. + +Additional demos are available in the `_demo` directory (prefixed with `_` so the `go` command skips them): + +* [hello](_demo/c/hello/hello.go): call C `printf` to print `Hello world` +* [concat](_demo/c/concat/concat.go): call C `fprintf` with `stderr` +* [qsort](_demo/c/qsort/qsort.go): call a C function that takes a callback (e.g. `qsort`) + +To run a demo (see [How to install](#how-to-install) if `llgo` isn't installed yet): + +```sh +cd # e.g. cd _demo/c/hello +llgo run . +``` + +### Other frequently used libraries + +Beyond the standard library, LLGo can import libraries from across the C/C++ ecosystem. Bindings are currently maintained by hand; automating this process, as is already done for Python library imports, is planned for the future. + +Available bindings include: + +* [c/bdwgc](https://pkg.go.dev/github.com/goplus/lib/c/bdwgc) +* [c/cjson](https://pkg.go.dev/github.com/goplus/lib/c/cjson) +* [c/clang](https://pkg.go.dev/github.com/goplus/lib/c/clang) +* [c/ffi](https://pkg.go.dev/github.com/goplus/lib/c/ffi) +* [c/libuv](https://pkg.go.dev/github.com/goplus/lib/c/libuv) +* [c/llama2](https://pkg.go.dev/github.com/goplus/lib/c/llama2) +* [c/lua](https://pkg.go.dev/github.com/goplus/lib/c/lua) +* [c/neco](https://pkg.go.dev/github.com/goplus/lib/c/neco) +* [c/openssl](https://pkg.go.dev/github.com/goplus/lib/c/openssl) +* [c/raylib](https://pkg.go.dev/github.com/goplus/lib/c/raylib) +* [c/sqlite](https://pkg.go.dev/github.com/goplus/lib/c/sqlite) +* [c/zlib](https://pkg.go.dev/github.com/goplus/lib/c/zlib) +* [cpp/inih](https://pkg.go.dev/github.com/goplus/lib/cpp/inih) +* [cpp/llvm](https://pkg.go.dev/github.com/goplus/lib/cpp/llvm) + +Examples built on these bindings: + +* [llama2-c](_demo/c/llama2-c): inference Llama 2 (the first LLGo AI example) +* [mkjson](https://github.com/goplus/lib/tree/main/c/cjson/_demo/mkjson/mkjson.go): create a JSON object and print it +* [sqlitedemo](https://github.com/goplus/lib/tree/main/c/sqlite/_demo/sqlitedemo/demo.go): a basic SQLite demo +* [tetris](https://github.com/goplus/lib/tree/main/c/raylib/_demo/tetris/tetris.go): a Tetris game based on raylib + ## Python support You can import a Python library in LLGo! -And you can import any Python library into `llgo` through a program called `llpyg` (see [Development tools](#development-tools)). The following libraries have been included in `llgo`: +You can import Python libraries into `llgo` through `llpyg` (see [Development tools](#development-tools)). Available bindings include: * [py](https://pkg.go.dev/github.com/goplus/lib/py) (abi) * [py/std](https://pkg.go.dev/github.com/goplus/lib/py/std) (builtins) @@ -139,7 +205,7 @@ And you can import any Python library into `llgo` through a program called `llpy * [py/torch](https://pkg.go.dev/github.com/goplus/lib/py/torch) * [py/matplotlib](https://pkg.go.dev/github.com/goplus/lib/py/matplotlib) -Note: For third-party libraries (such as pandas and pytorch), you still need to install the library files. +Third-party libraries such as pandas and PyTorch must be installed separately. Here is an example: @@ -204,155 +270,37 @@ func main() { Here we define two 3x3 matrices a and b, add them to get x, and then print the result. -The `_pydemo` directory contains some python related demos: +The `_demo/py/` directory contains some python related demos: -* [callpy](_pydemo/callpy/callpy.go): call Python standard library function `math.sqrt` -* [pi](_pydemo/pi/pi.go): print python constants `math.pi` -* [statistics](_pydemo/statistics/statistics.go): define a python list and call `statistics.mean` to get the mean -* [matrix](_pydemo/matrix/matrix.go): a basic `numpy` demo +* [callpy](_demo/py/callpy/callpy.go): call Python standard library function `math.sqrt` +* [pi](_demo/py/pi/pi.go): print python constants `math.pi` +* [statistics](_demo/py/statistics/statistics.go): define a python list and call `statistics.mean` to get the mean +* [matrix](_demo/py/matrix/matrix.go): a basic `numpy` demo To run these demos (If you haven't installed `llgo` yet, please refer to [How to install](#how-to-install)): ```sh -cd # eg. cd _pydemo/callpy +cd # eg. cd _demo/py/callpy llgo run . ``` - -## Other frequently used libraries - -LLGo can easily import any libraries from the C ecosystem. Currently, this import process is still manual, but in the future, it will be automated similar to Python library imports. - -The currently supported libraries include: - -* [c/bdwgc](https://pkg.go.dev/github.com/goplus/lib/c/bdwgc) -* [c/cjson](https://pkg.go.dev/github.com/goplus/lib/c/cjson) -* [c/clang](https://pkg.go.dev/github.com/goplus/lib/c/clang) -* [c/ffi](https://pkg.go.dev/github.com/goplus/lib/c/ffi) -* [c/libuv](https://pkg.go.dev/github.com/goplus/lib/c/libuv) -* [c/llama2](https://pkg.go.dev/github.com/goplus/lib/c/llama2) -* [c/lua](https://pkg.go.dev/github.com/goplus/lib/c/lua) -* [c/neco](https://pkg.go.dev/github.com/goplus/lib/c/neco) -* [c/openssl](https://pkg.go.dev/github.com/goplus/lib/c/openssl) -* [c/raylib](https://pkg.go.dev/github.com/goplus/lib/c/raylib) -* [c/sqlite](https://pkg.go.dev/github.com/goplus/lib/c/sqlite) -* [c/zlib](https://pkg.go.dev/github.com/goplus/lib/c/zlib) -* [cpp/inih](https://pkg.go.dev/github.com/goplus/lib/cpp/inih) -* [cpp/llvm](https://pkg.go.dev/github.com/goplus/lib/cpp/llvm) - -Here are some examples related to them: - -* [llama2-c](_demo/llama2-c): inference Llama 2 (It's the first llgo AI example) -* [mkjson](https://github.com/goplus/lib/tree/main/c/cjson/_demo/mkjson/mkjson.go): create a json object and print it -* [sqlitedemo](https://github.com/goplus/lib/tree/main/c/sqlite/_demo/sqlitedemo/demo.go): a basic sqlite demo -* [tetris](https://github.com/goplus/lib/tree/main/c/raylib/_demo/tetris/tetris.go): a tetris game based on raylib - - -## Go syntax support - -All Go syntax (including `cgo`) is already supported. Here are some examples: - -* [concat](_demo/concat/concat.go): define a variadic function -* [genints](_demo/genints/genints.go): various forms of closure usage (including C function, recv.method and anonymous function) -* [errors](_cmptest/errors/errors.go): demo to implement error interface -* [defer](_cmptest/defer/defer.go): defer demo -* [goroutine](_demo/goroutine/goroutine.go): goroutine demo - - -### Defer - -LLGo `defer` does not support usage in loops. This is not a bug but a feature, because we think that using `defer` in a loop is a very unrecommended practice. - - -### Garbage Collection (GC) - -By default, LLGo implements `gc` based on [bdwgc](https://www.hboehm.info/gc/) (also known as [libgc](https://www.hboehm.info/gc/)). - -However, you can disable gc by specifying the `nogc` tag. For example: - -```sh -llgo run -tags nogc . -``` - - -## Go packages support - -Here are the Go packages that can be imported correctly: - -* [unsafe](https://pkg.go.dev/unsafe) -* [unicode](https://pkg.go.dev/unicode) -* [unicode/utf8](https://pkg.go.dev/unicode/utf8) -* [unicode/utf16](https://pkg.go.dev/unicode/utf16) -* [math](https://pkg.go.dev/math) -* [math/big](https://pkg.go.dev/math/big) (partially) -* [math/bits](https://pkg.go.dev/math/bits) -* [math/cmplx](https://pkg.go.dev/math/cmplx) -* [math/rand](https://pkg.go.dev/math/rand) -* [net/url](https://pkg.go.dev/net/url) -* [errors](https://pkg.go.dev/errors) -* [context](https://pkg.go.dev/context) -* [io](https://pkg.go.dev/io) -* [io/fs](https://pkg.go.dev/io/fs) -* [io/ioutil](https://pkg.go.dev/io/ioutil) -* [log](https://pkg.go.dev/log) -* [flag](https://pkg.go.dev/flag) -* [sort](https://pkg.go.dev/sort) -* [bytes](https://pkg.go.dev/bytes) -* [bufio](https://pkg.go.dev/bufio) -* [strings](https://pkg.go.dev/strings) -* [strconv](https://pkg.go.dev/strconv) -* [path](https://pkg.go.dev/path) -* [path/filepath](https://pkg.go.dev/path/filepath) -* [sync/atomic](https://pkg.go.dev/sync/atomic) -* [sync](https://pkg.go.dev/sync) (partially) -* [syscall](https://pkg.go.dev/syscall) (partially) -* [runtime](https://pkg.go.dev/runtime) (partially) -* [os](https://pkg.go.dev/os) (partially) -* [os/exec](https://pkg.go.dev/os/exec) (partially) -* [fmt](https://pkg.go.dev/fmt) (partially) -* [reflect](https://pkg.go.dev/reflect) (partially) -* [time](https://pkg.go.dev/time) (partially) -* [encoding](https://pkg.go.dev/encoding) -* [encoding/binary](https://pkg.go.dev/encoding/binary) -* [encoding/hex](https://pkg.go.dev/encoding/hex) -* [encoding/base32](https://pkg.go.dev/encoding/base32) -* [encoding/base64](https://pkg.go.dev/encoding/base64) -* [encoding/csv](https://pkg.go.dev/encoding/csv) -* [net/textproto](https://pkg.go.dev/net/textproto) -* [hash](https://pkg.go.dev/hash) -* [hash/adler32](https://pkg.go.dev/hash/adler32) -* [hash/crc32](https://pkg.go.dev/hash/crc32) (partially) -* [hash/crc64](https://pkg.go.dev/hash/crc64) -* [crypto](https://pkg.go.dev/crypto) -* [crypto/md5](https://pkg.go.dev/crypto/md5) -* [crypto/sha1](https://pkg.go.dev/crypto/sha1) -* [crypto/sha256](https://pkg.go.dev/crypto/sha256) -* [crypto/sha512](https://pkg.go.dev/crypto/sha512) (partially) -* [crypto/hmac](https://pkg.go.dev/crypto/hmac) (partially) -* [crypto/rand](https://pkg.go.dev/crypto/rand) (partially) -* [crypto/subtle](https://pkg.go.dev/crypto/subtle) (partially) -* [regexp](https://pkg.go.dev/regexp) -* [regexp/syntax](https://pkg.go.dev/regexp/syntax) -* [go/token](https://pkg.go.dev/go/token) -* [go/scanner](https://pkg.go.dev/go/scanner) -* [go/parser](https://pkg.go.dev/go/parser) - - ## Dependencies -- [Go 1.21+](https://go.dev) -- [LLVM 18](https://llvm.org) -- [Clang 18](https://clang.llvm.org) -- [LLD 18](https://lld.llvm.org) -- [pkg-config 0.29+](https://www.freedesktop.org/wiki/Software/pkg-config/) +- [Go 1.25+](https://go.dev) (to build LLGo; CI also validates user packages with pinned Go 1.25 and Go 1.26 toolchains) +- [LLVM 19](https://llvm.org) +- [Clang 19](https://clang.llvm.org) +- [LLD 19](https://lld.llvm.org) +- [pkg-config 0.29+](https://gitlab.freedesktop.org/pkg-config/pkg-config) - [bdwgc/libgc 8.0+](https://www.hboehm.info/gc/) +- [libffi](https://sourceware.org/libffi/) +- [libuv](https://libuv.org/) - [OpenSSL 3.0+](https://www.openssl.org/) -- [zlib 1.2+](https://www.zlib.net) +- [zlib 1.2+](https://github.com/madler/zlib) - [Python 3.12+](https://www.python.org) (optional, for [github.com/goplus/lib/py](https://pkg.go.dev/github.com/goplus/lib/py)) ## How to install -Follow these steps to generate the `llgo` command (its usage is the same as the `go` command): +Follow these steps to install the `llgo` command, whose usage is similar to the `go` command: ### on macOS @@ -363,7 +311,7 @@ brew update brew install llvm@19 lld@19 bdw-gc openssl cjson libffi libuv pkg-config brew install python@3.12 # optional brew link --overwrite llvm@19 lld@19 libffi -# curl https://raw.githubusercontent.com/goplus/llgo/refs/heads/main/install.sh | bash +# curl https://raw.githubusercontent.com/xgo-dev/llgo/refs/heads/main/install.sh | bash ./install.sh ``` @@ -377,9 +325,9 @@ brew link --overwrite llvm@19 lld@19 libffi echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-19 main" | sudo tee /etc/apt/sources.list.d/llvm.list wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - sudo apt-get update -sudo apt-get install -y llvm-19-dev clang-19 libclang-19-dev lld-19 libunwind-19-dev libc++-19-dev pkg-config libgc-dev libssl-dev zlib1g-dev libcjson-dev libsqlite3-dev libuv1-dev +sudo apt-get install -y llvm-19-dev clang-19 libclang-19-dev lld-19 libunwind-19-dev libc++-19-dev pkg-config libgc-dev libssl-dev zlib1g-dev libffi-dev libcjson-dev libsqlite3-dev libuv1-dev sudo apt-get install -y python3.12-dev # optional -#curl https://raw.githubusercontent.com/goplus/llgo/refs/heads/main/install.sh | bash +#curl https://raw.githubusercontent.com/xgo-dev/llgo/refs/heads/main/install.sh | bash ./install.sh ``` @@ -393,7 +341,7 @@ export LLVM_CONFIG=/usr/lib/llvm19/bin/llvm-config export CGO_CPPFLAGS="$($LLVM_CONFIG --cppflags)" export CGO_CXXFLAGS=-std=c++17 export CGO_LDFLAGS="$($LLVM_CONFIG --ldflags) $($LLVM_CONFIG --libs all)" -curl https://raw.githubusercontent.com/goplus/llgo/refs/heads/main/install.sh | bash +curl https://raw.githubusercontent.com/xgo-dev/llgo/refs/heads/main/install.sh | bash ``` docker alpine 386 llgo environment @@ -412,25 +360,29 @@ TODO ```sh -git clone https://github.com/goplus/llgo.git +git clone https://github.com/xgo-dev/llgo.git cd llgo ./install.sh ``` ## Development tools -* [pydump](_xtool/pydump): It's the first program compiled by `llgo` (NOT `go`) in a production environment. It outputs symbol information (functions, variables, and constants) from a Python library in JSON format, preparing for the generation of corresponding packages in `llgo`. +* [pydump](_xtool/pydump): It is the first production program compiled with `llgo` rather than `go`. It outputs symbol information (functions, variables, and constants) from a Python library in JSON format, preparing for the generation of corresponding packages in `llgo`. * [pysigfetch](https://github.com/goplus/hdq/tree/main/chore/pysigfetch): It generates symbol information by extracting information from Python's documentation site. This tool is not part of the `llgo` project, but we depend on it. * [llpyg](chore/llpyg): It is used to automatically convert Python libraries into Go packages that `llgo` can import. It depends on `pydump` and `pysigfetch` to accomplish the task. * [llgen](chore/llgen): It is used to compile Go packages into LLVM IR files (*.ll). +* [gentests](chore/gentests): It refreshes runtime-output and package-metadata golden data under `cl/_test*`. LLVM IR checks live in Go sources as `// LITTEST` FileCheck directives. +* [litgen](chore/litgen): It maintains explicitly opted-in, source-embedded FileCheck snapshots. It supports function/global selection, update-only operation, stale-check verification, and stable LLVM value abstractions. Small handwritten checks remain manual. * [ssadump](chore/ssadump): It is a Go SSA builder and interpreter. +For local workflows and test-golden refresh commands, see [dev/README.md](dev/README.md#6-refresh-test-goldens). + How do I generate these tools? ```sh -git clone https://github.com/goplus/llgo.git +git clone https://github.com/xgo-dev/llgo.git cd llgo go install -v ./cmd/... go install -v ./chore/... # compile all tools except pydump @@ -438,13 +390,12 @@ export LLGO_ROOT=$PWD cd _xtool llgo install ./... # compile pydump go install github.com/goplus/hdq/chore/pysigfetch@v0.8.1 # compile pysigfetch - ``` ## Key modules Below are the key modules for understanding the implementation principles of `llgo`: -* [ssa](https://pkg.go.dev/github.com/goplus/llgo/ssa): It generates LLVM IR files (LLVM SSA) using the semantics (interfaces) of Go SSA. Although `LLVM SSA` and `Go SSA` are both IR languages, they work at completely different levels. `LLVM SSA` is closer to machine code, which abstracts different instruction sets. While `Go SSA` is closer to a high-level language. We can think of it as the instruction set of the `Go computer`. `llgo/ssa` is not just limited to the `llgo` compiler. If we view it as the high-level expressive power of `LLVM`, you'll find it very useful. Prior to `llgo/ssa`, you had to operate `LLVM` using machine code semantics. But now, with the advanced SSA form (in the semantics of Go SSA), you can conveniently utilize `LLVM`. -* [cl](https://pkg.go.dev/github.com/goplus/llgo/cl): It is the core of the llgo compiler. It converts a Go package into LLVM IR files. It depends on `llgo/ssa`. -* [internal/build](https://pkg.go.dev/github.com/goplus/llgo/internal/build): It strings together the entire compilation process of `llgo`. It depends on `llgo/ssa` and `llgo/cl`. +* [ssa](https://pkg.go.dev/github.com/xgo-dev/llgo/ssa): It generates LLVM IR files (LLVM SSA) using the semantics and interfaces of Go SSA. Although `LLVM SSA` and `Go SSA` are both IR languages, they work at completely different levels. `LLVM SSA` is closer to machine code and abstracts over different instruction sets, while `Go SSA` is closer to a high-level language. We can think of it as the instruction set of the `Go computer`. `llgo/ssa` is not limited to the `llgo` compiler. If we view it as providing the high-level expressive power of `LLVM`, it is very useful. Its advanced SSA form lets clients use LLVM without operating directly on machine-code semantics. +* [cl](https://pkg.go.dev/github.com/xgo-dev/llgo/cl): It is the core of the llgo compiler. It converts a Go package into LLVM IR files. It depends on `llgo/ssa`. +* [internal/build](https://pkg.go.dev/github.com/xgo-dev/llgo/internal/build): It strings together the entire compilation process of `llgo`. It depends on `llgo/ssa` and `llgo/cl`. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000000..98f5e538e2 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,135 @@ +# Third-party notices + +LLGo is licensed under Apache License 2.0. The following components retain +their own licenses. The referenced license files are included in source +checkouts, release archives, and packaged distributions. The nested `runtime` +module carries its own copy of LLGo's Apache-2.0 license in `runtime/LICENSE`. + +## TinyGo + +LLGo contains source derived from the [TinyGo project](https://github.com/tinygo-org/tinygo): + +- the conservative bare-metal collector in `runtime/internal/runtime/tinygogc/gc_tinygo.go`; +- firmware image support in `internal/firmware/esp.go`, `nrfutil.go`, `objcopy.go`, and `uf2.go`; +- the marked flashing-support portions of `internal/flash/flash.go`; +- serial monitoring and panic-location support in `internal/monitor/monitor.go`; +- the deprecated archive builder in `chore/_deprecated/ar/ar.go`; +- target configurations and support files under `targets`. + +Reference snapshots contemporaneous with the initial LLGo imports are: + +- [runtime GC](https://github.com/tinygo-org/tinygo/tree/79ab77facd8b4d7ea39257f85d37f094f52770d2/src/runtime); +- [firmware builders](https://github.com/tinygo-org/tinygo/tree/3869f76887feef6c444308e7e1531b7cac1bbd10/builder); +- [monitor implementation](https://github.com/tinygo-org/tinygo/blob/020664591ab3a995d6d0aab5097c6fab838a925c/monitor.go); +- [initial target configuration import](https://github.com/tinygo-org/tinygo/tree/8c5886060f022a36768b5c29327759846021a868/targets). + +The TinyGo-derived portions remain subject to the BSD 3-Clause License in +[`LICENSES/TinyGo-BSD-3-Clause.txt`](LICENSES/TinyGo-BSD-3-Clause.txt). +Independently written LLGo code remains subject to the repository's Apache +License 2.0. + +## The Go project + +LLGo distributes Go-derived source under `runtime`, `targets/wasm_exec.js`, +`chore/ssadump`, and selected compatibility tests. +The `llgo` executable also contains the Go standard library and packages from +`golang.org/x/mod`, `golang.org/x/sync`, `golang.org/x/sys`, and +`golang.org/x/tools`. The authoritative versions are recorded in `go.mod` and +the executable's Go build information. + +These portions are licensed under the Go project's BSD 3-Clause License in +[`LICENSES/Go-BSD-3-Clause.txt`](LICENSES/Go-BSD-3-Clause.txt). The nested +runtime module also carries a copy at +[`runtime/LICENSES/Go-BSD-3-Clause.txt`](runtime/LICENSES/Go-BSD-3-Clause.txt). + +## Source incorporated into LLGo + +| Component | Distributed in | License | +| --- | --- | --- | +| `github.com/sigurn/crc16` and LLGo adaptations | `internal/crc16`, compiled CLI | [MIT](LICENSES/CRC16-MIT.txt) | +| `github.com/marcinbor85/gohex` and LLGo adaptations | `internal/gohex`, compiled CLI | [MIT](LICENSES/GoHex-MIT.txt) | +| `github.com/blakesmith/ar` and LLGo adaptations | `xtool/ar`, source and development tools | [MIT](LICENSES/BlakeSmith-AR-MIT.txt) | +| Hardware-vendor target support | marked files under `targets/device` and `targets/rp2040-boot-stage2.S` | [vendor notices](LICENSES/Target-Device-Notices.txt), including the [Nordic BSD license](LICENSES/Nordic-BSD-3-Clause.txt) | + +The original file-level notices remain in the target support sources. Firmware +distributors must reproduce the applicable notices when those sources are +included in a firmware image. + +## Go modules compiled into the llgo executable + +This list is limited to modules in the shipped Darwin and Linux executable; +development- and test-only modules are not included. + +| Module | License | +| --- | --- | +| `github.com/creack/goselect` | [MIT](LICENSES/Goselect-MIT.txt) | +| `github.com/goplus/cobra` | [Apache-2.0](LICENSES/Cobra-Apache-2.0.txt); compiled `pflag` package: [BSD-3-Clause](LICENSES/Cobra-pflag-BSD-3-Clause.txt) | +| `github.com/mattn/go-tty` | [MIT](LICENSES/GoTTY-MIT.txt) | +| `github.com/qiniu/x` | [Apache-2.0](LICENSES/Qiniu-X-Apache-2.0.txt) | +| `github.com/xgo-dev/llvm` | [Apache-2.0 WITH LLVM-exception](LICENSES/XGo-LLVM-Apache-2.0-WITH-LLVM-exception.txt) | +| `github.com/xgo-dev/plan9asm` | [Apache-2.0](LICENSES/XGo-Plan9Asm-Apache-2.0.txt) | +| `go.bug.st/serial` | [BSD-3-Clause](LICENSES/Go-Serial-BSD-3-Clause.txt) | +| `go.yaml.in/yaml/v3` | [MIT and Apache-2.0](LICENSES/Go-YAML-LICENSE.txt), [NOTICE](LICENSES/Go-YAML-NOTICE.txt) | +| `golang.org/x/mod`, `x/sync`, `x/sys`, and `x/tools` | [BSD-3-Clause](LICENSES/Go-BSD-3-Clause.txt) | + +## LLVM/Clang + +LLGo can download the Espressif-maintained ESP LLVM/Clang +`19.1.2_20250905-3` toolchain on demand from +[`goplus/espressif-llvm-project-prebuilt`](https://github.com/goplus/espressif-llvm-project-prebuilt/releases/tag/19.1.2_20250905-3). +Current LLGo release archives also include this toolchain under +`crosscompile/clang`, because the shipped `llgo` executable dynamically links +its LLVM library. + +LLVM, Clang, LLD, libc++, libc++abi, libunwind, compiler-rt, and other +LLVM-project components are licensed under Apache License 2.0 with LLVM +Exceptions. The complete license is reproduced in +[`LICENSES/XGo-LLVM-Apache-2.0-WITH-LLVM-exception.txt`](LICENSES/XGo-LLVM-Apache-2.0-WITH-LLVM-exception.txt). +Archive extraction preserves upstream license files. + +## Components downloaded for cross-compilation + +LLGo downloads the following components directly from their upstream release +locations when a selected target needs them. They are stored in the user's LLGo +cache; they are not vendored in this source tree. Their license files remain in +the extracted source or SDK directory. + +The Go packages under `internal/crosscompile/compile/libc` and +`internal/crosscompile/compile/rtlib` are LLGo build manifests, not copies of +the C library sources. The manifest code itself is compiled into `llgo` and is +covered by LLGo's Apache-2.0 license. At cross-compilation time, those manifests +select files from the downloaded picolibc, ESP newlib, or compiler-rt tree and +build static archives in that same cached tree. The archives are then linked +into the target program, so they are not part of the `llgo` executable or LLGo +release archive, but their code can be incorporated into the resulting +firmware. + +| Component | Download source | License location | +| --- | --- | --- | +| WASI SDK 25 | [`WebAssembly/wasi-sdk`](https://github.com/WebAssembly/wasi-sdk/releases/tag/wasi-sdk-25) | upstream `LICENSE` and license files in the SDK | +| picolibc/newlib sources | [`goplus/picolibc`](https://github.com/goplus/picolibc) | upstream `COPYING.picolibc` and `COPYING.NEWLIB` | +| ESP newlib sources | [`goplus/newlib`](https://github.com/goplus/newlib/tree/esp-4.3.0_20250211-patch7) | upstream `COPYING.NEWLIB` and applicable file notices | +| compiler-rt sources | [`goplus/compiler-rt`](https://github.com/goplus/compiler-rt/tree/xtensa_release_19.1.2) | upstream `LICENSE.TXT` (Apache-2.0 WITH LLVM-exception) | + +Firmware or other binaries built from downloaded C library sources may carry +their own redistribution requirements. Distributors of those outputs should +retain the applicable license and copyright notices from the cached source; +in particular, picolibc and ESP newlib use per-file notices collected by their +upstream `COPYING` files. + +## External tools and system libraries + +BDWGC, OpenSSL, libffi, libuv, cJSON, SQLite, zlib, Python, Emscripten, QEMU, +OpenOCD, flashing utilities, and platform SDK/system libraries are installed or +provided separately. LLGo may link to or invoke them, but does not copy them +into its source tree or release archives, except for LLVM-project components +explicitly described above. Their upstream licenses therefore apply to the +separate installations and to any redistributed output that incorporates them; +they are not relicensed by LLGo. + +## Redistribution + +Distributions containing the source, the `llgo` executable, the bundled +toolchain, or firmware linked with third-party runtime or target support must +retain or reproduce the applicable copyright notices, license conditions, +disclaimers, and NOTICE text described above. diff --git a/_cmptest/filestat/filestat.go b/_cmptest/filestat/filestat.go new file mode 100644 index 0000000000..c53dbc0712 --- /dev/null +++ b/_cmptest/filestat/filestat.go @@ -0,0 +1,49 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + // Create test file with 400 bytes + f, err := os.Create("test.bin") + if err != nil { + fmt.Println("Error creating file:", err) + return + } + _, err = f.Write(make([]byte, 400)) + if err != nil { + fmt.Println("Error writing file:", err) + f.Close() + return + } + f.Close() + + // Test 1: os.Stat() should work correctly + info1, err := os.Stat("test.bin") + if err != nil { + fmt.Println("Error in os.Stat():", err) + } else { + fmt.Printf("os.Stat(): size = %d\n", info1.Size()) + } + + // Test 2: file.Stat() should also work correctly (was broken before fix) + file, err := os.Open("test.bin") + if err != nil { + fmt.Println("Error opening file:", err) + os.Remove("test.bin") + return + } + defer file.Close() + + info2, err := file.Stat() + if err != nil { + fmt.Println("Error in file.Stat():", err) + } else { + fmt.Printf("file.Stat(): size = %d\n", info2.Size()) + } + + // Clean up + os.Remove("test.bin") +} diff --git a/_cmptest/interf/interf.go b/_cmptest/interf/interf.go index 3f5880b9c2..f68333ad61 100644 --- a/_cmptest/interf/interf.go +++ b/_cmptest/interf/interf.go @@ -1,7 +1,7 @@ package main import ( - "github.com/goplus/llgo/_cmptest/interf/foo" + "github.com/xgo-dev/llgo/_cmptest/interf/foo" ) func Foo() any { diff --git a/_cmptest/osexec/exec.go b/_cmptest/osexec/exec.go index 0ec5c09e0f..5075af622d 100644 --- a/_cmptest/osexec/exec.go +++ b/_cmptest/osexec/exec.go @@ -6,7 +6,7 @@ import ( "os/exec" "runtime" - "github.com/goplus/llgo/xtool/env/llvm" + "github.com/xgo-dev/llgo/xtool/env/llvm" ) func main() { diff --git a/_cmptest/printfdemo/demo.go b/_cmptest/printfdemo/demo.go index 36a2afebf3..68604e6f3f 100644 --- a/_cmptest/printfdemo/demo.go +++ b/_cmptest/printfdemo/demo.go @@ -3,7 +3,7 @@ package main import ( "fmt" - "github.com/goplus/llgo/xtool/nm" + "github.com/xgo-dev/llgo/xtool/nm" ) func main() { diff --git a/_cmptest/regexdemo/regex.go b/_cmptest/regexdemo/regex.go index 7153a2e91c..853c4173d0 100644 --- a/_cmptest/regexdemo/regex.go +++ b/_cmptest/regexdemo/regex.go @@ -3,7 +3,7 @@ package main import ( "fmt" - "github.com/goplus/llgo/xtool/env" + "github.com/xgo-dev/llgo/xtool/env" ) func main() { diff --git a/_demo/async/main.go b/_demo/async/main.go deleted file mode 100644 index 6bc63d8208..0000000000 --- a/_demo/async/main.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import ( - "time" - - "github.com/goplus/llgo/_demo/async/async" - "github.com/goplus/llgo/_demo/async/timeout" -) - -func Sleep(i int, d time.Duration) async.Future[int] { - return async.Async(func(resolve func(int)) { - timeout.Timeout(d).Then(func(async.Void) { - resolve(i) - }) - }) -} - -func main() { - async.Run(async.Async(func(resolve func(async.Void)) { - println("read file") - defer resolve(async.Void{}) - })) -} diff --git a/_demo/asmcall/asmcall.go b/_demo/c/asmcall/asmcall.go similarity index 100% rename from _demo/asmcall/asmcall.go rename to _demo/c/asmcall/asmcall.go diff --git a/_demo/asmfullcall/asmfullcall.go b/_demo/c/asmfullcall/asmfullcall.go similarity index 100% rename from _demo/asmfullcall/asmfullcall.go rename to _demo/c/asmfullcall/asmfullcall.go diff --git a/_demo/c/asmfullcall/asmfullcall_darwin_amd64.go b/_demo/c/asmfullcall/asmfullcall_darwin_amd64.go new file mode 100644 index 0000000000..dd62f19f88 --- /dev/null +++ b/_demo/c/asmfullcall/asmfullcall_darwin_amd64.go @@ -0,0 +1,30 @@ +//go:build darwin && amd64 + +package main + +import "unsafe" + +func verify() { + // 0 output & 0 input + asmFull("nop", nil) + + // 0 output & 1 input with memory address + addr := uintptr(unsafe.Pointer(&testVar)) + asmFull("movq {value}, ({addr})", map[string]any{ + "addr": addr, + "value": 43, + }) + check(43, testVar) + + // 1 output & 1 input + res1 := asmFull("movq {value}, {}", map[string]any{ + "value": 41, + }) + check(41, int(res1)) + + res2 := asmFull("leaq ({a},{b}), {}", map[string]any{ + "a": 25, + "b": 17, + }) + check(42, int(res2)) +} diff --git a/_demo/asmfullcall/asmfullcall_darwin.go b/_demo/c/asmfullcall/asmfullcall_darwin_arm64.go similarity index 100% rename from _demo/asmfullcall/asmfullcall_darwin.go rename to _demo/c/asmfullcall/asmfullcall_darwin_arm64.go diff --git a/_demo/asmfullcall/asmfullcall_linux.go b/_demo/c/asmfullcall/asmfullcall_linux.go similarity index 100% rename from _demo/asmfullcall/asmfullcall_linux.go rename to _demo/c/asmfullcall/asmfullcall_linux.go diff --git a/_demo/c/asmfullcall/asmfullcall_linux_arm64.go b/_demo/c/asmfullcall/asmfullcall_linux_arm64.go new file mode 100644 index 0000000000..7a743aeaf3 --- /dev/null +++ b/_demo/c/asmfullcall/asmfullcall_linux_arm64.go @@ -0,0 +1,31 @@ +//go:build linux && arm64 + +package main + +import "unsafe" + +func verify() { + // 0 output & 0 input + asmFull("nop", nil) + + // 0 output & 1 input with memory address + addr := uintptr(unsafe.Pointer(&testVar)) + asmFull("str {value}, [{addr}]", map[string]any{ + "addr": addr, + "value": 43, + }) + check(43, testVar) + + // 1 output & 1 input + res1 := asmFull("mov {}, {value}", map[string]any{ + "value": 41, + }) + check(41, int(res1)) + + // 1 output & 2 inputs + res2 := asmFull("add {}, {a}, {b}", map[string]any{ + "a": 25, + "b": 17, + }) + check(42, int(res2)) +} diff --git a/_demo/cabi/main.go b/_demo/c/cabi/main.go similarity index 100% rename from _demo/cabi/main.go rename to _demo/c/cabi/main.go diff --git a/_demo/cabi/wrap/wrap.c b/_demo/c/cabi/wrap/wrap.c similarity index 100% rename from _demo/cabi/wrap/wrap.c rename to _demo/c/cabi/wrap/wrap.c diff --git a/_demo/cabisret/main.go b/_demo/c/cabisret/main.go similarity index 100% rename from _demo/cabisret/main.go rename to _demo/c/cabisret/main.go diff --git a/_demo/cargs/demo.go b/_demo/c/cargs/demo.go similarity index 100% rename from _demo/cargs/demo.go rename to _demo/c/cargs/demo.go diff --git a/_demo/catomic/atomic.go b/_demo/c/catomic/atomic.go similarity index 100% rename from _demo/catomic/atomic.go rename to _demo/c/catomic/atomic.go diff --git a/_demo/cexec/exec.go b/_demo/c/cexec/exec.go similarity index 100% rename from _demo/cexec/exec.go rename to _demo/c/cexec/exec.go diff --git a/_demo/cgofull/bar.go b/_demo/c/cgofull/bar.go similarity index 100% rename from _demo/cgofull/bar.go rename to _demo/c/cgofull/bar.go diff --git a/_demo/cgofull/cgofull.go b/_demo/c/cgofull/cgofull.go similarity index 97% rename from _demo/cgofull/cgofull.go rename to _demo/c/cgofull/cgofull.go index 18fda6aab0..a97c721d50 100644 --- a/_demo/cgofull/cgofull.go +++ b/_demo/c/cgofull/cgofull.go @@ -106,8 +106,8 @@ import ( "fmt" "unsafe" - "github.com/goplus/llgo/_demo/cgofull/pymod1" - "github.com/goplus/llgo/_demo/cgofull/pymod2" + "github.com/xgo-dev/llgo/_demo/c/cgofull/pymod1" + "github.com/xgo-dev/llgo/_demo/c/cgofull/pymod2" ) //export go_callback_not_use_in_go diff --git a/_demo/cgofull/foo.c b/_demo/c/cgofull/foo.c similarity index 100% rename from _demo/cgofull/foo.c rename to _demo/c/cgofull/foo.c diff --git a/_demo/cgofull/foo.go b/_demo/c/cgofull/foo.go similarity index 100% rename from _demo/cgofull/foo.go rename to _demo/c/cgofull/foo.go diff --git a/_demo/cgofull/foo.h b/_demo/c/cgofull/foo.h similarity index 100% rename from _demo/cgofull/foo.h rename to _demo/c/cgofull/foo.h diff --git a/_demo/cgofull/py.go b/_demo/c/cgofull/py.go similarity index 100% rename from _demo/cgofull/py.go rename to _demo/c/cgofull/py.go diff --git a/_demo/cgofull/pymod1/pymod1.go b/_demo/c/cgofull/pymod1/pymod1.go similarity index 100% rename from _demo/cgofull/pymod1/pymod1.go rename to _demo/c/cgofull/pymod1/pymod1.go diff --git a/_demo/cgofull/pymod2/pymod2.go b/_demo/c/cgofull/pymod2/pymod2.go similarity index 100% rename from _demo/cgofull/pymod2/pymod2.go rename to _demo/c/cgofull/pymod2/pymod2.go diff --git a/_demo/concat/concat.go b/_demo/c/concat/concat.go similarity index 100% rename from _demo/concat/concat.go rename to _demo/c/concat/concat.go diff --git a/_demo/cppintf/cppintf.go b/_demo/c/cppintf/cppintf.go similarity index 91% rename from _demo/cppintf/cppintf.go rename to _demo/c/cppintf/cppintf.go index ab549de69f..56f6dea458 100644 --- a/_demo/cppintf/cppintf.go +++ b/_demo/c/cppintf/cppintf.go @@ -3,7 +3,7 @@ package main import ( "github.com/goplus/lib/c" "github.com/goplus/lib/c/math" - "github.com/goplus/llgo/_demo/cppintf/foo" + "github.com/xgo-dev/llgo/_demo/c/cppintf/foo" ) type Bar struct { diff --git a/_demo/cppintf/foo/bar/bar.cpp b/_demo/c/cppintf/foo/bar/bar.cpp similarity index 100% rename from _demo/cppintf/foo/bar/bar.cpp rename to _demo/c/cppintf/foo/bar/bar.cpp diff --git a/_demo/cppintf/foo/foo.go b/_demo/c/cppintf/foo/foo.go similarity index 100% rename from _demo/cppintf/foo/foo.go rename to _demo/c/cppintf/foo/foo.go diff --git a/_demo/cppmintf/cpp_multi_intf.go b/_demo/c/cppmintf/cpp_multi_intf.go similarity index 93% rename from _demo/cppmintf/cpp_multi_intf.go rename to _demo/c/cppmintf/cpp_multi_intf.go index 80c8ef0575..6901e943cb 100644 --- a/_demo/cppmintf/cpp_multi_intf.go +++ b/_demo/c/cppmintf/cpp_multi_intf.go @@ -5,7 +5,7 @@ import ( "github.com/goplus/lib/c" "github.com/goplus/lib/c/math" - "github.com/goplus/llgo/_demo/cppmintf/foo" + "github.com/xgo-dev/llgo/_demo/c/cppmintf/foo" ) type Bar struct { diff --git a/_demo/cppmintf/foo/bar/bar.cpp b/_demo/c/cppmintf/foo/bar/bar.cpp similarity index 100% rename from _demo/cppmintf/foo/bar/bar.cpp rename to _demo/c/cppmintf/foo/bar/bar.cpp diff --git a/_demo/cppmintf/foo/foo.go b/_demo/c/cppmintf/foo/foo.go similarity index 100% rename from _demo/cppmintf/foo/foo.go rename to _demo/c/cppmintf/foo/foo.go diff --git a/_demo/cppstr/cppstr.go b/_demo/c/cppstr/cppstr.go similarity index 100% rename from _demo/cppstr/cppstr.go rename to _demo/c/cppstr/cppstr.go diff --git a/_demo/crand/rand.go b/_demo/c/crand/rand.go similarity index 100% rename from _demo/crand/rand.go rename to _demo/c/crand/rand.go diff --git a/_demo/ctime/time.go b/_demo/c/ctime/time.go similarity index 100% rename from _demo/ctime/time.go rename to _demo/c/ctime/time.go diff --git a/_demo/fcntl/fcntl.go b/_demo/c/fcntl/fcntl.go similarity index 100% rename from _demo/fcntl/fcntl.go rename to _demo/c/fcntl/fcntl.go diff --git a/_demo/genints/genints.go b/_demo/c/genints/genints.go similarity index 100% rename from _demo/genints/genints.go rename to _demo/c/genints/genints.go diff --git a/_demo/getcwd/getcwd.go b/_demo/c/getcwd/getcwd.go similarity index 100% rename from _demo/getcwd/getcwd.go rename to _demo/c/getcwd/getcwd.go diff --git a/_demo/c/go.mod b/_demo/c/go.mod new file mode 100644 index 0000000000..b6450a729d --- /dev/null +++ b/_demo/c/go.mod @@ -0,0 +1,5 @@ +module github.com/xgo-dev/llgo/_demo/c + +go 1.20 + +require github.com/goplus/lib v0.3.0 diff --git a/_demo/c/go.sum b/_demo/c/go.sum new file mode 100644 index 0000000000..54e0f00c86 --- /dev/null +++ b/_demo/c/go.sum @@ -0,0 +1,2 @@ +github.com/goplus/lib v0.3.0 h1:y0ZGb5Q/RikW1oMMB4Di7XIZIpuzh/7mlrR8HNbxXCA= +github.com/goplus/lib v0.3.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0= diff --git a/_demo/hello/hello.go b/_demo/c/hello/hello.go similarity index 100% rename from _demo/hello/hello.go rename to _demo/c/hello/hello.go diff --git a/_demo/helloc/helloc.go b/_demo/c/helloc/helloc.go similarity index 100% rename from _demo/helloc/helloc.go rename to _demo/c/helloc/helloc.go diff --git a/_demo/linkname/linkname.go b/_demo/c/linkname/linkname.go similarity index 100% rename from _demo/linkname/linkname.go rename to _demo/c/linkname/linkname.go diff --git a/_demo/llama2-c/README.md b/_demo/c/llama2-c/README.md similarity index 100% rename from _demo/llama2-c/README.md rename to _demo/c/llama2-c/README.md diff --git a/_demo/llama2-c/assets/llama_cute.jpg b/_demo/c/llama2-c/assets/llama_cute.jpg similarity index 100% rename from _demo/llama2-c/assets/llama_cute.jpg rename to _demo/c/llama2-c/assets/llama_cute.jpg diff --git a/_demo/llama2-c/run.go b/_demo/c/llama2-c/run.go similarity index 99% rename from _demo/llama2-c/run.go rename to _demo/c/llama2-c/run.go index 12c3b3e8d5..5e68e3bd9a 100644 --- a/_demo/llama2-c/run.go +++ b/_demo/c/llama2-c/run.go @@ -36,7 +36,6 @@ loop: // parse command line arguments // build the Tokenizer via the tokenizer .bin file var tokenizer llama2.Tokenizer llama2.BuildTokenizer(&tokenizer, tokenizerPath, transformer.Config.VocabSize) - // build the Sampler var sampler llama2.Sampler llama2.BuildSampler(&sampler, transformer.Config.VocabSize, temperature, topp, rngSeed) diff --git a/_demo/llama2-c/tokenizer.bin b/_demo/c/llama2-c/tokenizer.bin similarity index 100% rename from _demo/llama2-c/tokenizer.bin rename to _demo/c/llama2-c/tokenizer.bin diff --git a/_demo/netdbdemo/netdb.go b/_demo/c/netdbdemo/netdb.go similarity index 100% rename from _demo/netdbdemo/netdb.go rename to _demo/c/netdbdemo/netdb.go diff --git a/_demo/qsort/qsort.go b/_demo/c/qsort/qsort.go similarity index 100% rename from _demo/qsort/qsort.go rename to _demo/c/qsort/qsort.go diff --git a/_demo/setjmp/setjmp.go b/_demo/c/setjmp/setjmp.go similarity index 100% rename from _demo/setjmp/setjmp.go rename to _demo/c/setjmp/setjmp.go diff --git a/_demo/socket/client/client.go b/_demo/c/socket/client/client.go similarity index 100% rename from _demo/socket/client/client.go rename to _demo/c/socket/client/client.go diff --git a/_demo/socket/server/server.go b/_demo/c/socket/server/server.go similarity index 100% rename from _demo/socket/server/server.go rename to _demo/c/socket/server/server.go diff --git a/_demo/c/stacksave/stacksave_amd64.go b/_demo/c/stacksave/stacksave_amd64.go new file mode 100644 index 0000000000..e1a493f879 --- /dev/null +++ b/_demo/c/stacksave/stacksave_amd64.go @@ -0,0 +1,22 @@ +//go:build amd64 + +package main + +import ( + "unsafe" + _ "unsafe" +) + +//go:linkname getsp llgo.stackSave +func getsp() unsafe.Pointer + +//go:linkname asmFull llgo.asm +func asmFull(instruction string, regs map[string]any) uintptr { return 0 } + +func main() { + sp := asmFull("movq %rsp, {}", nil) + + if sp != uintptr(getsp()) { + panic("invalid stack pointer") + } +} diff --git a/_demo/c/stacksave/stacksave_arm64.go b/_demo/c/stacksave/stacksave_arm64.go new file mode 100644 index 0000000000..6681541372 --- /dev/null +++ b/_demo/c/stacksave/stacksave_arm64.go @@ -0,0 +1,22 @@ +//go:build arm64 + +package main + +import ( + "unsafe" + _ "unsafe" +) + +//go:linkname getsp llgo.stackSave +func getsp() unsafe.Pointer + +//go:linkname asmFull llgo.asm +func asmFull(instruction string, regs map[string]any) uintptr { return 0 } + +func main() { + sp := asmFull("mov {}, sp", nil) + + if sp != uintptr(getsp()) { + panic("invalid stack pointer") + } +} diff --git a/_demo/syncdebug/syncdebug.go b/_demo/c/syncdebug/syncdebug.go similarity index 100% rename from _demo/syncdebug/syncdebug.go rename to _demo/c/syncdebug/syncdebug.go diff --git a/_demo/thread/thd.go b/_demo/c/thread/thd.go similarity index 100% rename from _demo/thread/thd.go rename to _demo/c/thread/thd.go diff --git a/_embdemo/hello-esp32/main.go b/_demo/embed/esp32/hello/main.go similarity index 100% rename from _embdemo/hello-esp32/main.go rename to _demo/embed/esp32/hello/main.go diff --git a/_embdemo/hello-esp32/uart.go b/_demo/embed/esp32/hello/uart.go similarity index 100% rename from _embdemo/hello-esp32/uart.go rename to _demo/embed/esp32/hello/uart.go diff --git a/_demo/embed/esp32/libc/main.go b/_demo/embed/esp32/libc/main.go new file mode 100644 index 0000000000..14270e4210 --- /dev/null +++ b/_demo/embed/esp32/libc/main.go @@ -0,0 +1,1162 @@ +package main + +import ( + "unsafe" + + "github.com/goplus/lib/c" + "github.com/xgo-dev/llgo/_demo/embed/esp32/watchdog" +) + +// +//go:linkname strlen C.strlen +func strlen(str *c.Char) c.SizeT + +//go:linkname strcpy C.strcpy +func strcpy(dest, src *c.Char) *c.Char + +//go:linkname strncpy C.strncpy +func strncpy(dest, src *c.Char, n c.SizeT) *c.Char + +//go:linkname strcat C.strcat +func strcat(dest, src *c.Char) *c.Char + +//go:linkname strncat C.strncat +func strncat(dest, src *c.Char, n c.SizeT) *c.Char + +//go:linkname strcmp C.strcmp +func strcmp(s1, s2 *c.Char) c.Int + +//go:linkname strncmp C.strncmp +func strncmp(s1, s2 *c.Char, n c.SizeT) c.Int + +//go:linkname strchr C.strchr +func strchr(s *c.Char, c c.Int) *c.Char + +//go:linkname strrchr C.strrchr +func strrchr(s *c.Char, c c.Int) *c.Char + +//go:linkname strstr C.strstr +func strstr(haystack, needle *c.Char) *c.Char + +//go:linkname strtok C.strtok +func strtok(str, delim *c.Char) *c.Char + +//go:linkname strtok_r C.strtok_r +func strtok_r(str, delim *c.Char, saveptr **c.Char) *c.Char + +//go:linkname strspn C.strspn +func strspn(s, accept *c.Char) c.SizeT + +//go:linkname strcspn C.strcspn +func strcspn(s, reject *c.Char) c.SizeT + +//go:linkname strdup C.strdup +func strdup(s *c.Char) *c.Char + +//go:linkname strndup C.strndup +func strndup(s *c.Char, n c.SizeT) *c.Char + +//go:linkname memcpy C.memcpy +func memcpy(dest, src unsafe.Pointer, n c.SizeT) unsafe.Pointer + +//go:linkname memmove C.memmove +func memmove(dest, src unsafe.Pointer, n c.SizeT) unsafe.Pointer + +//go:linkname memset C.memset +func memset(s unsafe.Pointer, c c.Int, n c.SizeT) unsafe.Pointer + +//go:linkname memcmp C.memcmp +func memcmp(s1, s2 unsafe.Pointer, n c.SizeT) c.Int + +//go:linkname memchr C.memchr +func memchr(s unsafe.Pointer, c c.Int, n c.SizeT) unsafe.Pointer + +//go:linkname memrchr C.memrchr +func memrchr(s unsafe.Pointer, c c.Int, n c.SizeT) unsafe.Pointer + +//go:linkname bcmp C.bcmp +func bcmp(s1, s2 unsafe.Pointer, n c.SizeT) c.Int + +//go:linkname bcopy C.bcopy +func bcopy(src, dest unsafe.Pointer, n c.SizeT) + +//go:linkname bzero C.bzero +func bzero(s unsafe.Pointer, n c.SizeT) + +//go:linkname explicit_bzero C.explicit_bzero +func explicit_bzero(s unsafe.Pointer, n c.SizeT) + +//go:linkname ffs C.ffs +func ffs(i c.Int) c.Int + +//go:linkname ffsl C.ffsl +func ffsl(i c.Long) c.Int + +//go:linkname ffsll C.ffsll +func ffsll(i c.LongLong) c.Int + +//go:linkname fls C.fls +func fls(i c.Int) c.Int + +//go:linkname flsl C.flsl +func flsl(i c.Long) c.Int + +//go:linkname flsll C.flsll +func flsll(i c.LongLong) c.Int + +//go:linkname index C.index +func index(s *c.Char, c c.Int) *c.Char + +//go:linkname rindex C.rindex +func rindex(s *c.Char, c c.Int) *c.Char + +//go:linkname strcasecmp C.strcasecmp +func strcasecmp(s1, s2 *c.Char) c.Int + +//go:linkname strncasecmp C.strncasecmp +func strncasecmp(s1, s2 *c.Char, n c.SizeT) c.Int + +//go:linkname strlcat C.strlcat +func strlcat(dst, src *c.Char, size c.SizeT) c.SizeT + +//go:linkname strlcpy C.strlcpy +func strlcpy(dst, src *c.Char, size c.SizeT) c.SizeT + +//go:linkname strsep C.strsep +func strsep(stringp **c.Char, delim *c.Char) *c.Char + +//go:linkname strcoll C.strcoll +func strcoll(s1, s2 *c.Char) c.Int + +//go:linkname strxfrm C.strxfrm +func strxfrm(dest, src *c.Char, n c.SizeT) c.SizeT + +//go:linkname strerror C.strerror +func strerror(errnum c.Int) *c.Char + +//go:linkname strerror_r C.strerror_r +func strerror_r(errnum c.Int, buf *c.Char, buflen c.SizeT) c.Int + +//go:linkname strverscmp C.strverscmp +func strverscmp(s1, s2 *c.Char) c.Int + +// +//go:linkname malloc C.malloc +func malloc(size c.SizeT) unsafe.Pointer + +//go:linkname calloc C.calloc +func calloc(nmemb, size c.SizeT) unsafe.Pointer + +//go:linkname realloc C.realloc +func realloc(ptr unsafe.Pointer, size c.SizeT) unsafe.Pointer + +//go:linkname reallocarray C.reallocarray +func reallocarray(ptr unsafe.Pointer, nmemb, size c.SizeT) unsafe.Pointer + +//go:linkname reallocf C.reallocf +func reallocf(ptr unsafe.Pointer, size c.SizeT) unsafe.Pointer + +//go:linkname valloc C.valloc +func valloc(size c.SizeT) unsafe.Pointer + +//go:linkname pvalloc C.pvalloc +func pvalloc(size c.SizeT) unsafe.Pointer + +//go:linkname memalign C.memalign +func memalign(alignment, size c.SizeT) unsafe.Pointer + +//go:linkname aligned_alloc C.aligned_alloc +func aligned_alloc(alignment, size c.SizeT) unsafe.Pointer + +//go:linkname posix_memalign C.posix_memalign +func posix_memalign(memptr *unsafe.Pointer, alignment, size c.SizeT) c.Int + +//go:linkname free C.free +func free(ptr unsafe.Pointer) + +//go:linkname malloc_size C.malloc_size +func malloc_size(ptr unsafe.Pointer) c.SizeT + +//go:linkname malloc_usable_size C.malloc_usable_size +func malloc_usable_size(ptr unsafe.Pointer) c.SizeT + +//go:linkname malloc_stats C.malloc_stats +func malloc_stats() + +//go:linkname malloc_info C.malloc_info +func malloc_info(options c.Int, stream c.FilePtr) c.Int + +//go:linkname mallopt C.mallopt +func mallopt(param, value c.Int) c.Int + +// +//go:linkname fopen C.fopen +func fopen(pathname, mode *c.Char) c.FilePtr + +//go:linkname fdopen C.fdopen +func fdopen(fd c.Int, mode *c.Char) c.FilePtr + +//go:linkname freopen C.freopen +func freopen(pathname, mode *c.Char, stream c.FilePtr) c.FilePtr + +//go:linkname fclose C.fclose +func fclose(stream c.FilePtr) c.Int + +//go:linkname fflush C.fflush +func fflush(stream c.FilePtr) c.Int + +//go:linkname fseek C.fseek +func fseek(stream c.FilePtr, offset c.Long, whence c.Int) c.Int + +//go:linkname ftell C.ftell +func ftell(stream c.FilePtr) c.Long + +// //go:linkname fseeko C.fseeko +// func fseeko(stream c.FilePtr, offset C.off_t, whence c.Int) c.Int + +// //go:linkname ftello C.ftello +// func ftello(stream c.FilePtr) C.off_t + +//go:linkname rewind C.rewind +func rewind(stream c.FilePtr) + +// //go:linkname fgetpos C.fgetpos +// func fgetpos(stream c.FilePtr, pos *C.fpos_t) c.Int + +// //go:linkname fsetpos C.fsetpos +// func fsetpos(stream c.FilePtr, pos *C.fpos_t) c.Int + +//go:linkname fread C.fread +func fread(ptr unsafe.Pointer, size, nmemb c.SizeT, stream c.FilePtr) c.SizeT + +//go:linkname fwrite C.fwrite +func fwrite(ptr unsafe.Pointer, size, nmemb c.SizeT, stream c.FilePtr) c.SizeT + +//go:linkname fgetc C.fgetc +func fgetc(stream c.FilePtr) c.Int + +//go:linkname getc C.getc +func getc(stream c.FilePtr) c.Int + +//go:linkname getchar C.getchar +func getchar() c.Int + +//go:linkname ungetc C.ungetc +func ungetc(c c.Int, stream c.FilePtr) c.Int + +//go:linkname fgets C.fgets +func fgets(s *c.Char, size c.Int, stream c.FilePtr) *c.Char + +//go:linkname fputc C.fputc +func fputc(c c.Int, stream c.FilePtr) c.Int + +//go:linkname putc C.putc +func putc(c c.Int, stream c.FilePtr) c.Int + +//go:linkname putchar C.putchar +func putchar(c c.Int) c.Int + +//go:linkname fputs C.fputs +func fputs(s *c.Char, stream c.FilePtr) c.Int + +//go:linkname puts C.puts +func puts(s *c.Char) c.Int + +//go:linkname printf C.printf +func printf(format *c.Char, __llgo_va_list ...any) c.Int + +//go:linkname fprintf C.fprintf +func fprintf(stream c.FilePtr, format *c.Char, __llgo_va_list ...any) c.Int + +//go:linkname sprintf C.sprintf +func sprintf(str, format *c.Char, __llgo_va_list ...any) c.Int + +//go:linkname snprintf C.snprintf +func snprintf(str *c.Char, size c.SizeT, format *c.Char, __llgo_va_list ...any) c.Int + +// //go:linkname vprintf C.vprintf +// func vprintf(format *c.Char, ap C.va_list) c.Int + +// //go:linkname vfprintf C.vfprintf +// func vfprintf(stream c.FilePtr, format *c.Char, ap C.va_list) c.Int + +// //go:linkname vsprintf C.vsprintf +// func vsprintf(str, format *c.Char, ap C.va_list) c.Int + +// //go:linkname vsnprintf C.vsnprintf +// func vsnprintf(str *c.Char, size c.SizeT, format *c.Char, ap C.va_list) c.Int + +//go:linkname scanf C.scanf +func scanf(format *c.Char, __llgo_va_list ...any) c.Int + +//go:linkname fscanf C.fscanf +func fscanf(stream c.FilePtr, format *c.Char, __llgo_va_list ...any) c.Int + +//go:linkname sscanf C.sscanf +func sscanf(str, format *c.Char, __llgo_va_list ...any) c.Int + +//go:linkname remove C.remove +func remove(pathname *c.Char) c.Int + +//go:linkname rename C.rename +func rename(oldpath, newpath *c.Char) c.Int + +//go:linkname tmpfile C.tmpfile +func tmpfile() c.FilePtr + +//go:linkname tmpnam C.tmpnam +func tmpnam(s *c.Char) *c.Char + +//go:linkname tempnam C.tempnam +func tempnam(dir, pfx *c.Char) *c.Char + +//go:linkname fileno C.fileno +func fileno(stream c.FilePtr) c.Int + +//go:linkname feof C.feof +func feof(stream c.FilePtr) c.Int + +//go:linkname ferror C.ferror +func ferror(stream c.FilePtr) c.Int + +//go:linkname clearerr C.clearerr +func clearerr(stream c.FilePtr) + +//go:linkname perror C.perror +func perror(s *c.Char) + +//go:linkname setvbuf C.setvbuf +func setvbuf(stream c.FilePtr, buf *c.Char, mode c.Int, size c.SizeT) c.Int + +//go:linkname setbuf C.setbuf +func setbuf(stream c.FilePtr, buf *c.Char) + +//go:linkname setbuffer C.setbuffer +func setbuffer(stream c.FilePtr, buf *c.Char, size c.SizeT) + +//go:linkname setlinebuf C.setlinebuf +func setlinebuf(stream c.FilePtr) + +//go:linkname fcloseall C.fcloseall +func fcloseall() c.Int + +//go:linkname fmemopen C.fmemopen +func fmemopen(buf unsafe.Pointer, size c.SizeT, mode *c.Char) c.FilePtr + +//go:linkname open_memstream C.open_memstream +func open_memstream(ptr **c.Char, sizeloc *c.SizeT) c.FilePtr + +//go:linkname fpurge C.fpurge +func fpurge(stream c.FilePtr) c.Int + +//go:linkname __fpurge C.__fpurge +func __fpurge(stream c.FilePtr) c.Int + +// +// //go:linkname acos C.acos +// func acos(x c.Double) c.Double + +// //go:linkname asin C.asin +// func asin(x c.Double) c.Double + +// //go:linkname atan C.atan +// func atan(x c.Double) c.Double + +// //go:linkname atan2 C.atan2 +// func atan2(y, x c.Double) c.Double + +// //go:linkname cos C.cos +// func cos(x c.Double) c.Double + +// //go:linkname sin C.sin +// func sin(x c.Double) c.Double + +// //go:linkname tan C.tan +// func tan(x c.Double) c.Double + +// //go:linkname cosh C.cosh +// func cosh(x c.Double) c.Double + +// //go:linkname sinh C.sinh +// func sinh(x c.Double) c.Double + +// //go:linkname tanh C.tanh +// func tanh(x c.Double) c.Double + +// //go:linkname exp C.exp +// func exp(x c.Double) c.Double + +// //go:linkname frexp C.frexp +// func frexp(value c.Double, exp *c.Int) c.Double + +// //go:linkname ldexp C.ldexp +// func ldexp(x c.Double, exp c.Int) c.Double + +// //go:linkname log C.log +// func log(x c.Double) c.Double + +// //go:linkname log10 C.log10 +// func log10(x c.Double) c.Double + +// //go:linkname modf C.modf +// func modf(value c.Double, iptr *c.Double) c.Double + +// //go:linkname pow C.pow +// func pow(x, y c.Double) c.Double + +// //go:linkname sqrt C.sqrt +// func sqrt(x c.Double) c.Double + +// //go:linkname ceil C.ceil +// func ceil(x c.Double) c.Double + +// //go:linkname fabs C.fabs +// func fabs(x c.Double) c.Double + +// //go:linkname floor C.floor +// func floor(x c.Double) c.Double + +// //go:linkname fmod C.fmod +// func fmod(x, y c.Double) c.Double + +// //go:linkname erf C.erf +// func erf(x c.Double) c.Double + +// //go:linkname erfc C.erfc +// func erfc(x c.Double) c.Double + +// //go:linkname gamma C.gamma +// func gamma(x c.Double) c.Double + +// //go:linkname hypot C.hypot +// func hypot(x, y c.Double) c.Double + +// //go:linkname isnan C.isnan +// func isnan(x c.Double) c.Int + +// //go:linkname isinf C.isinf +// func isinf(x c.Double) c.Int + +// //go:linkname j0 C.j0 +// func j0(x c.Double) c.Double + +// //go:linkname j1 C.j1 +// func j1(x c.Double) c.Double + +// //go:linkname jn C.jn +// func jn(n c.Int, x c.Double) c.Double + +// //go:linkname lgamma C.lgamma +// func lgamma(x c.Double) c.Double + +// //go:linkname y0 C.y0 +// func y0(x c.Double) c.Double + +// //go:linkname y1 C.y1 +// func y1(x c.Double) c.Double + +// //go:linkname yn C.yn +// func yn(n c.Int, x c.Double) c.Double + +// //go:linkname acosh C.acosh +// func acosh(x c.Double) c.Double + +// //go:linkname asinh C.asinh +// func asinh(x c.Double) c.Double + +// //go:linkname atanh C.atanh +// func atanh(x c.Double) c.Double + +// //go:linkname cbrt C.cbrt +// func cbrt(x c.Double) c.Double + +// //go:linkname expm1 C.expm1 +// func expm1(x c.Double) c.Double + +// //go:linkname ilogb C.ilogb +// func ilogb(x c.Double) c.Int + +// //go:linkname log1p C.log1p +// func log1p(x c.Double) c.Double + +// //go:linkname logb C.logb +// func logb(x c.Double) c.Double + +// //go:linkname nextafter C.nextafter +// func nextafter(x, y c.Double) c.Double + +// //go:linkname remainder C.remainder +// func remainder(x, y c.Double) c.Double + +// //go:linkname scalbn C.scalbn +// func scalbn(x c.Double, n c.Int) c.Double + +// //go:linkname scalbln C.scalbln +// func scalbln(x c.Double, n c.Long) c.Double + +// //go:linkname nearbyint C.nearbyint +// func nearbyint(x c.Double) c.Double + +// //go:linkname rint C.rint +// func rint(x c.Double) c.Double + +// //go:linkname lrint C.lrint +// func lrint(x c.Double) c.Long + +// //go:linkname llrint C.llrint +// func llrint(x c.Double) c.LongLong + +// //go:linkname round C.round +// func round(x c.Double) c.Double + +// //go:linkname lround C.lround +// func lround(x c.Double) c.Long + +// //go:linkname llround C.llround +// func llround(x c.Double) c.LongLong + +// //go:linkname trunc C.trunc +// func trunc(x c.Double) c.Double + +// //go:linkname fdim C.fdim +// func fdim(x, y c.Double) c.Double + +// //go:linkname fmax C.fmax +// func fmax(x, y c.Double) c.Double + +// //go:linkname fmin C.fmin +// func fmin(x, y c.Double) c.Double + +// //go:linkname fma C.fma +// func fma(x, y, z c.Double) c.Double + +// // 浮点版本 +// // +// //go:linkname acosf C.acosf +// func acosf(x c.Float) c.Float + +// //go:linkname asinf C.asinf +// func asinf(x c.Float) c.Float + +// //go:linkname atanf C.atanf +// func atanf(x c.Float) c.Float + +// //go:linkname atan2f C.atan2f +// func atan2f(y, x c.Float) c.Float + +// //go:linkname cosf C.cosf +// func cosf(x c.Float) c.Float + +// //go:linkname sinf C.sinf +// func sinf(x c.Float) c.Float + +// //go:linkname tanf C.tanf +// func tanf(x c.Float) c.Float + +// //go:linkname coshf C.coshf +// func coshf(x c.Float) c.Float + +// //go:linkname sinhf C.sinhf +// func sinhf(x c.Float) c.Float + +// //go:linkname tanhf C.tanhf +// func tanhf(x c.Float) c.Float + +// //go:linkname expf C.expf +// func expf(x c.Float) c.Float + +// //go:linkname frexpf C.frexpf +// func frexpf(value c.Float, exp *c.Int) c.Float + +// //go:linkname ldexpf C.ldexpf +// func ldexpf(x c.Float, exp c.Int) c.Float + +// //go:linkname logf C.log极 +// func logf(x c.Float) c.Float + +// //go:linkname log10f C.log10f +// func log10f(x c.Float) c.Float + +// //go:linkname modff C.modff +// func modff(value c.Float, iptr *c.Float) c.Float + +// //go:linkname powf C.powf +// func powf(x, y c.Float) c.Float + +// //go:linkname sqrtf C.sqrtf +// func sqrtf(x c.Float) c.Float + +// //go:linkname ceilf C.ceilf +// func ceilf(x c.Float) c.Float + +// //go:linkname fabsf C.fabsf +// func fabsf(x c.Float) c.Float + +// //go:linkname floorf C.floorf +// func floorf(x c.Float) c.Float + +// //go:linkname fmodf C.fmodf +// func fmodf(x, y c.Float) c.Float + +// //go:linkname erff C.erff +// func erff(x c.Float) c.Float + +// //go:linkname erfcf C.erfcf +// func erfcf(x c.Float) c.Float + +// //go:linkname gammaf C.gammaf +// func gammaf(x c.Float) c.Float + +// //go:linkname hypotf C.hypotf +// func hypotf(x, y c.Float) c.Float + +// //go:linkname isnanf C.isnanf +// func isnanf(x c.Float) c.Int + +// //go:linkname isinff C.isinff +// func isinff(x c.Float) c.Int + +// //go:linkname j0f C.j0f +// func j0f(x c.Float) c.Float + +// //go:linkname j1f C.j1f +// func j1f(x c.Float) c.Float + +// //go:linkname jnf C.jnf +// func jnf(n c.Int, x c.Float) c.Float + +// //go:linkname lgammaf C.lgammaf +// func lgammaf(x c.Float) c.Float + +// //go:linkname y0f C.y0f +// func y0f(x c.Float) c.Float + +// //go:linkname y1f C.y1f +// func y1f(x c.Float) c.Float + +// //go:linkname ynf C.ynf +// func ynf(n c.Int, x c.Float) c.Float + +// //go:linkname acoshf C.acoshf +// func acoshf(x c.Float) c.Float + +// //go:linkname asinhf C.asinhf +// func asinhf(x c.Float) c.Float + +// //go:linkname atanhf C.atanhf +// func atanhf(x c.Float) c.Float + +// //go:linkname cbrtf C.cbrtf +// func cbrtf(x c.Float) c.Float + +// //go:linkname expm1f C.expm1f +// func expm1f(x c.Float) c.Float + +// //go:linkname ilogbf C.ilogbf +// func ilogbf(x c.Float) c.Int + +// //go:linkname log1pf C.log1pf +// func log1pf(x c.Float) c.Float + +// //go:linkname logbf C.logbf +// func logbf(x c.Float) c.Float + +// //go:linkname nextafterf C.nextafterf +// func nextafterf(x, y c.Float) c.Float + +// //go:linkname remainderf C.remainderf +// func remainderf(x, y c.Float) c.Float + +// //go:linkname scalbnf C.scalbnf +// func scalbnf(x c.Float, n c.Int) c.Float + +// //go:linkname scalblnf C.scalblnf +// func scalblnf(x c.Float, n c.Long) c.Float + +// //go:linkname nearbyintf C.nearbyintf +// func nearbyintf(x c.Float) c.Float + +// //go:linkname rintf C.rintf +// func rintf(x c.Float) c.Float + +// //go:linkname lrintf C.lrintf +// func lrintf(x c.Float) c.Long + +// //go:linkname llrintf C.llrintf +// func llrintf(x c.Float) c.LongLong + +// //go:linkname roundf C.roundf +// func roundf(x c.Float) c.Float + +// //go:linkname lroundf C.lroundf +// func lroundf(x c.Float) c.Long + +// //go:linkname llroundf C.llroundf +// func llroundf(x c.Float) c.LongLong + +// //go:linkname truncf C.truncf +// func truncf(x c.Float) c.Float + +// //go:linkname fdimf C.fdimf +// func fdimf(x, y c.Float) c.Float + +// //go:linkname fmaxf C.fmaxf +// func fmaxf(x, y c.Float) c.Float + +// //go:linkname fminf C.fminf +// func fminf(x, y c.Float) c.Float + +// //go:linkname fmaf C.fmaf +// func fmaf(x, y, z c.Float) c.Float + +// +// //go:linkname time C.time +// func time(t *c.ti) C.time_t + +// //go:linkname localtime C.localtime +// func localtime(t *C.time_t) *C.tm + +// //go:linkname gmtime C.gmtime +// func gmtime(t *C.time_t) *C.tm + +// //go:linkname mktime C.mktime +// func mktime(t *C.tm) C.time_t + +// //go:linkname strftime C.strftime +// func strftime(s *c.Char, max c.SizeT, format *c.Char, tm *C.tm) c.SizeT + +// //go:linkname clock C.clock +// func clock() C.clock_t + +// //go:linkname difftime C.difftime +// func difftime(time1, time0 C.time_t) c.Double + +// //go:linkname asctime C.asctime +// func asctime(tm *C.tm) *c.Char + +// //go:linkname ctime C.ctime +// func ctime(t *C.time_t) *c.Char + +// +//go:linkname exit C.exit +func exit(status c.Int) + +//go:linkname abort C.abort +func abort() + +//go:linkname system C.system +func system(command *c.Char) c.Int + +//go:linkname getenv C.getenv +func getenv(name *c.Char) *c.Char + +//go:linkname setenv C.setenv +func setenv(name, value *c.Char, overwrite c.Int) c.Int + +//go:linkname unsetenv C.unsetenv +func unsetenv(name *c.Char) c.Int + +//go:linkname putenv C.putenv +func putenv(string *c.Char) c.Int + +//go:linkname clearenv C.clearenv +func clearenv() c.Int + +// //go:linkname getpid C.getpid +// func getpid() C.pid_t + +// //go:linkname getppid C.getppid +// func getppid() C.pid_t + +// //go:linkname fork C.fork +// func fork() C.pid_t + +// //go:linkname vfork C.vfork +// func vfork() C.pid_t + +//go:linkname execve C.execve +func execve(path *c.Char, argv **c.Char, envp **c.Char) c.Int + +//go:linkname execv C.execv +func execv(path *c.Char, argv **c.Char) c.Int + +//go:linkname execvp C.execvp +func execvp(file *c.Char, argv **c.Char) c.Int + +//go:linkname execl C.execl +func execl(path *c.Char, arg0 *c.Char, __llgo_va_list ...any) c.Int + +//go:linkname execlp C.execlp +func execlp(file *c.Char, arg0 *c.Char, __llgo_va_list ...any) c.Int + +//go:linkname execle C.execle +func execle(path *c.Char, arg0 *c.Char, __llgo_va_list ...any) c.Int + +// //go:linkname wait C.wait +// func wait(status *c.Int) C.piI_t + +// //go:linkname waitpid C.waitpid +// func waitpid(pid C.pid_t, status *c.Int, options c.Int) C.piI_t + +// +//go:linkname signal C.signal +func signal(sig c.Int, handler unsafe.Pointer) unsafe.Pointer + +//go:linkname raise C.raise +func raise(sig c.Int) c.Int + +// //go:linkname kill C.kill +// func kill(pid C.pid_t, sig c.Int) c.Int + +// //go:linkname alarm C.alarm +// func alarm(seconds C.uint) C.uint + +//go:linkname pause C.pause +func pause() c.Int + +// +// //go:linkname opendir C.opendir +// func opendir(name *c.Char) *C.DIR + +// //go:linkname readdir C.readdir +// func readdir(dirp *C.DIR) *C.dirent + +// //go:linkname closedir C.closedir +// func closedir(dirp *C.DIR) c.Int + +// //go:linkname rewinddir C.rewinddir +// func rewinddir(dirp *C.DIR) + +// //go:linkname seekdir C.seekdir +// func seekdir(dirp *C.DIR, loc c.Long) + +// //go:linkname telldir C.telldir +// func telldir(dirp *C.DIR) c.Long + +// +//go:linkname open C.open +func open(path *c.Char, oflag c.Int, mode c.Int) c.Int + +//go:linkname creat C.creat +func creat(path *c.Char, mode c.Int) c.Int + +//go:linkname close C.close +func close(fd c.Int) c.Int + +//go:linkname read C.read +func read(fd c.Int, buf unsafe.Pointer, count c.SizeT) c.SsizeT + +//go:linkname write C.write +func write(fd c.Int, buf unsafe.Pointer, count c.SizeT) c.SsizeT + +// //go:linkname lseek C.lseek +// func lseek(fd c.Int, offset C.off_t, whence c.Int) C.ofI_t + +//go:linkname fsync C.fsync +func fsync(fd c.Int) c.Int + +//go:linkname fdatasync C.fdatasync +func fdatasync(fd c.Int) c.Int + +// //go:linkname ftruncate C.ftruncate +// func ftruncate(fd c.Int, length C.off_t) c.Int + +//go:linkname dup C.dup +func dup(fd c.Int) c.Int + +//go:linkname dup2 C.dup2 +func dup2(oldfd, newfd c.Int) c.Int + +//go:linkname pipe C.pipe +func pipe(fildes *[2]c.Int) c.Int + +//go:linkname chdir C.chdir +func chdir(path *c.Char) c.Int + +//go:linkname fchdir C.fchdir +func fchdir(fd c.Int) c.Int + +//go:linkname getcwd C.getcwd +func getcwd(buf *c.Char, size c.SizeT) *c.Char + +//go:linkname chmod C.chmod +func chmod(path *c.Char, mode c.Int) c.Int + +//go:linkname fchmod C.fchmod +func fchmod(fd c.Int, mode c.Int) c.Int + +// //go:linkname chown C.chown +// func chown(path *c.Char, owner C.uid_t, group C.gid_t) c.Int + +// //go:linkname fchown C.fchown +// func fchown(fd c.Int, owner C.uid_t, group C.gid_t) c.Int + +// //go:linkname lchown C.lchown +// func lchown(path *c.Char, owner C.uid_t, group C.gid_t) c.Int + +//go:linkname link C.link +func link(oldpath, newpath *c.Char) c.Int + +//go:linkname unlink C.unlink +func unlink(path *c.Char) c.Int + +//go:linkname symlink C.symlink +func symlink(oldpath, newpath *c.Char) c.Int + +//go:linkname readlink C.readlink +func readlink(path *c.Char, buf *c.Char, bufsiz c.SizeT) c.SsizeT + +//go:linkname mkdir C.mkdir +func mkdir(path *c.Char, mode c.Int) c.Int + +//go:linkname rmdir C.rmdir +func rmdir(path *c.Char) c.Int + +// //go:linkname stat C.stat +// func stat(path *c.Char, buf *C.struct_stat) c.Int + +// //go:linkname fstat C.fstat +// func fstat(fd c.Int, buf *C.struct_stat) c.Int + +// //go:linkname lstat C.lstat +// func lstat(path *c.Char, buf *C.struct_stat) c.Int + +//go:linkname access C.access +func access(path *c.Char, mode c.Int) c.Int + +//go:linkname umask C.umask +func umask(mask c.Int) c.Int + +func testStringFunctions() { + println("Testing string functions...") + + // Test strlen + s := c.AllocaCStr("Hello, World!") + // FIXME: defer + // defer c.Free(unsafe.Pointer(s)) + if strlen(s) != 13 { + println("FAIL: strlen") + } else { + println("PASS: strlen") + } + + // Test strcpy + dest := make([]byte, 20) + strcpy((*c.Char)(unsafe.Pointer(&dest[0])), s) + if c.GoString((*c.Char)(unsafe.Pointer(&dest[0]))) != "Hello, World!" { + println("FAIL: strcpy") + } else { + println("PASS: strcpy") + } + + // Test strcmp + s1 := c.AllocaCStr("abc") + s2 := c.AllocaCStr("abc") + s3 := c.AllocaCStr("abd") + // defer c.Free(unsafe.Pointer(s1)) + // defer c.Free(unsafe.Pointer(s2)) + // defer c.Free(unsafe.Pointer(s3)) + if strcmp(s1, s2) != 0 { + println("FAIL: strcmp (equal)") + } else if strcmp(s1, s3) >= 0 { + println("FAIL: strcmp (less than)") + } else { + println("PASS: strcmp") + } +} + +func testMemoryFunctions() { + println("Testing memory functions...") + + // Test malloc/free + ptr := malloc(100) + if ptr == nil { + println("FAIL: malloc") + } else { + println("PASS: malloc") + free(ptr) + } + + // Test memcpy + src := []byte("Hello") + dest := make([]byte, 10) + memcpy(unsafe.Pointer(&dest[0]), unsafe.Pointer(&src[0]), 5) + if string(dest[:5]) != "Hello" { + println("FAIL: memcpy") + } else { + println("PASS: memcpy") + } + + // Test memset + memset(unsafe.Pointer(&dest[0]), 'A', 5) + if string(dest[:5]) != "AAAAA" { + println("FAIL: memset") + } else { + println("PASS: memset") + } +} + +// func testMathFunctions() { +// println("Testing math functions...") + +// // Test abs +// if c.ab(-5) != 5 || abs(5) != 5 { +// println("FAIL: abs") +// } else { +// println("PASS: abs") +// } + +// // Test atoi +// s := c.AllocaCStr("12345") +// defer c.Free(unsafe.Pointer(s)) +// if c.Atoi(s) != 12345 { +// println("FAIL: atoi") +// } else { +// println("PASS: atoi") +// } + +// } + +func testIOFunctions() { + println("Testing I/O functions...") + + // Test fopen/fclose + filename := c.AllocaCStr("test.txt") + // defer c.Free(unsafe.Pointer(filename)) + mode := c.AllocaCStr("w") + // defer c.Free(unsafe.Pointer(mode)) + file := fopen(filename, mode) + if file == nil { + println("FAIL: fopen") + } else { + println("PASS: fopen") + if fclose(file) != 0 { + println("FAIL: fclose") + } else { + println("PASS: fclose") + } + } + + // Test printf + format := c.AllocaCStr("Test: %d\n") + // defer c.Free(unsafe.Pointer(format)) + if printf(format, 42) < 0 { + println("FAIL: printf") + } else { + println("PASS: printf") + } +} + +// func testTimeFunctions() { +// println("Testing time functions...") + +// // Test time +// t := time(nil) +// if t == 0 { +// println("FAIL: time") +// } else { +// println("PASS: time") +// } + +// // Test localtime +// tm := localtime(&t) +// if tm == nil { +// println("FAIL: localtime") +// } else { +// println("PASS: localtime") +// } +// } + +// func testProcessFunctions() { +// println("Testing process functions...") + +// // Test getpid +// pid := getpid() +// if pid <= 0 { +// println("FAIL: getpid") +// } else { +// println("PASS: getpid") +// } + +// // Test getenv +// env := c.AllocaCStr("PATH") +// defer c.Free(unsafe.Pointer(env)) +// path := getenv(env) +// if path == nil { +// println("FAIL: getenv") +// } else { +// println("PASS: getenv") +// } +// } + +// func testDirectoryFunctions() { +// println("Testing directory functions...") + +// // Test opendir/closedir +// dirname := c.AllocaCStr(".") +// defer c.Free(unsafe.Pointer(dirname)) +// dir := opendir(dirname) +// if dir == nil { +// println("FAIL: opendir") +// } else { +// println("PASS: opendir") +// if closedir(dir) != 0 { +// println("FAIL: closedir") +// } else { +// println("PASS: closedir") +// } +// } +// } + +// func testFileFunctions() { +// println("Testing file functions...") + +// // Test open/close +// filename := c.AllocaCStr("testfile.txt") +// defer c.Free(unsafe.Pointer(filename)) +// fd := open(filename, C.O_CREAT|C.O_WRONLY, 0644) +// if fd < 0 { +// println("FAIL: open") +// } else { +// println("PASS: open") +// if close(fd) != 0 { +// println("FAIL: close") +// } else { +// println("PASS: close") +// } +// } +// } + +// func testSignalFunctions() { +// println("Testing signal functions...") + +// // Test signal +// handler := signal(c.Int(syscall.SIGINT), unsafe.Pointer(syscall.SIG_IGN)) +// if handler == nil { +// println("FAIL: signal") +// } else { +// println("PASS: signal") +// } + +// // Test raise +// if raise(C.SIGINT) != 0 { +// println("FAIL: raise") +// } else { +// println("PASS: raise") +// } +// } + +func main() { + watchdog.Disable() + println("Starting libc tests...") + testStringFunctions() + testMemoryFunctions() + // testMathFunctions() + testIOFunctions() + // testTimeFunctions() + // testProcessFunctions() + // testDirectoryFunctions() + // testFileFunctions() + // testSignalFunctions() + println("All tests completed!") +} diff --git a/_demo/embed/esp32/rt/main.go b/_demo/embed/esp32/rt/main.go new file mode 100644 index 0000000000..eb78c59e67 --- /dev/null +++ b/_demo/embed/esp32/rt/main.go @@ -0,0 +1,385 @@ +package main + +import ( + _ "unsafe" + + "github.com/xgo-dev/llgo/_demo/embed/esp32/watchdog" +) + +// +//go:linkname absvdi2 __absvdi2 +func absvdi2(a int64) int64 + +//go:linkname absvsi2 __absvsi2 +func absvsi2(a int32) int32 + +//go:linkname adddf3 __adddf3 +func adddf3(a, b float64) float64 + +//go:linkname addsf3 __addsf3 +func addsf3(a, b float32) float32 + +//go:linkname addvdi3 __addvdi3 +func addvdi3(a, b int64) int64 + +//go:linkname addvsi3 __addvsi3 +func addvsi3(a, b int32) int32 + +//go:linkname udivdi3 __udivdi3 +func udivdi3(a, b uint64) uint64 + +//go:linkname clzdi2 __clzdi2 +func clzdi2(a uint64) int32 + +//go:linkname clzsi2 __clzsi2 +func clzsi2(a uint32) int32 + +//go:linkname ctzdi2 __ctzdi2 +func ctzdi2(a uint64) int32 + +//go:linkname ctzsi2 __ctzsi2 +func ctzsi2(a uint32) int32 + +//go:linkname popcountdi2 __popcountdi2 +func popcountdi2(a uint64) int32 + +//go:linkname popcountsi2 __popcountsi2 +func popcountsi2(a uint32) int32 + +//go:linkname divdf3 __divdf3 +func divdf3(a, b float64) float64 + +//go:linkname divsf3 __divsf3 +func divsf3(a, b float32) float32 + +//go:linkname mulsf3 __mulsf3 +func mulsf3(a, b float32) float32 + +//go:linkname divdi3 __divdi3 +func divdi3(a, b int64) int64 + +//go:linkname muldf3 __muldf3 +func muldf3(a, b float64) float64 + +//go:linkname muldi3 __muldi3 +func muldi3(a, b int64) int64 + +//go:linkname subdf3 __subdf3 +func subdf3(a, b float64) float64 + +//go:linkname subsf3 __subsf3 +func subsf3(a, b float32) float32 + +//go:linkname extendsfdf2 __extendsfdf2 +func extendsfdf2(a float32) float64 + +//go:linkname fixdfdi __fixdfdi +func fixdfdi(a float64) int64 + +//go:linkname fixdfsi __fixdfsi +func fixdfsi(a float64) int32 + +//go:linkname fixsfdi __fixsfdi +func fixsfdi(a float32) int64 + +//go:linkname fixsfsi __fixsfsi +func fixsfsi(a float32) int32 + +//go:linkname floatdidf __floatdidf +func floatdidf(a int64) float64 + +//go:linkname floatsidf __floatsidf +func floatsidf(a int32) float64 + +//go:linkname ashldi3 __ashldi3 +func ashldi3(a int64, b int32) int64 + +//go:linkname ashrdi3 __ashrdi3 +func ashrdi3(a int64, b int32) int64 + +//go:linkname lshrdi3 __lshrdi3 +func lshrdi3(a uint64, b int32) uint64 + +//go:linkname bswapdi2 __bswapdi2 +func bswapdi2(a uint64) uint64 + +//go:linkname bswapsi2 __bswapsi2 +func bswapsi2(a uint32) uint32 + +var totalTests = 0 +var passedTests = 0 +var failedTests = 0 + +func assertEqualInt32(name string, actual, expected int32) { + totalTests++ + if actual != expected { + println("FAIL: %s: expected %d, got %d\n", name, expected, actual) + failedTests++ + } else { + passedTests++ + } +} + +func assertEqualInt64(name string, actual, expected int64) { + totalTests++ + if actual != expected { + println("FAIL: %s: expected %d, got %d\n", name, expected, actual) + failedTests++ + } else { + passedTests++ + } +} + +func assertEqualUint32(name string, actual, expected uint32) { + totalTests++ + if actual != expected { + println("FAIL: %s: expected %d, got %d\n", name, expected, actual) + failedTests++ + } else { + passedTests++ + } +} + +func assertEqualUint64(name string, actual, expected uint64) { + totalTests++ + if actual != expected { + println("FAIL: %s: expected %d, got %d\n", name, expected, actual) + failedTests++ + } else { + passedTests++ + } +} + +func assertEqualFloat32(name string, actual, expected float32, epsilon float32) { + totalTests++ + diff := actual - expected + if diff < 0 { + diff = -diff + } + if diff > epsilon { + println("FAIL: %s: expected %f, got %f\n", name, expected, actual) + failedTests++ + } else { + passedTests++ + } +} + +func assertEqualFloat64(name string, actual, expected float64, epsilon float64) { + totalTests++ + diff := actual - expected + if diff < 0 { + diff = -diff + } + if diff > epsilon { + println("FAIL: %s: expected %f, got %f\n", name, expected, actual) + failedTests++ + } else { + passedTests++ + } +} + +func testAbsFunctions() { + println("Testing absolute value functions...") + + // Test absvsi2 + assertEqualInt32("absvsi2", absvsi2(12345), 12345) + assertEqualInt32("absvsi2", absvsi2(-12345), 12345) + assertEqualInt32("absvsi2", absvsi2(0), 0) + + // Test absvdi2 + assertEqualInt64("absvdi2", absvdi2(1234567890123456789), 1234567890123456789) + assertEqualInt64("absvdi2", absvdi2(-1234567890123456789), 1234567890123456789) + assertEqualInt64("absvdi2", absvdi2(0), 0) +} + +func testAddFunctions() { + println("Testing addition functions...") + + // Test addvsi3 + assertEqualInt32("addvsi3", addvsi3(1000, 2000), 3000) + assertEqualInt32("addvsi3", addvsi3(-1000, -2000), -3000) + assertEqualInt32("addvsi3", addvsi3(0, 0), 0) + + // Test addvdi3 + assertEqualInt64("addvdi3", addvdi3(1000000000, 2000000000), 3000000000) + assertEqualInt64("addvdi3", addvdi3(-1000000000, -2000000000), -3000000000) + assertEqualInt64("addvdi3", addvdi3(0, 0), 0) + + // Test adddf3 + assertEqualFloat64("adddf3", adddf3(3.14, 2.71), 5.85, 1e-10) + assertEqualFloat64("adddf3", adddf3(-3.14, -2.71), -5.85, 1e-10) + assertEqualFloat64("adddf3", adddf3(0.0, 0.0), 0.0, 1e-10) + + // Test addsf3 + assertEqualFloat32("addsf3", addsf3(3.14, 2.71), 5.85, 1e-6) + assertEqualFloat32("addsf3", addsf3(-3.14, -2.71), -5.85, 1e-6) + assertEqualFloat32("addsf3", addsf3(0.0, 0.0), 0.0, 1e-6) +} + +func testCountFunctions() { + println("Testing count functions...") + + // Test clzsi2 - count leading zeros in 32-bit integer + assertEqualInt32("clzsi2", clzsi2(1), 31) // 0x00000001 has 31 leading zeros + assertEqualInt32("clzsi2", clzsi2(0x80000000), 0) // 0x80000000 has 0 leading zeros + assertEqualInt32("clzsi2", clzsi2(0), 32) // 0 has 32 leading zeros + + // FIXME + // // Test clzdi2 - count leading zeros in 64-bit integer + // assertEqualInt32("clzdi2", clzdi2(1), 63) // 0x0000000000000001 has 63 leading zeros + // assertEqualInt32("clzdi2", clzdi2(0x8000000000000000), 0) // 0x8000000000000000 has 0 leading zeros + // assertEqualInt32("clzdi2", clzdi2(0), 64) // 0 has 64 leading zeros + + // Test ctzsi2 - count trailing zeros in 32-bit integer + assertEqualInt32("ctzsi2", ctzsi2(1<<5), 5) // 0x00000020 has 5 trailing zeros + assertEqualInt32("ctzsi2", ctzsi2(0x80000000), 31) // 0x80000000 has 31 trailing zeros + assertEqualInt32("ctzsi2", ctzsi2(0), 32) // 0 has 32 trailing zeros + + // Test ctzdi2 - count trailing zeros in 64-bit integer + assertEqualInt32("ctzdi2", ctzdi2(1<<10), 10) // 0x0000000000000400 has 10 trailing zeros + assertEqualInt32("ctzdi2", ctzdi2(0x8000000000000000), 63) // 0x8000000000000000 has 63 trailing zeros + assertEqualInt32("ctzdi2", ctzdi2(0), 64) // 0 has 64 trailing zeros + + // Test popcountsi2 - population count of 32-bit integer + assertEqualInt32("popcountsi2", popcountsi2(0xF0F0F0F0), 16) // 0xF0F0F0F0 has 16 ones + assertEqualInt32("popcountsi2", popcountsi2(0), 0) // 0 has 0 ones + assertEqualInt32("popcountsi2", popcountsi2(0xFFFFFFFF), 32) // 0xFFFFFFFF has 32 ones + + // Test popcountdi2 - population count of 64-bit integer + assertEqualInt32("popcountdi2", popcountdi2(0xFFFF0000FFFF0000), 32) // 0xFFFF0000FFFF0000 has 32 ones + assertEqualInt32("popcountdi2", popcountdi2(0), 0) // 0 has 0 ones + assertEqualInt32("popcountdi2", popcountdi2(0xFFFFFFFFFFFFFFFF), 64) // 0xFFFFFFFFFFFFFFFF has 64 ones +} + +func testDivisionFunctions() { + println("Testing division functions...") + + // Test udivdi3 - unsigned 64-bit division + assertEqualUint64("udivdi3", udivdi3(100, 5), 20) + assertEqualUint64("udivdi3", udivdi3(18446744073709551615, 3), 6148914691236517205) + assertEqualUint64("udivdi3", udivdi3(0, 123456789), 0) + + // Test divdi3 - signed 64-bit division + assertEqualInt64("divdi3", divdi3(20, 3), 6) + assertEqualInt64("divdi3", divdi3(-20, 3), -6) + assertEqualInt64("divdi3", divdi3(20, -3), -6) + + // Test divdf3 - double precision division + assertEqualFloat64("divdf3", divdf3(20.0, 3.0), 6.666666666666667, 1e-10) + assertEqualFloat64("divdf3", divdf3(-20.0, 3.0), -6.666666666666667, 1e-10) + + // Test divsf3 - single precision division + assertEqualFloat32("divsf3", divsf3(20.0, 3.0), 6.6666665, 1e-6) + assertEqualFloat32("divsf3", divsf3(-20.0, 3.0), -6.6666665, 1e-6) +} + +func testMultiplicationFunctions() { + println("Testing multiplication functions...") + + // Test muldi3 - signed 64-bit multiplication + assertEqualInt64("muldi3", muldi3(5, 4), 20) + assertEqualInt64("muldi3", muldi3(-5, 4), -20) + assertEqualInt64("muldi3", muldi3(5, -4), -20) + + // Test muldf3 - double precision multiplication + assertEqualFloat64("muldf3", muldf3(3.0, 4.0), 12.0, 1e-10) + assertEqualFloat64("muldf3", muldf3(-3.0, 4.0), -12.0, 1e-10) + + // Test mulsf3 - single precision multiplication + assertEqualFloat32("mulsf3", mulsf3(3.0, 4.0), 12.0, 1e-6) + assertEqualFloat32("mulsf3", mulsf3(-3.0, 4.0), -12.0, 1e-6) +} + +func testSubtractionFunctions() { + println("Testing subtraction functions...") + + // Test subdf3 - double precision subtraction + assertEqualFloat64("subdf3", subdf3(5.0, 3.0), 2.0, 1e-10) + assertEqualFloat64("subdf3", subdf3(3.0, 5.0), -2.0, 1e-10) + + // Test subsf3 - single precision subtraction + assertEqualFloat32("subsf3", subsf3(5.0, 3.0), 2.0, 1e-6) + assertEqualFloat32("subsf3", subsf3(3.0, 5.0), -2.0, 1e-6) +} + +func testConversionFunctions() { + println("Testing conversion functions...") + + // Test extendsfdf2 - single to double precision conversion + // FIXME + // assertEqualFloat64("extendsfdf2", extendsfdf2(3.14), 3.14, 1e-10) + + // Test fixdfsi - double precision to int32 conversion + assertEqualInt32("fixdfsi", fixdfsi(123.45), 123) + assertEqualInt32("fixdfsi", fixdfsi(-123.45), -123) + + // Test fixsfsi - single precision to int32 conversion + assertEqualInt32("fixsfsi", fixsfsi(123.45), 123) + assertEqualInt32("fixsfsi", fixsfsi(-123.45), -123) + + // Test fixdfdi - double precision to int64 conversion + assertEqualInt64("fixdfdi", fixdfdi(123456789.123), 123456789) + assertEqualInt64("fixdfdi", fixdfdi(-123456789.123), -123456789) + + // Test fixsfdi - single precision to int64 conversion + // FIXME + // assertEqualInt64("fixsfdi", fixsfdi(123456789.123), 123456789) + // assertEqualInt64("fixsfdi", fixsfdi(-123456789.123), -123456789) + + // Test floatsidf - int32 to double precision conversion + assertEqualFloat64("floatsidf", floatsidf(42), 42.0, 1e-10) + assertEqualFloat64("floatsidf", floatsidf(-100), -100.0, 1e-10) + + // Test floatdidf - int64 to double precision conversion + assertEqualFloat64("floatdidf", floatdidf(123456789), 123456789.0, 1e-10) + assertEqualFloat64("floatdidf", floatdidf(-123456789), -123456789.0, 1e-10) +} + +func testShiftFunctions() { + println("Testing shift functions...") + + // Test ashldi3 - arithmetic shift left + assertEqualInt64("ashldi3", ashldi3(1, 10), 1024) + + // Test ashrdi3 - arithmetic shift right + assertEqualInt64("ashrdi3", ashrdi3(1024, 10), 1) + + // Test lshrdi3 - logical shift right + assertEqualUint64("lshrdi3", lshrdi3(1024, 10), 1) + assertEqualUint64("lshrdi3", lshrdi3(0x8000000000000000, 63), 1) +} + +func testBitManipulationFunctions() { + println("Testing bit manipulation functions...") + + // Test bswapsi2 - byte swap 32-bit integer + assertEqualUint32("bswapsi2", bswapsi2(0x12345678), 0x78563412) + + // Test bswapdi2 - byte swap 64-bit integer + assertEqualUint64("bswapdi2", bswapdi2(0x1234567890ABCDEF), 0xEFCDAB9078563412) +} + +func main() { + watchdog.Disable() + println("Testing Compiler-RT Builtins Functions") + println("=====================================") + testAbsFunctions() + testAddFunctions() + testCountFunctions() + testDivisionFunctions() + testMultiplicationFunctions() + testSubtractionFunctions() + testConversionFunctions() + testShiftFunctions() + testBitManipulationFunctions() + + println("\n=====================================") + println("Test Results: %d total, %d passed, %d failed\n", totalTests, passedTests, failedTests) + println("=====================================") + + if failedTests == 0 { + println("All tests PASSED!") + } else { + println("Some tests FAILED!") + } +} diff --git a/_demo/embed/esp32/watchdog/watchdog.go b/_demo/embed/esp32/watchdog/watchdog.go new file mode 100644 index 0000000000..1ee927f557 --- /dev/null +++ b/_demo/embed/esp32/watchdog/watchdog.go @@ -0,0 +1,16 @@ +package watchdog + +import ( + "unsafe" + _ "unsafe" +) + +//go:linkname StoreUint32 llgo.atomicStore +func StoreUint32(addr *uint32, val uint32) + +func Disable() { + StoreUint32((*uint32)(unsafe.Pointer(uintptr(0x3ff480A4))), 0x50D83AA1) + + StoreUint32((*uint32)(unsafe.Pointer(uintptr(0x3ff4808C))), 0) + StoreUint32((*uint32)(unsafe.Pointer(uintptr(0x3ff5f048))), 0) +} diff --git a/_embdemo/write-esp32/main.go b/_demo/embed/esp32/write/main.go similarity index 100% rename from _embdemo/write-esp32/main.go rename to _demo/embed/esp32/write/main.go diff --git a/_demo/embed/esp32c3/float-1664/main.go b/_demo/embed/esp32c3/float-1664/main.go new file mode 100644 index 0000000000..3a2291aeb3 --- /dev/null +++ b/_demo/embed/esp32c3/float-1664/main.go @@ -0,0 +1,68 @@ +package main + +type point struct { + x float64 + y float64 +} + +type myPoint = point + +func (p *point) scale(factor float64) { + p.x *= factor + p.y *= factor +} + +func (p *myPoint) move(dx, dy float64) { + p.x += dx + p.y += dy +} + +func pair(f float64) (int, float64) { + return 1, f +} + +type bar struct { + pb *byte + f float32 +} + +func toBar(v any) (ret bar, ok bool) { + ret, ok = v.(bar) + return +} + +type foo struct { + pb *byte + f float32 +} + +func toFoo(v any) (ret foo, ok bool) { + ret, ok = v.(foo) + return +} + +func xadd(a, b int) int { + return a + b +} + +func double(v float64) float64 { + return v * 2 +} + +func main() { + pt := &myPoint{1, 2} + pt.scale(2) + pt.move(3, 4) + println(pt.x, pt.y) + + i, f := pair(2.0) + println(i, f) + + ret, ok := toBar(nil) + println(ret.pb, ret.f, "notOk:", !ok) + + ret2, ok2 := toFoo(foo{}) + println(ret2.pb, ret2.f, ok2) + + println(xadd(1, 2), double(3.14)) +} diff --git a/_demo/embed/esp32c3/print-float-1723/main.go b/_demo/embed/esp32c3/print-float-1723/main.go new file mode 100644 index 0000000000..e381089d90 --- /dev/null +++ b/_demo/embed/esp32c3/print-float-1723/main.go @@ -0,0 +1,9 @@ +package main + +import "github.com/goplus/lib/c" + +func main() { + println("go", 0) + c.Fflush(nil) + c.Printf(c.Str("f=%f\n"), 1.1) +} diff --git a/_demo/embed/esp32c3/write/main.go b/_demo/embed/esp32c3/write/main.go new file mode 100644 index 0000000000..e9b027d317 --- /dev/null +++ b/_demo/embed/esp32c3/write/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/goplus/lib/c" + +func main() { + c.Printf(c.Str("Hello from ESP32-C3 via USB Serial JTAG!\n")) +} diff --git a/_demo/embed/export/main.go b/_demo/embed/export/main.go new file mode 100644 index 0000000000..0fcdcce198 --- /dev/null +++ b/_demo/embed/export/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "github.com/goplus/lib/c" +) + +// This demo shows how to use //export with different symbol names on embedded targets. +// +// On embedded targets, you can export Go functions with different C symbol names. +// This is useful for hardware interrupt handlers that require specific names. + +// Standard Go export - same name +// +//export HelloWorld +func HelloWorld() { + c.Printf(c.Str("Hello from ")) + c.Printf(c.Str("HelloWorld\n")) +} + +// Embedded target export - different name +// Go function name: interruptLPSPI2 +// Exported C symbol: LPSPI2_IRQHandler +// +//export LPSPI2_IRQHandler +func interruptLPSPI2() { + c.Printf(c.Str("LPSPI2 interrupt ")) + c.Printf(c.Str("handler called\n")) +} + +// Embedded target export - different name +// Go function name: systemTickHandler +// Exported C symbol: SysTick_Handler +// +//export SysTick_Handler +func systemTickHandler() { + c.Printf(c.Str("SysTick ")) + c.Printf(c.Str("handler called\n")) +} + +// Embedded target export - different name +// Go function name: Add +// Exported C symbol: AddFunc +// +//export AddFunc +func Add(a, b int) int { + result := a + b + c.Printf(c.Str("AddFunc(%d, %d) = %d\n"), a, b, result) + return result +} + +func main() { + c.Printf(c.Str("=== Export Demo ===\n\n")) + + // Call exported functions directly from Go + c.Printf(c.Str("Calling HelloWorld:\n")) + HelloWorld() + + c.Printf(c.Str("\nSimulating hardware interrupts:\n")) + interruptLPSPI2() + systemTickHandler() + + c.Printf(c.Str("\nTesting function with return value:\n")) + result := Add(10, 20) + c.Printf(c.Str("Result: %d\n"), result) + + c.Printf(c.Str("\n=== Demo Complete ===\n")) +} diff --git a/_demo/embed/export/verify_export.sh b/_demo/embed/export/verify_export.sh new file mode 100755 index 0000000000..41dd62b15f --- /dev/null +++ b/_demo/embed/export/verify_export.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +set -e + +echo "Building for embedded target..." + +# Build for embedded target as executable +# Use llgo directly instead of llgo.sh to avoid go.mod version check +llgo build -o test-verify --target=esp32 . + +echo "Checking exported symbols..." + +# Get exported symbols +exported_symbols=$(nm -gU ./test-verify.elf | grep -E "(HelloWorld|LPSPI2_IRQHandler|SysTick_Handler|AddFunc)" | awk '{print $NF}') + +echo "" +echo "Exported symbols:" +echo "$exported_symbols" | awk '{print " " $0}' +echo "" + +# Check expected symbols +expected=("HelloWorld" "LPSPI2_IRQHandler" "SysTick_Handler" "AddFunc") +missing="" + +for symbol in "${expected[@]}"; do + if ! echo "$exported_symbols" | grep -q "^$symbol$"; then + missing="$missing $symbol" + fi +done + +if [ -n "$missing" ]; then + echo "❌ Missing symbols:$missing" + exit 1 +fi + +echo "✅ Symbol name mapping verification:" +echo " HelloWorld -> HelloWorld" +echo " interruptLPSPI2 -> LPSPI2_IRQHandler" +echo " systemTickHandler -> SysTick_Handler" +echo " Add -> AddFunc" +echo "" +echo "🎉 All export symbols verified successfully!" +echo "" + +echo "Testing that non-embedded target rejects different export names..." +# Build without --target should fail with panic +if llgo build -o test-notarget . 2>&1 | grep -q 'export comment has wrong name "LPSPI2_IRQHandler"'; then + echo "✅ Correctly rejected different export name on non-embedded target" +else + echo "❌ Should have panicked with 'export comment has wrong name' error" + exit 1 +fi +echo "" +echo "Note: Different symbol names are only supported on embedded targets." diff --git a/_demo/embed/go.mod b/_demo/embed/go.mod new file mode 100644 index 0000000000..9d8a374c62 --- /dev/null +++ b/_demo/embed/go.mod @@ -0,0 +1,5 @@ +module github.com/xgo-dev/llgo/_demo/embed + +go 1.20 + +require github.com/goplus/lib v0.3.0 diff --git a/_demo/embed/go.sum b/_demo/embed/go.sum new file mode 100644 index 0000000000..54e0f00c86 --- /dev/null +++ b/_demo/embed/go.sum @@ -0,0 +1,2 @@ +github.com/goplus/lib v0.3.0 h1:y0ZGb5Q/RikW1oMMB4Di7XIZIpuzh/7mlrR8HNbxXCA= +github.com/goplus/lib v0.3.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0= diff --git a/_demo/targetsbuild/C/c.go b/_demo/embed/targetsbuild/C/c.go similarity index 100% rename from _demo/targetsbuild/C/c.go rename to _demo/embed/targetsbuild/C/c.go diff --git a/_demo/embed/targetsbuild/build.sh b/_demo/embed/targetsbuild/build.sh new file mode 100755 index 0000000000..23171f9014 --- /dev/null +++ b/_demo/embed/targetsbuild/build.sh @@ -0,0 +1,268 @@ +#!/bin/bash + +# Function to display usage information +show_usage() { + cat << EOF +Usage: $(basename "$0") [OPTIONS] [TARGET_FILE] + +Build targets for llgo across multiple platforms. + +OPTIONS: + -h, --help Show this help message and exit + +ARGUMENTS: + TEST_DIR Required. The test directory containing main.go to build. + Examples: empty, defer + + TARGET_FILE Optional. A text file containing target names, one per line. + Lines starting with # are treated as comments and ignored. + Empty lines are also ignored. + +BEHAVIOR: + Without TARGET_FILE: + - Automatically discovers all targets from ../../targets/*.json files + - Extracts target names from JSON filenames + + With TARGET_FILE: + - Reads target names from the specified file + - Supports comments (lines starting with #) + - Ignores empty lines and whitespace + +IGNORED TARGETS: + The following targets are automatically ignored and not built: + atmega1280, atmega2560, atmega328p, atmega32u4, attiny85, + fe310, k210, riscv32, riscv64, rp2040 + +RESULT CATEGORIES: + ✅ Successful: Build completed successfully + 🔕 Ignored: Target is in the ignore list + ⚠️ Warned: Build failed with configuration warnings + ❌ Failed: Build failed with errors + +EXIT CODES: + 0 All builds successful, ignored, or warned only + 1 One or more builds failed with errors + +EXAMPLES: + $(basename "$0") empty # Build empty test for all targets + $(basename "$0") defer # Build defer test for all targets + $(basename "$0") empty my-targets.txt # Build empty test for specific targets + $(basename "$0") --help # Show this help + +TARGET FILE FORMAT: + # This is a comment + esp32 + cortex-m4 + + # Another comment + riscv64 +EOF +} + +# Check for help flag +if [[ "$1" == "-h" || "$1" == "--help" ]]; then + show_usage + exit 0 +fi + +# Check for required TEST_DIR argument +if [ $# -lt 1 ]; then + echo "Error: TEST_DIR is required." + echo "Use '$(basename "$0") --help' for usage information." + exit 1 +fi + +# Check for invalid number of arguments +if [ $# -gt 2 ]; then + echo "Error: Too many arguments." + echo "Use '$(basename "$0") --help' for usage information." + exit 1 +fi + +# Get test directory +test_dir="$1" +if [ ! -d "$test_dir" ]; then + echo "Error: Test directory '$test_dir' not found." + echo "Use '$(basename "$0") --help' for usage information." + exit 1 +fi + +echo "Testing: $test_dir" +echo "----------------------------------------" + +# Initialize arrays to store results +successful_targets=() +ignored_targets=() +warned_targets=() +failed_targets=() +targets_to_build=() + +# Define ignore list based on test directory +case "$test_dir" in + empty) + ignore_list=( + "atmega1280" + "atmega2560" + "atmega328p" + "atmega32u4" + "attiny85" + "fe310" + "k210" + "riscv32" + "riscv64" + "rp2040" + ) + ;; + defer) + ignore_list=( + "atmega1280" + "atmega2560" + "atmega328p" + "atmega32u4" + "attiny85" + "fe310" + "k210" + "riscv32" + "riscv64" + "rp2040" + + # :0: error: out of range branch target (expected an integer in the range -4096 to 4095) + # error: cannot compile inline asm + "digispark" + + #In file included from /home/runner/work/llgo/llgo/runtime/internal/clite/debug/_wrap/debug.c:9: + # In file included from /usr/include/dlfcn.h:22: + # In file included from /usr/include/features.h:394: + # /usr/include/features-time64.h:20:10: fatal error: 'bits/wordsize.h' file not found + # 20 | #include + # | ^~~~~~~~~~~~~~~~~ + # 1 error generated. + # panic: exit status 1 + "nintendoswitch" + + # ld.lld: error: /home/runner/.cache/llgo/crosscompile/picolibc-v0.1.0/libc-avr.a(-home-runner-.cache-llgo-crosscompile-picolibc-v0.1.0-newlib-libc-tinystdio-printf.c1170247836.o): cannot link object files with incompatible target ISA + "simavr" + + # libc symbol lack + "arduino-leonardo" + "arduino-mega1280" + "arduino-mega2560" + "arduino-nano-new" + "arduino-nano" + "arduino" + "atmega1284p" + "atmega328pb" + "attiny1616" + "cortex-m0" + "cortex-m0plus" + "cortex-m3" + "cortex-m33" + "cortex-m4" + "cortex-m7" + "d1mini" + "esp8266" + "gameboy-advance" + "hifive1b" + "maixbit" + "nodemcu" + "riscv-qemu" + "riscv32-esp" + "stm32l0x2" + "tkey" + ) + ;; + *) + echo "Error: Unknown test directory '$test_dir'. Please add ignore_list in build.sh." + exit 1 + ;; +esac + +# Build the targets list based on input method +if [ $# -eq 2 ]; then + # Read targets from file + target_file="$2" + if [ ! -f "$target_file" ]; then + echo "Error: Target file '$target_file' not found." + echo "Use '$(basename "$0") --help' for usage information." + exit 1 + fi + + while IFS= read -r target || [[ -n "$target" ]]; do + # Skip empty lines and comments + if [[ -z "$target" || "$target" =~ ^[[:space:]]*# ]]; then + continue + fi + + # Trim whitespace + target=$(echo "$target" | xargs) + targets_to_build+=("$target") + done < "$target_file" +else + # Use targets from *.json files + for target_file in ../../../targets/*.json; do + # Extract target name from filename (remove path and .json extension) + target=$(basename "$target_file" .json) + targets_to_build+=("$target") + done +fi + +# Process each target +for target in "${targets_to_build[@]}"; do + # Check if target is in ignore list + if [[ " ${ignore_list[@]} " =~ " ${target} " ]]; then + echo 🔕 $target "(ignored)" + ignored_targets+=("$target") + continue + fi + + output=$(../../../dev/llgo.sh build -target $target -o hello.elf "./$test_dir" 2>&1) + if [ $? -eq 0 ]; then + echo ✅ $target `file hello.elf` + successful_targets+=("$target") + else + # Check if output contains warning messages + if echo "$output" | grep -q "does not have a valid LLVM target triple\|does not have a valid CPU configuration"; then + echo ⚠️ $target + echo "$output" + warned_targets+=("$target") + else + echo ❌ $target + echo "$output" + failed_targets+=("$target") + fi + fi +done + +echo "" +echo "----------------------------------------" + +# Output successful targets +echo "Successful targets (${#successful_targets[@]} total):" +for target in "${successful_targets[@]}"; do + echo "$target" +done + +echo "" +echo "Ignored targets (${#ignored_targets[@]} total):" +for target in "${ignored_targets[@]}"; do + echo "$target" +done + +echo "" +echo "Warned targets (${#warned_targets[@]} total):" +for target in "${warned_targets[@]}"; do + echo "$target" +done + +echo "" +echo "Failed targets (${#failed_targets[@]} total):" +for target in "${failed_targets[@]}"; do + echo "$target" +done + +# Exit with error code if there are any failed targets +if [ ${#failed_targets[@]} -gt 0 ]; then + echo "" + echo "Build failed with ${#failed_targets[@]} failed targets." + exit 1 +fi diff --git a/_demo/embed/targetsbuild/defer/main.go b/_demo/embed/targetsbuild/defer/main.go new file mode 100644 index 0000000000..23af3850ed --- /dev/null +++ b/_demo/embed/targetsbuild/defer/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "github.com/goplus/lib/c" + _ "github.com/xgo-dev/llgo/_demo/embed/targetsbuild/C" +) + +func main() { + defer c.Printf(c.Str("defer 1\n")) + defer c.Printf(c.Str("defer 2\n")) + var counter int = 1 + + if counter > 0 { + defer c.Printf(c.Str("defer in if\n")) + } else { + defer c.Printf(c.Str("defer in else\n")) + } + + for i := 0; i < 3; i++ { + defer c.Printf(c.Str("defer in loop %d\n"), i) + } + panic("panic occured") +} diff --git a/_demo/embed/targetsbuild/empty/main.go b/_demo/embed/targetsbuild/empty/main.go new file mode 100644 index 0000000000..55c786d3b7 --- /dev/null +++ b/_demo/embed/targetsbuild/empty/main.go @@ -0,0 +1,6 @@ +package main + +import _ "github.com/xgo-dev/llgo/_demo/embed/targetsbuild/C" + +func main() { +} diff --git a/_demo/embed/test-esp-serial-startup.sh b/_demo/embed/test-esp-serial-startup.sh new file mode 100755 index 0000000000..1b75af950f --- /dev/null +++ b/_demo/embed/test-esp-serial-startup.sh @@ -0,0 +1,122 @@ +#!/bin/bash +# ESP serial targets smoke test (emulator run only). +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TEMP_DIR="$SCRIPT_DIR/.test_tmp_$$" + +CASE_ROOT="$SCRIPT_DIR/testdata/esp32-serial" + +cleanup() { + rm -rf "$TEMP_DIR" +} +trap cleanup EXIT + +run_emulator_smoke() { + local target="$1" + local label="$2" + local case_dir="$3" + local expected_file="$4" + + echo "" + echo "=== Smoke: $label emulator output ===" + + local run_out_file + run_out_file=$(mktemp "${TEMP_DIR}/run_${target}.XXXX.log") + + set +e + llgo run -a -target="$target" -emulator "$case_dir" 2>&1 | tee "$run_out_file" + local run_rc=${PIPESTATUS[0]} + set -e + + local run_out + run_out=$(cat "$run_out_file") + rm -f "$run_out_file" + + if [ "$run_rc" -ne 0 ]; then + echo "[WARN] $label emulator exited with code $run_rc; validating output tail instead" + fi + + local normalized_out + normalized_out=$(printf "%s\n" "$run_out" | tr -d '\r') + + local expected_tail + expected_tail=$(cat "$expected_file") + + local normalized_expected + normalized_expected=$(printf "%s" "$expected_tail" | tr -d '\r') + + local n + n=$(printf "%s\n" "$normalized_expected" | awk 'END{print NR}') + if [ -z "$n" ] || [ "$n" -le 0 ]; then + echo "✗ FAIL: invalid expected tail for $label" + exit 1 + fi + + local actual_tail + actual_tail=$(printf "%s\n" "$normalized_out" | awk 'NF{print}' | tail -n "$n") + + if [ "$actual_tail" = "$normalized_expected" ]; then + echo "✓ PASS: $label output tail (last $n line(s)) matched" + else + echo "✗ FAIL: $label output mismatch" + echo "Expected tail (last $n line(s)):" + printf "%s\n" "$normalized_expected" + echo "Actual tail:" + printf "%s\n" "$actual_tail" + echo "" + echo "Full output:" + echo "$run_out" + exit 1 + fi +} + +run_case() { + local case_dir="$1" + local case_name + case_name="$(basename "$case_dir")" + + local expected_file="$case_dir/expect.txt" + if [ ! -f "$case_dir/main.go" ]; then + echo "✗ FAIL: missing testcase source: $case_dir/main.go" + exit 1 + fi + + run_emulator_smoke "esp32c3-basic" "ESP32-C3 [$case_name]" "$case_dir" "$expected_file" + run_emulator_smoke "esp32" "ESP32 [$case_name]" "$case_dir" "$expected_file" +} + +run_all_cases() { + local found=0 + local case_dir + exec 3< <(find "$CASE_ROOT" -mindepth 1 -maxdepth 1 -type d | sort) + while IFS= read -r case_dir <&3; do + if [ -f "$case_dir/main.go" ] && [ -f "$case_dir/expect.txt" ]; then + found=1 + run_case "$case_dir" + fi + done + exec 3<&- + + if [ "$found" -eq 0 ]; then + echo "✗ FAIL: no testcase found under $CASE_ROOT (need main.go + expect.txt)" + exit 1 + fi +} + +mkdir -p "$TEMP_DIR" +if [ ! -d "$CASE_ROOT" ]; then + echo "✗ FAIL: testcase root not found: $CASE_ROOT" + exit 1 +fi + +cd "$SCRIPT_DIR" + +echo "" +echo "=== ESP Serial Smoke Tests: Emulator Run ===" +run_all_cases + +echo "" +echo "=== Smoke Tests Passed ===" +echo "✓ ESP32-C3 and ESP32 emulator smoke passed for all serial testcases" +echo "✓ Cases are discovered only from testdata/esp32-serial (main.go + expect.txt)" diff --git a/_demo/embed/test_esp32c3_startup.sh b/_demo/embed/test_esp32c3_startup.sh new file mode 100755 index 0000000000..647cb234d2 --- /dev/null +++ b/_demo/embed/test_esp32c3_startup.sh @@ -0,0 +1,250 @@ +#!/bin/bash +# ESP32-C3 startup/linker regression test +# +# Keep default ESP smoke coverage in test-esp-serial-startup.sh. +# This script is dedicated to ESP32-C3-specific regressions. +# +# Verifies: +# 1. _start uses newlib's __libc_init_array (not TinyGo's start.S) +# 2. __init_array_start symbol is present in ELF +# 3. __init_array_start is included in BIN load segments +# 4. float output regression case(s) remain stable on esp32c3-basic emulator + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# Create temp dir inside _demo/embed/ to use existing go.mod +TEMP_DIR="$SCRIPT_DIR/.test_tmp_$$" +mkdir -p "$TEMP_DIR" +TEST_GO="$TEMP_DIR/main.go" +TEST_ELF="test.elf" +TEST_BIN="test.bin" + +cleanup() { + rm -rf "$TEMP_DIR" +} +trap cleanup EXIT + +extract_last_nonempty_lines() { + local n="$1" + awk -v n="$n" ' + NF { out[++count] = $0 } + END { + if (n <= 0 || count == 0) { + exit + } + start = count - n + 1 + if (start < 1) { + start = 1 + } + for (i = start; i <= count; i++) { + print out[i] + } + }' +} + +run_case_and_compare() { + local case_dir="$1" + local expected="$2" + local raw_output + local actual + local expected_lines + + echo "Running: llgo run -a -target=esp32c3-basic -emulator $case_dir" + if ! raw_output=$(llgo run -a -target=esp32c3-basic -emulator "$case_dir" 2>&1); then + echo "✗ FAIL: command failed for $case_dir" + echo "$raw_output" + return 1 + fi + + expected_lines=$(printf "%s\n" "$expected" | awk 'NF { n++ } END { print n + 0 }') + actual=$(printf "%s\n" "$raw_output" | tr -d '\r' | extract_last_nonempty_lines "$expected_lines") + if [ "$actual" = "$expected" ]; then + echo "✓ PASS: $case_dir" + return 0 + fi + + echo "✗ FAIL: output mismatch for $case_dir" + echo "Expected:" + printf "%s\n" "$expected" + echo "" + echo "Got:" + printf "%s\n" "$actual" + echo "" + echo "Diff:" + diff -u <(printf "%s\n" "$expected") <(printf "%s\n" "$actual") || true + return 1 +} + +# Check if esptool.py is installed +# esptool.py is required to parse ESP32-C3 BIN file format and verify +# that constructor-related data is included in the firmware +if ! command -v esptool.py &> /dev/null; then + echo "✗ FAIL: esptool.py not found" + echo "Please install: pip3 install esptool==5.1.0" + exit 1 +fi + +echo "==> Creating minimal test program..." +cat > "$TEST_GO" << 'EOF' +package main + +import "github.com/goplus/lib/c" + +func main() { + println("Hello World\n") + c.Printf(c.Str("Hello World\n")) +} +EOF + +echo "==> Building for ESP32-C3 target (ELF + BIN)..." +cd "$TEMP_DIR" +llgo build -target=esp32c3 -o test -obin "$TEST_GO" + +if [ ! -f "$TEST_ELF" ]; then + echo "✗ FAIL: Build failed, $TEST_ELF not found" + exit 1 +fi + +if [ ! -f "$TEST_BIN" ]; then + echo "✗ FAIL: BIN file not generated, $TEST_BIN not found" + exit 1 +fi + +echo "" +echo "=== Test 1: Verify newlib startup (calls __libc_init_array) ===" + +# Disassemble _start and check for __libc_init_array call +if llvm-objdump -d "$TEST_ELF" | grep -A50 "<_start>:" | grep "__libc_init_array" > /dev/null; then + echo "✓ PASS: _start calls __libc_init_array" + echo " ESP32-C3 uses newlib's standard startup" +else + echo "✗ FAIL: _start does NOT call __libc_init_array" + echo " ESP32-C3 should use newlib's startup flow" + echo "" + echo "Expected inheritance: esp32c3 → riscv32-nostart → riscv-nostart → riscv-basic" + echo "Current _start disassembly:" + llvm-objdump -d "$TEST_ELF" | grep -A50 "<_start>:" || true + exit 1 +fi + +echo "" +echo "=== Test 2: Verify __init_array_start symbol (ELF) ===" + +# Get __init_array_start symbol address +# This symbol marks where .init_array is placed by the linker script +# +# Real output from: llvm-nm "$TEST_ELF" +# +# 40380450 d __init_array_start +# ^$1 ^$2 ^$3 +# (Addr) (Type: d=local data symbol in .rodata) +# +# Field breakdown: +# $1 = 40380450 (symbol address in hex without 0x prefix) +# $2 = d (symbol type: d=local data, D=global data, T=text, etc.) +# $3 = __init_array_start (symbol name) +# +# We extract $1 and add "0x" prefix: 40380450 → 0x40380450 +INIT_ARRAY_START=$(llvm-nm "$TEST_ELF" | grep "__init_array_start" | awk '{print "0x"$1}') +if [ -z "$INIT_ARRAY_START" ]; then + echo "✗ FAIL: __init_array_start symbol not found" + exit 1 +fi + +echo "✓ PASS: __init_array_start found at $INIT_ARRAY_START" +INIT_ADDR_DEC=$((INIT_ARRAY_START)) + +INIT_ARRAY_INFO=$(llvm-readelf -S "$TEST_ELF" | grep "\.init_array") +if [ -z "$INIT_ARRAY_INFO" ]; then + echo "✗ FAIL: .init_array section not found" + exit 1 +fi +INIT_ARRAY_SIZE=$(echo "$INIT_ARRAY_INFO" | awk '{print "0x"$7}') +INIT_ARRAY_SIZE_DEC=$((INIT_ARRAY_SIZE)) +echo ".init_array section size: $INIT_ARRAY_SIZE" + +echo "" +echo "=== Test 3: Verify __init_array_start included in BIN file ===" + +# Get BIN file segment information using esptool.py +# ESP32-C3 BIN files contain multiple segments with load addresses. +# We need to verify the __init_array_start address is covered by one segment. +# +# Real output from: esptool.py --chip esp32c3 image_info test.bin +# +# Segments Information +# ==================== +# Segment Length Load addr File offs Memory types +# ------- ------- ---------- ---------- ------------ +# 0 0x000f0 0x3fc84468 0x00000018 DRAM +# 1 0x00004 0x3fc84558 0x00000110 DRAM +# 2 0x0006c 0x3fc8455c 0x0000011c DRAM +# 3 0x0042c 0x40380000 0x00000190 IRAM +# 4 0x0003c 0x4038042c 0x000005c4 IRAM ← This is .rodata! +# ^$2 ^$3 ^$4 +# (Length) (LoadAddr) (FileOffset) +# +# Field breakdown for segment line: +# $1 = 4 (segment number) +# $2 = 0x0003c (segment length/size) +# $3 = 0x4038042c (load address - matches .rodata address from ELF!) +# $4 = 0x000005c4 (file offset in BIN) +# $5+ = IRAM (memory type) +# +# We extract $2 (length) and $3 (load addr) to verify __init_array_start is within bounds +if ! BIN_INFO=$(esptool.py --chip esp32c3 image_info "$TEST_BIN" 2>&1); then + echo "✗ FAIL: esptool.py failed to parse BIN file" + echo "$BIN_INFO" + exit 1 +fi + +FOUND_SEG=0 +if [ $INIT_ARRAY_SIZE_DEC -eq 0 ]; then + echo "✓ PASS: .init_array is empty; no constructor payload needs BIN segment coverage" +else + INIT_ARRAY_END_DEC=$((INIT_ADDR_DEC + INIT_ARRAY_SIZE_DEC)) + while read -r SEG_NUM SEG_LEN SEG_LOAD; do + SEG_LEN_DEC=$((SEG_LEN)) + SEG_LOAD_DEC=$((SEG_LOAD)) + SEG_END=$((SEG_LOAD_DEC + SEG_LEN_DEC)) + if [ $INIT_ADDR_DEC -ge $SEG_LOAD_DEC ] && [ $INIT_ARRAY_END_DEC -le $SEG_END ]; then + OFFSET=$((INIT_ADDR_DEC - SEG_LOAD_DEC)) + echo "✓ PASS: .init_array is within BIN Segment $SEG_NUM (offset: +0x$(printf '%x' $OFFSET), size: $INIT_ARRAY_SIZE)" + echo " Segment range: [$SEG_LOAD, $(printf '0x%x' $SEG_END))" + FOUND_SEG=1 + break + fi + done < <(echo "$BIN_INFO" | awk '$1 ~ /^[0-9]+$/ && $2 ~ /^0x/ && $3 ~ /^0x/ {print $1, $2, $3}') + + if [ $FOUND_SEG -eq 0 ]; then + echo "✗ FAIL: .init_array payload is NOT within any BIN segment" + echo " .init_array range: [$INIT_ARRAY_START, $(printf '0x%x' $INIT_ARRAY_END_DEC))" + echo "" + echo "BIN segments:" + echo "$BIN_INFO" | grep -A20 "Segments Information" + exit 1 + fi +fi + +echo "" +echo "=== Test 4: ESP32-C3 float output regressions (temporary) ===" +pushd "$SCRIPT_DIR" > /dev/null +FLOAT_1664_EXPECT=$'+5.000000e+000 +8.000000e+000\n1 +2.000000e+000\n0x0 +0.000000e+000 notOk: true\n0x0 +0.000000e+000 true\n3 +6.280000e+000' +if [[ "$(go env GOVERSION)" == go1.26* ]]; then + FLOAT_1664_EXPECT=$'5 8\n1 2\n0x0 0 notOk: true\n0x0 0 true\n3 6.28' +fi +run_case_and_compare "./esp32c3/float-1664" "$FLOAT_1664_EXPECT" +# Mixed Go println + C printf(%f) regression tracking for issue #1723. +run_case_and_compare "./esp32c3/print-float-1723" $'go 0\nf=1.100000' +popd > /dev/null + +echo "" +echo "=== All Tests Passed ===" +echo "✓ ESP32-C3 uses newlib startup (_start calls __libc_init_array)" +echo "✓ __init_array_start symbol exists in ELF" +echo "✓ .init_array payload is correctly handled in BIN (or empty)" +echo "✓ ESP32-C3 float output regression cases match expected output" +echo "✓ Constructor function pointers will be correctly flashed to ESP32-C3" + +exit 0 diff --git a/_demo/embed/testdata/esp32-serial/chello/expect.txt b/_demo/embed/testdata/esp32-serial/chello/expect.txt new file mode 100644 index 0000000000..557db03de9 --- /dev/null +++ b/_demo/embed/testdata/esp32-serial/chello/expect.txt @@ -0,0 +1 @@ +Hello World diff --git a/_demo/embed/testdata/esp32-serial/chello/main.go b/_demo/embed/testdata/esp32-serial/chello/main.go new file mode 100644 index 0000000000..1617b61abe --- /dev/null +++ b/_demo/embed/testdata/esp32-serial/chello/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/goplus/lib/c" + +func main() { + c.Printf(c.Str("Hello World\n")) +} diff --git a/_demo/embed/testdata/esp32-serial/gc-runtime/expect.txt b/_demo/embed/testdata/esp32-serial/gc-runtime/expect.txt new file mode 100644 index 0000000000..d86bac9de5 --- /dev/null +++ b/_demo/embed/testdata/esp32-serial/gc-runtime/expect.txt @@ -0,0 +1 @@ +OK diff --git a/_demo/embed/testdata/esp32-serial/gc-runtime/main.go b/_demo/embed/testdata/esp32-serial/gc-runtime/main.go new file mode 100644 index 0000000000..8f42a98154 --- /dev/null +++ b/_demo/embed/testdata/esp32-serial/gc-runtime/main.go @@ -0,0 +1,1409 @@ +package main + +import "unsafe" + +// scrubStack overwrites the caller's stack frame spill area with zeros. +// On RISC-V, callee register values are spilled into the caller's stack frame. +// After a build function returns, these stale heap pointers remain on the stack +// and the conservative GC treats them as live roots. This function allocates a +// large local array that overlaps the spill area, clearing those stale values. +// +//go:noinline +func scrubStack() { + var buf [256]uintptr + for i := range buf { + buf[i] = 0 + } + // Prevent the compiler from optimizing away the array. + if buf[0] != 0 { + println("unreachable") + } +} + +const debugGC = true + +type gcStats struct { + Alloc uint64 + TotalAlloc uint64 + Sys uint64 + Mallocs uint64 + Frees uint64 + HeapAlloc uint64 + HeapSys uint64 + HeapIdle uint64 + HeapInuse uint64 + StackInuse uint64 + StackSys uint64 + GCSys uint64 +} + +//go:linkname gcCollect github.com/xgo-dev/llgo/runtime/internal/runtime/tinygogc.GC +func gcCollect() uintptr + +//go:linkname readGCStats github.com/xgo-dev/llgo/runtime/internal/runtime/tinygogc.ReadGCStats +func readGCStats() gcStats + +func fail(msg string) bool { + println("FAIL:", msg) + return false +} + +func printStats(label string, freeBytes uintptr, s gcStats) { + if !debugGC { + return + } + println( + "GC", + label, + "free", freeBytes, + "alloc", s.Alloc, + "total", s.TotalAlloc, + "mallocs", s.Mallocs, + "frees", s.Frees, + "heapAlloc", s.HeapAlloc, + "heapSys", s.HeapSys, + "heapIdle", s.HeapIdle, + "heapInuse", s.HeapInuse, + "stackInuse", s.StackInuse, + "stackSys", s.StackSys, + "gcSys", s.GCSys, + ) +} + +func collectAndPrint(label string) (uintptr, gcStats) { + freeBytes := gcCollect() + stats := readGCStats() + printStats(label, freeBytes, stats) + return freeBytes, stats +} + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +type node struct { + next *node + alt *node + data [4]uintptr + id int +} + +type tinyObj struct { + v int +} + +type largeObj struct { + data [32]uintptr + id int +} + +type nested struct { + child *nested + leaf *node + id int +} + +// --------------------------------------------------------------------------- +// Global roots +// --------------------------------------------------------------------------- + +var roots []*node +var cycleRoot *node +var pressureRoots []*node +var pressureExtra []*node +var globalRoot *node +var globalSlice []*node +var globalNested *nested +var globalIface any +var globalAppendSlice []*node +var deepChainRoot *node +var orphanSlice []*node +var globalLarge *largeObj +var partialRoot *node +var cycleA *node +var cycleB *node +var mixedSmall []*tinyObj +var mixedMedium []*node +var mixedLargeSlice []*largeObj + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +func buildGlobalPointerRoot() { + globalRoot = &node{id: 550} + globalRoot.next = &node{id: 551} +} + +func checkGlobalPointerRoot() bool { + return globalRoot != nil && + globalRoot.id == 550 && + globalRoot.next != nil && + globalRoot.next.id == 551 +} + +func buildGlobalSliceRoot() { + globalSlice = make([]*node, 2) + globalSlice[0] = &node{id: 600} + globalSlice[1] = &node{id: 601} +} + +func checkGlobalSliceRoot() bool { + return len(globalSlice) == 2 && + globalSlice[0] != nil && + globalSlice[1] != nil && + globalSlice[0].id == 600 && + globalSlice[1].id == 601 +} + +func buildReachabilityGraph() { + roots = make([]*node, 2) + roots[0] = &node{id: 1} + roots[1] = &node{id: 2} + roots[0].next = &node{id: 3} + roots[0].alt = &node{id: 4} + roots[0].next.next = &node{id: 5} + roots[0].alt.next = roots[0].next.next + roots[0].next.next.next = roots[0].alt +} + +func checkReachabilityGraph() bool { + return len(roots) == 2 && + roots[0] != nil && + roots[0].next != nil && + roots[0].alt != nil && + roots[0].next.next != nil && + roots[0].next.next.id == 5 && + roots[0].alt.next != nil && + roots[0].alt.next.id == 5 +} + +func buildRootedCycle() { + cycleRoot = &node{id: 200} + cycleRoot.next = &node{id: 201} + cycleRoot.next.next = &node{id: 202} + cycleRoot.next.next.next = &node{id: 203} + cycleRoot.next.next.next.next = &node{id: 204} + cycleRoot.next.next.next.next.next = cycleRoot +} + +func buildPressureRoots(total int) { + pressureRoots = make([]*node, total) + for i := 0; i < total; i++ { + pressureRoots[i] = &node{id: 300 + i*2} + pressureRoots[i].data[0] = uintptr(i*2 + 1) + } +} + +func buildPressureExtra(total int) { + pressureExtra = make([]*node, total) + for i := 0; i < total; i++ { + pressureExtra[i] = &node{id: 500 + i} + pressureExtra[i].data[0] = uintptr(i + 1) + } +} + +func checkPressureRoots() bool { + if len(pressureRoots) != 96 { + return false + } + for i, p := range pressureRoots { + if p == nil || p.id != 300+i*2 || p.data[0] != uintptr(i*2+1) { + return false + } + } + return true +} + +func checkPressureExtra() bool { + if len(pressureExtra) != 48 { + return false + } + for i, p := range pressureExtra { + if p == nil || p.id != 500+i || p.data[0] != uintptr(i+1) { + return false + } + } + return true +} + +func sumCycle(head *node) (int, bool) { + cur := head + sum := 0 + for i := 0; i < 5; i++ { + if cur == nil { + return 0, false + } + sum += cur.id + cur = cur.next + } + return sum, cur == head +} + +func newDeferredNode(id int) *node { + return &node{id: id} +} + +// --------------------------------------------------------------------------- +// 1. testBasicAllocationAndStats +// Covers: Alloc bookkeeping and ReadGCStats accounting. +// --------------------------------------------------------------------------- + +func testBasicAllocationAndStats() bool { + _, base := collectAndPrint("basic-base") + + // Store pointers in a global so conservative GC can always find them. + // Local vars may stay in registers that aren't scanned on some architectures. + globalSlice = make([]*node, 3) + globalSlice[0] = &node{id: 10} + globalSlice[1] = &node{id: 11, next: globalSlice[0]} + globalSlice[2] = &node{id: 12, next: globalSlice[1], alt: globalSlice[0]} + + _, after := collectAndPrint("basic-after") + + if globalSlice[0] == nil || globalSlice[1] == nil || globalSlice[2] == nil { + return fail("basic allocation returned nil object") + } + if globalSlice[0] == globalSlice[1] || globalSlice[1] == globalSlice[2] || globalSlice[0] == globalSlice[2] { + return fail("distinct allocations collapsed to same address") + } + if globalSlice[2].next != globalSlice[1] || globalSlice[2].alt != globalSlice[0] { + return fail("allocated objects lost their references") + } + if after.Mallocs <= base.Mallocs { + return fail("malloc count did not increase after basic allocation") + } + if after.TotalAlloc <= base.TotalAlloc { + return fail("total allocation did not increase after basic allocation") + } + if after.Alloc <= base.Alloc { + return fail("allocated bytes did not increase after basic allocation") + } + if after.Alloc != after.HeapAlloc { + return fail("Alloc and HeapAlloc diverged") + } + if after.HeapInuse+after.HeapIdle != after.HeapSys { + return fail("HeapSys accounting mismatch") + } + if after.StackInuse > after.StackSys { + return fail("StackInuse exceeded StackSys") + } + if after.Sys == 0 || after.GCSys == 0 { + return fail("GC system stats were not initialized") + } + globalSlice = nil + return true +} + +// --------------------------------------------------------------------------- +// 2. testStatsConsistency +// Covers: ReadGCStats invariants regardless of live set size. +// --------------------------------------------------------------------------- + +func testStatsConsistency() bool { + _, stats := collectAndPrint("stats") + + if stats.Alloc != stats.HeapAlloc { + return fail("Alloc and HeapAlloc diverged in stats check") + } + if stats.HeapSys != stats.HeapInuse+stats.HeapIdle { + return fail("HeapSys was inconsistent with HeapInuse and HeapIdle") + } + if stats.Mallocs < stats.Frees { + return fail("malloc count fell below free count") + } + if stats.StackInuse > stats.StackSys { + return fail("StackInuse exceeded StackSys in stats check") + } + if stats.Sys == 0 || stats.GCSys == 0 { + return fail("stats check observed zeroed system counters") + } + return true +} + +// --------------------------------------------------------------------------- +// 3. testGlobalPointerRoot +// Covers: markRoots(globalsStart, globalsEnd) for a direct global pointer. +// --------------------------------------------------------------------------- + +func testGlobalPointerRoot() bool { + globalRoot = nil + _, base := collectAndPrint("global-ptr-base") + + buildGlobalPointerRoot() + _, after := collectAndPrint("global-ptr-after") + + if !checkGlobalPointerRoot() { + return fail("global pointer root was not preserved") + } + if after.Mallocs <= base.Mallocs { + return fail("global pointer root did not allocate objects") + } + globalRoot = nil + _, afterDrop := collectAndPrint("global-ptr-drop") + if afterDrop.Frees < after.Frees+2 { + return fail("clearing global pointer root did not free objects") + } + return true +} + +// --------------------------------------------------------------------------- +// 4. testGlobalSliceRoot +// Covers: conservative global scanning of a slice header + recursive marking +// of heap-backed slice data containing pointers. +// --------------------------------------------------------------------------- + +func testGlobalSliceRoot_build() (bool, gcStats, gcStats) { + globalSlice = nil + _, base := collectAndPrint("global-slice-base") + + buildGlobalSliceRoot() + _, after := collectAndPrint("global-slice-after") + if !checkGlobalSliceRoot() { + fail("global slice root was not preserved") + return false, base, after + } + if after.Mallocs <= base.Mallocs { + fail("global slice root did not allocate objects") + return false, base, after + } + return true, base, after +} + +func testGlobalSliceRoot_drop(after gcStats) bool { + globalSlice = nil + _, afterDrop := collectAndPrint("global-slice-drop") + if afterDrop.Frees < after.Frees+3 { + return fail("clearing global slice root did not free objects") + } + return true +} + +// --------------------------------------------------------------------------- +// 5. testDeferClosureLiveness +// Covers: stack root scanning for a deferred closure environment. +// --------------------------------------------------------------------------- + +func runDeferClosureScenario() bool { + ok := false + func() { + x := &node{id: 42} + defer func() { + ok = x != nil && x.id == 42 + }() + gcCollect() + gcCollect() + gcCollect() + }() + return ok +} + +func testDeferClosureLiveness() bool { + if !runDeferClosureScenario() { + return fail("defer closure lost heap object after GC") + } + return true +} + +// --------------------------------------------------------------------------- +// 6. testDeferArgumentLiveness +// Covers: stack root scanning for deferred call arguments. +// --------------------------------------------------------------------------- + +func runDeferArgumentScenario() bool { + ok := false + func() { + defer func(p *node) { + ok = p != nil && p.id == 43 + }(newDeferredNode(43)) + gcCollect() + gcCollect() + gcCollect() + }() + return ok +} + +func testDeferArgumentLiveness() bool { + if !runDeferArgumentScenario() { + return fail("defer argument lost heap object after GC") + } + return true +} + +// --------------------------------------------------------------------------- +// 7. testReachabilityGraph +// Covers: startMark/markRoot recursively traversing a shared heap graph. +// --------------------------------------------------------------------------- + +func testReachabilityGraph_build() (bool, gcStats) { + roots = nil + _, base := collectAndPrint("reachability-base") + + buildReachabilityGraph() + _, mid := collectAndPrint("reachability-after-build") + + if !checkReachabilityGraph() { + fail("root graph was not constructed") + return false, mid + } + if mid.Mallocs <= base.Mallocs { + fail("malloc count did not increase for reachability graph") + return false, mid + } + if mid.Alloc != mid.HeapAlloc { + fail("Alloc and HeapAlloc diverged") + return false, mid + } + if mid.HeapInuse+mid.HeapIdle != mid.HeapSys { + fail("HeapSys accounting mismatch") + return false, mid + } + return true, mid +} + +func testReachabilityGraph_drop(mid gcStats) bool { + roots = nil + _, afterDrop := collectAndPrint("reachability-drop") + if afterDrop.Frees < mid.Frees+6 { + return fail("clearing reachability roots did not free graph") + } + if afterDrop.Alloc >= mid.Alloc { + return fail("heap usage did not shrink after clearing reachability roots") + } + return true +} + +// --------------------------------------------------------------------------- +// 8. testCircularReferences +// Covers: rooted cycles surviving mark-and-sweep; full reclamation on unroot. +// --------------------------------------------------------------------------- + +func testCircularReferences() bool { + cycleRoot = nil + _, base := collectAndPrint("cycle-base") + + buildRootedCycle() + _, mid := collectAndPrint("cycle-rooted") + + sum, ok := sumCycle(cycleRoot) + if !ok { + return fail("rooted cycle no longer closed after GC") + } + if sum != 200+201+202+203+204 { + return fail("rooted cycle payload was corrupted after GC") + } + if mid.Mallocs <= base.Mallocs { + return fail("rooted cycle did not allocate objects") + } + cycleRoot = nil + _, afterDrop := collectAndPrint("cycle-drop") + if afterDrop.Frees < mid.Frees+5 { + return fail("clearing rooted cycle did not free cycle objects") + } + if afterDrop.Alloc >= mid.Alloc { + return fail("heap usage did not shrink after clearing rooted cycle") + } + return true +} + +// --------------------------------------------------------------------------- +// 9. testMemoryPressure +// Covers: repeated allocations with an existing live set. +// --------------------------------------------------------------------------- + +func testMemoryPressure_build() (bool, gcStats) { + pressureRoots = nil + pressureExtra = nil + _, base := collectAndPrint("pressure-base") + + buildPressureRoots(96) + _, kept := collectAndPrint("pressure-kept") + + if !checkPressureRoots() { + fail("pressure roots were not preserved") + return false, kept + } + if kept.Mallocs <= base.Mallocs { + fail("memory pressure allocations did not increase malloc count") + return false, kept + } + + buildPressureExtra(48) + _, afterExtra := collectAndPrint("pressure-extra") + + if !checkPressureExtra() { + fail("allocation under memory pressure failed") + return false, afterExtra + } + if afterExtra.Mallocs <= kept.Mallocs { + fail("extra allocations under pressure did not advance malloc count") + return false, afterExtra + } + if !checkPressureRoots() { + fail("pressure roots were corrupted by extra allocations") + return false, afterExtra + } + return true, afterExtra +} + +func testMemoryPressure_drop(afterExtra gcStats) bool { + pressureRoots = nil + pressureExtra = nil + _, afterDrop := collectAndPrint("pressure-drop") + if afterDrop.Frees < afterExtra.Frees+146 { + return fail("clearing pressure roots did not free live objects") + } + if afterDrop.Alloc >= afterExtra.Alloc { + return fail("heap usage did not shrink after clearing pressure roots") + } + return true +} + +// --------------------------------------------------------------------------- +// 10. testZeroSizeAllocation +// Covers: Alloc(0) returning the sentinel zeroSizedAlloc pointer. The +// sentinel must not be freed by GC and must be consistent across calls. +// --------------------------------------------------------------------------- + +func testZeroSizeAllocation() bool { + _, base := collectAndPrint("zero-base") + + type empty struct{} + a := new(empty) + b := new(empty) + + gcCollect() + gcCollect() + + _, after := collectAndPrint("zero-after") + + if a == nil || b == nil { + return fail("zero-size allocation returned nil") + } + // Both zero-size allocations should yield the same sentinel pointer. + if uintptr(unsafe.Pointer(a)) != uintptr(unsafe.Pointer(b)) { + return fail("zero-size allocations returned different pointers") + } + // Zero-size allocations should not bump the Mallocs counter. + if after.Mallocs != base.Mallocs { + return fail("zero-size allocation incorrectly changed malloc count") + } + return true +} + +// --------------------------------------------------------------------------- +// 11. testLargeObjectMultiBlock +// Covers: allocation spanning multiple blocks (largeObj has 33 uintptr-sized +// fields = >8 blocks on 64-bit). Survival when rooted, reclamation when not. +// --------------------------------------------------------------------------- + +func buildAndCheckLargeObject() (bool, uint64) { + globalLarge = &largeObj{id: 700} + for i := range globalLarge.data { + globalLarge.data[i] = uintptr(i + 1) + } + _, after := collectAndPrint("large-after") + + if globalLarge == nil || globalLarge.id != 700 { + fail("large object was not preserved after GC") + return false, 0 + } + for i := range globalLarge.data { + if globalLarge.data[i] != uintptr(i+1) { + fail("large object data was corrupted after GC") + return false, 0 + } + } + return true, after.Mallocs +} + +func testLargeObjectMultiBlock() bool { + globalLarge = nil + _, base := collectAndPrint("large-base") + + ok, afterMallocs := buildAndCheckLargeObject() + if !ok { + return false + } + if afterMallocs <= base.Mallocs { + return fail("large object allocation did not increase malloc count") + } + + _, mid := collectAndPrint("large-mid") + globalLarge = nil + _, afterDrop := collectAndPrint("large-drop") + if afterDrop.Frees < mid.Frees+1 { + return fail("clearing large object did not free it") + } + if afterDrop.Alloc >= mid.Alloc { + return fail("heap usage did not shrink after clearing large object") + } + return true +} + +// --------------------------------------------------------------------------- +// 12. testSliceElementLiveness +// Covers: heap objects reachable through a global slice survive GC. +// The slice header is in a global variable, but the backing array and +// each *node element are heap-allocated. This verifies that the GC +// traces the indirect reference chain: global → slice → heap objects. +// --------------------------------------------------------------------------- + +var sliceTestElements []*node + +func testSliceElementLiveness() bool { + sliceTestElements = make([]*node, 4) + sliceTestElements[0] = &node{id: 800} + sliceTestElements[1] = &node{id: 801} + sliceTestElements[2] = &node{id: 802} + sliceTestElements[3] = &node{id: 803} + + gcCollect() + gcCollect() + gcCollect() + + for i := 0; i < 4; i++ { + if sliceTestElements[i] == nil || sliceTestElements[i].id != 800+i { + return fail("slice element: heap pointer lost after GC") + } + } + sliceTestElements = nil + return true +} + +// --------------------------------------------------------------------------- +// 12b. testRegisterRootProbe +// Probe: pointer lives ONLY in a local variable — never stored to a global +// or the heap. On architectures with windowed registers (Xtensa) the +// conservative stack scanner may miss it. A failure here is an architecture- +// specific GC limitation, not a logic error. +// --------------------------------------------------------------------------- + +func testRegisterRootProbe() bool { + a := &node{id: 801} + + gcCollect() + + if a.id != 801 { + println("WARN: register root not scanned (expected on Xtensa windowed ABI)") + return true // known limitation, not a hard failure + } + return true +} + +// --------------------------------------------------------------------------- +// 13. testInterfaceLiveness +// Covers: heap objects referenced through an interface (any) global surviving +// GC, and being reclaimed once the interface is nilled. +// --------------------------------------------------------------------------- + +func testInterfaceLiveness() bool { + globalIface = nil + _, base := collectAndPrint("iface-base") + + n := &node{id: 900} + globalIface = n + _, after := collectAndPrint("iface-after") + + got, ok := globalIface.(*node) + if !ok || got == nil || got.id != 900 { + return fail("interface-boxed heap object lost after GC") + } + if after.Mallocs <= base.Mallocs { + return fail("interface allocation did not increase malloc count") + } + + globalIface = nil + _, afterDrop := collectAndPrint("iface-drop") + if afterDrop.Frees <= after.Frees { + return fail("clearing interface did not free heap object") + } + return true +} + +// --------------------------------------------------------------------------- +// 14. testSliceAppendGrowth +// Covers: slice append triggering a new backing array. The old backing array +// should be reclaimed after the slice header updates to the new one. +// --------------------------------------------------------------------------- + +func buildAppendSlice() { + globalAppendSlice = make([]*node, 0, 2) + globalAppendSlice = append(globalAppendSlice, &node{id: 1000}) + globalAppendSlice = append(globalAppendSlice, &node{id: 1001}) + // Force growth by appending beyond capacity. + for i := 0; i < 10; i++ { + globalAppendSlice = append(globalAppendSlice, &node{id: 1002 + i}) + } +} + +func checkAppendSlice() bool { + if len(globalAppendSlice) != 12 { + return fail("append slice has wrong length") + } + if globalAppendSlice[0].id != 1000 || globalAppendSlice[1].id != 1001 { + return fail("append slice original elements corrupted") + } + for i := 0; i < 10; i++ { + if globalAppendSlice[2+i].id != 1002+i { + return fail("append slice appended elements corrupted") + } + } + return true +} + +func testSliceAppendGrowth() bool { + globalAppendSlice = nil + _, base := collectAndPrint("append-base") + + buildAppendSlice() + _, afterBuild := collectAndPrint("append-build") + + if !checkAppendSlice() { + return false + } + if afterBuild.Mallocs <= base.Mallocs { + return fail("slice growth did not allocate") + } + + globalAppendSlice = nil + _, afterDrop := collectAndPrint("append-drop") + if afterDrop.Frees <= afterBuild.Frees { + return fail("clearing append slice did not free objects") + } + return true +} + +// --------------------------------------------------------------------------- +// 15. testDeepReferenceChain +// Covers: deep linked list that may overflow the mark stack (markStackSize=8 +// pointers), triggering markStackOverflow and finishMark rescan. +// --------------------------------------------------------------------------- + +func buildDeepChain(chainLen int) { + deepChainRoot = &node{id: 2000} + cur := deepChainRoot + for i := 1; i < chainLen; i++ { + cur.next = &node{id: 2000 + i} + cur = cur.next + } +} + +func verifyDeepChain(chainLen int) bool { + cur := deepChainRoot + for i := 0; i < chainLen; i++ { + if cur == nil { + return fail("deep chain truncated at link") + } + if cur.id != 2000+i { + return fail("deep chain node has wrong id") + } + cur = cur.next + } + if cur != nil { + return fail("deep chain has unexpected extra node") + } + return true +} + +func testDeepReferenceChain() bool { + deepChainRoot = nil + _, base := collectAndPrint("deep-base") + + const chainLen = 30 // well beyond markStackSize (8) + buildDeepChain(chainLen) + _, after := collectAndPrint("deep-after") + + if !verifyDeepChain(chainLen) { + return false + } + if after.Mallocs <= base.Mallocs { + return fail("deep chain did not allocate objects") + } + + deepChainRoot = nil + _, afterDrop := collectAndPrint("deep-drop") + expectedFrees := after.Frees + uint64(chainLen) + if afterDrop.Frees < expectedFrees { + return fail("clearing deep chain did not free all nodes") + } + if afterDrop.Alloc >= after.Alloc { + return fail("heap usage did not shrink after clearing deep chain") + } + return true +} + +// --------------------------------------------------------------------------- +// 16. testUnreachableOrphanCollection +// Covers: objects allocated but never rooted are collected as garbage. +// --------------------------------------------------------------------------- + +func buildOrphanSlice() { + orphanSlice = make([]*node, 5) + for i := range orphanSlice { + orphanSlice[i] = &node{id: 3000 + i} + } +} + +func testUnreachableOrphanCollection_build() (bool, gcStats) { + orphanSlice = nil + _, base := collectAndPrint("orphan-base") + + buildOrphanSlice() + _, afterBuild := collectAndPrint("orphan-built") + + if afterBuild.Mallocs <= base.Mallocs { + fail("orphan allocation did not increase malloc count") + return false, afterBuild + } + return true, afterBuild +} + +func testUnreachableOrphanCollection_drop(afterBuild gcStats) bool { + orphanSlice = nil + _, afterDrop := collectAndPrint("orphan-drop") + if afterDrop.Frees < afterBuild.Frees+6 { + return fail("orphaned objects were not collected") + } + if afterDrop.Alloc >= afterBuild.Alloc { + return fail("heap usage did not shrink after orphan collection") + } + return true +} + +// --------------------------------------------------------------------------- +// 17. testGCIdempotency +// Covers: running GC twice on a stable heap must not free additional objects. +// --------------------------------------------------------------------------- + +func testGCIdempotency() bool { + globalRoot = &node{id: 4000} + globalRoot.next = &node{id: 4001} + _, first := collectAndPrint("idempotent-first") + + _, second := collectAndPrint("idempotent-second") + + if second.Frees != first.Frees { + return fail("second GC on stable heap freed extra objects") + } + if second.Mallocs != first.Mallocs { + return fail("second GC on stable heap changed malloc count") + } + if second.Alloc != first.Alloc { + return fail("Alloc changed between idempotent GC cycles") + } + + globalRoot = nil + collectAndPrint("idempotent-cleanup") + return true +} + +// --------------------------------------------------------------------------- +// 18. testAllocAfterGCReclaim +// Covers: after GC frees objects, the allocator can reuse freed space. +// --------------------------------------------------------------------------- + +func testAllocAfterGCReclaim() bool { + globalSlice = make([]*node, 10) + for i := range globalSlice { + globalSlice[i] = &node{id: 5000 + i} + } + _, before := collectAndPrint("reclaim-before") + + globalSlice = nil + collectAndPrint("reclaim-freed") + + // Re-allocate the same amount. If GC reclaimed the old objects, + // the allocator can reuse that space. + globalSlice = make([]*node, 10) + for i := range globalSlice { + globalSlice[i] = &node{id: 6000 + i} + } + _, afterReuse := collectAndPrint("reclaim-reuse") + + for i := range globalSlice { + if globalSlice[i] == nil || globalSlice[i].id != 6000+i { + return fail("reclaim: reused allocation corrupted") + } + } + if afterReuse.Mallocs <= before.Mallocs { + return fail("reclaim: new allocations did not advance malloc count") + } + + globalSlice = nil + collectAndPrint("reclaim-cleanup") + return true +} + +// --------------------------------------------------------------------------- +// 19. testNestedStructPointers +// Covers: recursive marking through nested struct pointer fields (child and +// leaf), verifying the full tree survives GC and is freed when unrooted. +// --------------------------------------------------------------------------- + +func testNestedStructPointers() bool { + globalNested = nil + _, base := collectAndPrint("nested-base") + + globalNested = &nested{id: 100} + globalNested.leaf = &node{id: 101} + globalNested.child = &nested{id: 102} + globalNested.child.leaf = &node{id: 103} + globalNested.child.child = &nested{id: 104} + globalNested.child.child.leaf = &node{id: 105} + _, after := collectAndPrint("nested-after") + + if globalNested == nil || globalNested.id != 100 { + return fail("nested root lost") + } + if globalNested.leaf == nil || globalNested.leaf.id != 101 { + return fail("nested root leaf lost") + } + if globalNested.child == nil || globalNested.child.id != 102 { + return fail("nested child lost") + } + if globalNested.child.leaf == nil || globalNested.child.leaf.id != 103 { + return fail("nested child leaf lost") + } + if globalNested.child.child == nil || globalNested.child.child.id != 104 { + return fail("nested grandchild lost") + } + if globalNested.child.child.leaf == nil || globalNested.child.child.leaf.id != 105 { + return fail("nested grandchild leaf lost") + } + if after.Mallocs <= base.Mallocs { + return fail("nested struct allocation did not increase malloc count") + } + + globalNested = nil + // This collector scans stack words conservatively. On Xtensa, a dead + // register-window spill can keep the deepest nested pointer alive for one + // cycle. The first collection also overwrites that spill, so verify complete + // reclamation after a settling cycle instead of requiring precise-GC behavior. + collectAndPrint("nested-drop-first") + _, afterDrop := collectAndPrint("nested-drop") + // 3 nested + 3 node = 6 objects (at minimum) + if afterDrop.Frees < after.Frees+6 { + return fail("clearing nested struct did not free all objects") + } + if afterDrop.Alloc >= after.Alloc { + return fail("heap usage did not shrink after clearing nested struct") + } + return true +} + +// --------------------------------------------------------------------------- +// 20. testTotalAllocMonotonicity +// Covers: TotalAlloc is cumulative and must never decrease after GC. +// --------------------------------------------------------------------------- + +func testTotalAllocMonotonicity() bool { + _, s1 := collectAndPrint("mono-s1") + + globalRoot = &node{id: 7000} + globalRoot.next = &node{id: 7001} + _, s2 := collectAndPrint("mono-s2") + + if s2.TotalAlloc <= s1.TotalAlloc { + return fail("TotalAlloc did not increase after allocation") + } + + globalRoot = nil + _, s3 := collectAndPrint("mono-s3") + + if s3.TotalAlloc < s2.TotalAlloc { + return fail("TotalAlloc decreased after GC (should be monotonic)") + } + + globalRoot = &node{id: 7002} + _, s4 := collectAndPrint("mono-s4") + + if s4.TotalAlloc <= s3.TotalAlloc { + return fail("TotalAlloc did not increase after further allocation") + } + + globalRoot = nil + collectAndPrint("mono-cleanup") + return true +} + +// --------------------------------------------------------------------------- +// 21. testStatsConsistencyAfterMultipleGCCycles +// Covers: ReadGCStats invariants hold after several allocation/collection +// cycles with varying live set sizes. +// --------------------------------------------------------------------------- + +func testStatsConsistencyAfterMultipleGCCycles() bool { + globalSlice = nil + collectAndPrint("multi-gc-init") + + for round := 0; round < 3; round++ { + n := 8 + round*4 + globalSlice = make([]*node, n) + for i := 0; i < n; i++ { + globalSlice[i] = &node{id: 8000 + round*100 + i} + } + _, stats := collectAndPrint("multi-gc-round") + + if stats.Alloc != stats.HeapAlloc { + return fail("Alloc != HeapAlloc after multi-GC round") + } + if stats.HeapSys != stats.HeapInuse+stats.HeapIdle { + return fail("HeapSys inconsistent after multi-GC round") + } + if stats.Mallocs < stats.Frees { + return fail("Mallocs < Frees after multi-GC round") + } + if stats.StackInuse > stats.StackSys { + return fail("StackInuse > StackSys after multi-GC round") + } + } + + globalSlice = nil + collectAndPrint("multi-gc-cleanup") + return true +} + +// --------------------------------------------------------------------------- +// 22. testDeferInLoopLiveness +// Covers: defer inside a loop — each iteration's deferred closure captures +// a different heap object. All must survive GC executed mid-loop. +// --------------------------------------------------------------------------- + +func runDeferInLoopScenario() bool { + results := make([]int, 4) + func() { + for i := 0; i < 4; i++ { + n := &node{id: 9000 + i} + defer func(p *node, pos int) { + results[pos] = p.id + }(n, i) + + if i == 2 { + gcCollect() + gcCollect() + } + } + gcCollect() + }() + // results[i] = 9000+i for each i + for i := 0; i < 4; i++ { + if results[i] != 9000+i { + return false + } + } + return true +} + +func testDeferInLoopLiveness() bool { + if !runDeferInLoopScenario() { + return fail("defer in loop lost heap object after GC") + } + return true +} + +// --------------------------------------------------------------------------- +// 23. testPartialGraphUnlinking +// Covers: removing a reference edge from a rooted graph makes a subgraph +// unreachable; GC must collect only the unreachable part. +// --------------------------------------------------------------------------- + +func buildPartialGraph() { + // Build: root -> a -> b -> c + partialRoot = &node{id: 10000} + a := &node{id: 10001} + b := &node{id: 10002} + cNode := &node{id: 10003} + partialRoot.next = a + a.next = b + b.next = cNode +} + +func unlinkPartialGraph() { + // Unlink b from a: b and c become unreachable via global root. + partialRoot.next.next = nil +} + +func testPartialGraphUnlinking() bool { + partialRoot = nil + _, base := collectAndPrint("partial-base") + + buildPartialGraph() + _, afterBuild := collectAndPrint("partial-build") + + if afterBuild.Mallocs <= base.Mallocs { + return fail("partial graph did not allocate") + } + + unlinkPartialGraph() + _, afterUnlink := collectAndPrint("partial-unlink") + + if partialRoot == nil || partialRoot.id != 10000 { + return fail("partial graph root lost after unlink") + } + if partialRoot.next == nil || partialRoot.next.id != 10001 { + return fail("partial graph node a lost after unlink") + } + // b and c were freed. + if afterUnlink.Frees < afterBuild.Frees+2 { + return fail("partial graph unlink did not free unreachable subgraph") + } + + partialRoot = nil + collectAndPrint("partial-cleanup") + return true +} + +// --------------------------------------------------------------------------- +// 24. testMultipleCyclesDisjoint +// Covers: two disjoint cycles rooted separately; clearing one root frees +// only that cycle while the other remains live. +// --------------------------------------------------------------------------- + +func testMultipleCyclesDisjoint() bool { + cycleA = nil + cycleB = nil + collectAndPrint("disjoint-base") + + // Cycle A: 3 nodes + cycleA = &node{id: 11000} + cycleA.next = &node{id: 11001} + cycleA.next.next = &node{id: 11002} + cycleA.next.next.next = cycleA + + // Cycle B: 2 nodes + cycleB = &node{id: 12000} + cycleB.next = &node{id: 12001} + cycleB.next.next = cycleB + + _, afterBuild := collectAndPrint("disjoint-build") + + // Drop cycle A only. + cycleA = nil + _, afterDropA := collectAndPrint("disjoint-dropA") + if afterDropA.Frees < afterBuild.Frees+3 { + return fail("dropping cycle A did not free 3 nodes") + } + + // Cycle B still alive. + if cycleB == nil || cycleB.id != 12000 || cycleB.next == nil || cycleB.next.id != 12001 { + return fail("cycle B was corrupted after dropping cycle A") + } + if cycleB.next.next != cycleB { + return fail("cycle B lost its cycle link") + } + + // Drop cycle B. + cycleB = nil + _, afterDropB := collectAndPrint("disjoint-dropB") + if afterDropB.Frees < afterDropA.Frees+2 { + return fail("dropping cycle B did not free 2 nodes") + } + return true +} + +// --------------------------------------------------------------------------- +// 25. testMixedObjectSizes +// Covers: coexistence of small (tinyObj), medium (node), and large (largeObj) +// allocations; GC must correctly track and sweep all sizes. +// --------------------------------------------------------------------------- + +func buildMixedSizes() { + mixedSmall = make([]*tinyObj, 5) + for i := range mixedSmall { + mixedSmall[i] = &tinyObj{v: 13000 + i} + } + mixedMedium = make([]*node, 5) + for i := range mixedMedium { + mixedMedium[i] = &node{id: 14000 + i} + } + mixedLargeSlice = make([]*largeObj, 3) + for i := range mixedLargeSlice { + mixedLargeSlice[i] = &largeObj{id: 15000 + i} + } +} + +func checkMixedSizes() bool { + for i, s := range mixedSmall { + if s == nil || s.v != 13000+i { + return false + } + } + for i, m := range mixedMedium { + if m == nil || m.id != 14000+i { + return false + } + } + for i, l := range mixedLargeSlice { + if l == nil || l.id != 15000+i { + return false + } + } + return true +} + +func testMixedObjectSizes() bool { + mixedSmall = nil + mixedMedium = nil + mixedLargeSlice = nil + _, base := collectAndPrint("mixed-base") + + buildMixedSizes() + _, afterBuild := collectAndPrint("mixed-build") + + if !checkMixedSizes() { + return fail("mixed objects corrupted") + } + if afterBuild.Mallocs <= base.Mallocs { + return fail("mixed sizes did not allocate") + } + + mixedSmall = nil + mixedMedium = nil + mixedLargeSlice = nil + _, afterDrop := collectAndPrint("mixed-drop") + if afterDrop.Frees <= afterBuild.Frees { + return fail("mixed sizes were not collected") + } + if afterDrop.Alloc >= afterBuild.Alloc { + return fail("heap did not shrink after clearing mixed sizes") + } + return true +} + +// --------------------------------------------------------------------------- +// main — run all tests in order +// --------------------------------------------------------------------------- + +func runTest(name string, fn func() bool) bool { + if !fn() { + return false + } + if debugGC { + println("PASS:", name) + } + return true +} + +func main() { + ok := true + + if !runTest("BasicAllocationAndStats", testBasicAllocationAndStats) { + ok = false + } + if !runTest("StatsConsistency", testStatsConsistency) { + ok = false + } + if !runTest("GlobalPointerRoot", testGlobalPointerRoot) { + ok = false + } + + // Split tests: scrubStack() between build/drop phases clears stale + // RISC-V register spills from the caller's stack frame so the + // conservative GC does not treat them as live roots. + { + buildOk, _, afterStats := testGlobalSliceRoot_build() + scrubStack() + if buildOk && testGlobalSliceRoot_drop(afterStats) { + if debugGC { + println("PASS: GlobalSliceRoot") + } + } else { + ok = false + } + } + if !runTest("DeferClosureLiveness", testDeferClosureLiveness) { + ok = false + } + if !runTest("DeferArgumentLiveness", testDeferArgumentLiveness) { + ok = false + } + { + buildOk, midStats := testReachabilityGraph_build() + scrubStack() + if buildOk && testReachabilityGraph_drop(midStats) { + if debugGC { + println("PASS: ReachabilityGraph") + } + } else { + ok = false + } + } + if !runTest("CircularReferences", testCircularReferences) { + ok = false + } + { + buildOk, extraStats := testMemoryPressure_build() + scrubStack() + if buildOk && testMemoryPressure_drop(extraStats) { + if debugGC { + println("PASS: MemoryPressure") + } + } else { + ok = false + } + } + if !runTest("ZeroSizeAllocation", testZeroSizeAllocation) { + ok = false + } + if !runTest("LargeObjectMultiBlock", testLargeObjectMultiBlock) { + ok = false + } + if !runTest("SliceElementLiveness", testSliceElementLiveness) { + ok = false + } + if !runTest("RegisterRootProbe", testRegisterRootProbe) { + ok = false + } + if !runTest("InterfaceLiveness", testInterfaceLiveness) { + ok = false + } + if !runTest("SliceAppendGrowth", testSliceAppendGrowth) { + ok = false + } + if !runTest("DeepReferenceChain", testDeepReferenceChain) { + ok = false + } + { + buildOk, buildStats := testUnreachableOrphanCollection_build() + scrubStack() + if buildOk && testUnreachableOrphanCollection_drop(buildStats) { + if debugGC { + println("PASS: UnreachableOrphanCollection") + } + } else { + ok = false + } + } + if !runTest("GCIdempotency", testGCIdempotency) { + ok = false + } + if !runTest("AllocAfterGCReclaim", testAllocAfterGCReclaim) { + ok = false + } + if !runTest("NestedStructPointers", testNestedStructPointers) { + ok = false + } + if !runTest("TotalAllocMonotonicity", testTotalAllocMonotonicity) { + ok = false + } + if !runTest("StatsConsistencyAfterMultipleGCCycles", testStatsConsistencyAfterMultipleGCCycles) { + ok = false + } + if !runTest("DeferInLoopLiveness", testDeferInLoopLiveness) { + ok = false + } + if !runTest("PartialGraphUnlinking", testPartialGraphUnlinking) { + ok = false + } + if !runTest("MultipleCyclesDisjoint", testMultipleCyclesDisjoint) { + ok = false + } + if !runTest("MixedObjectSizes", testMixedObjectSizes) { + ok = false + } + + if ok { + println("OK") + } +} diff --git a/_demo/embed/testdata/esp32-serial/int64slice/expect.txt b/_demo/embed/testdata/esp32-serial/int64slice/expect.txt new file mode 100644 index 0000000000..ab8f87ee7b --- /dev/null +++ b/_demo/embed/testdata/esp32-serial/int64slice/expect.txt @@ -0,0 +1 @@ +slice64 ok diff --git a/_demo/embed/testdata/esp32-serial/int64slice/main.go b/_demo/embed/testdata/esp32-serial/int64slice/main.go new file mode 100644 index 0000000000..d0de5dd62f --- /dev/null +++ b/_demo/embed/testdata/esp32-serial/int64slice/main.go @@ -0,0 +1,14 @@ +package main + +import "github.com/goplus/lib/c" + +func main() { + s := "hello" + var idx int64 = 1 + tail := s[idx:] + if len(tail) == 4 && tail[0] == 'e' && tail[1] == 'l' && tail[2] == 'l' && tail[3] == 'o' { + c.Printf(c.Str("slice64 ok\n")) + } else { + c.Printf(c.Str("slice64 bad\n")) + } +} diff --git a/_demo/failed/stacktrace/main.go b/_demo/failed/stacktrace/main.go deleted file mode 100644 index 8ef1842465..0000000000 --- a/_demo/failed/stacktrace/main.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "fmt" -) - -type MyStruct[T any] struct { - value T -} - -func (m *MyStruct[T]) Method() { - fmt.Println("In generic method") - genericFunc[T](m.value) -} - -func genericFunc[T any](v T) { - fmt.Println("In generic function") - normalFunc() -} - -func normalFunc() { - fmt.Println("In normal function") - panic("panic occurs here") -} - -func main() { - m := &MyStruct[string]{value: "hello"} - m.Method() -} - -//Expected: -// In generic method -// In generic function -// In normal function -// panic: panic occurs here - -// [0x00C6D310 github.com/goplus/llgo/internal/runtime.Rethrow+0x2f, SP = 0x60] -// [0x00C6CF44 github.com/goplus/llgo/internal/runtime.Panic+0x2d, SP = 0x50] -// [0x00C69420 main.normalFunc+0xf, SP = 0xa8] -// [0x00C69564 main.genericFunc[string]+0x18, SP = 0x74] -// [0x00C694A8 main.(*MyStruct[string]).Method+0x1f, SP = 0x84] -// [0x00C6936C main+0x4, SP = 0x40] diff --git a/_demo/go.mod b/_demo/go.mod deleted file mode 100644 index b5dd0630f3..0000000000 --- a/_demo/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/goplus/llgo/_demo - -go 1.20 - -require github.com/goplus/lib v0.2.0 diff --git a/_demo/go/abimethod/main.go b/_demo/go/abimethod/main.go new file mode 100644 index 0000000000..18d373947c --- /dev/null +++ b/_demo/go/abimethod/main.go @@ -0,0 +1,219 @@ +package main + +import ( + "bytes" + "fmt" + "sync/atomic" + "unsafe" +) + +func main() { + testGeneric() + testNamed1() + testNamed2() + testNamed3() + testNamed4() + testAnonymous1() + testAnonymous2() + testAnonymous3() + testAnonymous4() + testAnonymous5() + testAnonymous6() + testAnonymous7() + testAnonymous8() + testAnonymousBuffer() +} + +func testNamed1() { + var a I = &T{100} + if a.Demo1() != 100 { + panic("testNamed1 error") + } +} + +func testNamed2() { + var a I = T{100} + if a.Demo1() != 100 { + panic("testNamed2 error") + } +} + +func testNamed3() { + var a I2 = &T{100} + if a.Demo2() != 100 { + panic("testNamed3 error") + } +} + +func testNamed4() { + type M struct { + T + } + v := &M{T{100}} + v.Demo1() + v.Demo2() + var a I2 = v + if a.Demo2() != 100 { + panic("testNamed4 error") + } +} + +type Pointer[T any] struct { + // Mention *T in a field to disallow conversion between Pointer types. + // See go.dev/issue/56603 for more details. + // Use *T, not T, to avoid spurious recursive type definition errors. + _ [0]*T + v unsafe.Pointer +} + +// Load atomically loads and returns the value stored in x. +func (x *Pointer[T]) Load() *T { return (*T)(atomic.LoadPointer(&x.v)) } + +// Store atomically stores val into x. +func (x *Pointer[T]) Store(val *T) { atomic.StorePointer(&x.v, unsafe.Pointer(val)) } + +type IP interface { + Store(*any) + Load() *any +} + +func testGeneric() { + var p IP = &Pointer[any]{} + p.Store(func() *any { + var a any = 100 + return &a + }()) + if (*p.Load()).(int) != 100 { + panic("testGeneric error") + } +} + +func testAnonymous1() { + var s I = &struct { + m int + *T + }{10, &T{100}} + if s.Demo1() != 100 { + panic("testAnonymous1 error") + } +} + +func testAnonymous2() { + var s I = struct { + m int + *T + }{10, &T{100}} + if s.Demo1() != 100 { + panic("testAnonymous2 error") + } +} + +func testAnonymous3() { + var s I = struct { + m int + T + }{10, T{100}} + if s.Demo1() != 100 { + panic("testAnonymous3 error") + } +} + +func testAnonymous4() { + var s I = &struct { + m int + T + }{10, T{100}} + if s.Demo1() != 100 { + panic("testAnonymous4 error") + } +} + +func testAnonymous5() { + var s I2 = &struct { + m int + T + }{10, T{100}} + if s.Demo2() != 100 { + panic("testAnonymous5 error") + } +} + +func testAnonymous6() { + var s I2 = struct { + m int + *T + }{10, &T{100}} + if s.Demo2() != 100 { + panic("testAnonymous6 error") + } +} + +func testAnonymous7() { + var s interface { + Demo1() int + Demo2() int + } = struct { + m int + *T + }{10, &T{100}} + if s.Demo1() != 100 { + panic("testAnonymous7 error") + } + if s.Demo2() != 100 { + panic("testAnonymous7 error") + } +} + +func testAnonymous8() { + var s interface { + Demo1() int + Demo2() int + demo3() int + } = struct { + m int + *T + }{10, &T{100}} + if s.Demo1() != 100 { + panic("testAnonymous8 error") + } + if s.Demo2() != 100 { + panic("testAnonymous8 error") + } + if s.demo3() != 100 { + panic("testAnonymous8 error") + } +} + +func testAnonymousBuffer() { + var s fmt.Stringer = &struct { + m int + *bytes.Buffer + }{10, bytes.NewBufferString("hello")} + if s.String() != "hello" { + panic("testAnonymousBuffer error") + } +} + +type T struct { + n int +} + +func (t T) Demo1() int { + return t.n +} + +func (t *T) Demo2() int { + return t.n +} + +func (t *T) demo3() int { + return t.n +} + +type I interface { + Demo1() int +} + +type I2 interface { + Demo2() int +} diff --git a/_demo/go/aliasrecv/main.go b/_demo/go/aliasrecv/main.go new file mode 100644 index 0000000000..5bd23aa1e9 --- /dev/null +++ b/_demo/go/aliasrecv/main.go @@ -0,0 +1,20 @@ +package main + +func main() { + var v any = &threadImpl{100} + println(v.(interface{ String() string }).String()) +} + +type threadImpl struct { + id int64 +} + +type Thread = *threadImpl + +func (t *threadImpl) ID() int64 { + return t.id +} + +func (t Thread) String() string { + return "thread" +} diff --git a/_demo/async/async/async.go b/_demo/go/async/async/async.go similarity index 100% rename from _demo/async/async/async.go rename to _demo/go/async/async/async.go diff --git a/_demo/go/async/main.go b/_demo/go/async/main.go new file mode 100644 index 0000000000..98c107d0b7 --- /dev/null +++ b/_demo/go/async/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "time" + + "github.com/xgo-dev/llgo/_demo/go/async/async" + "github.com/xgo-dev/llgo/_demo/go/async/timeout" +) + +func Sleep(i int, d time.Duration) async.Future[int] { + return async.Async(func(resolve func(int)) { + timeout.Timeout(d).Then(func(async.Void) { + resolve(i) + }) + }) +} + +func main() { + async.Run(async.Async(func(resolve func(async.Void)) { + println("read file") + defer resolve(async.Void{}) + })) +} diff --git a/_demo/async/timeout/timeout.go b/_demo/go/async/timeout/timeout.go similarity index 81% rename from _demo/async/timeout/timeout.go rename to _demo/go/async/timeout/timeout.go index 20d632e7b7..eeca39811c 100644 --- a/_demo/async/timeout/timeout.go +++ b/_demo/go/async/timeout/timeout.go @@ -3,7 +3,7 @@ package timeout import ( "time" - "github.com/goplus/llgo/_demo/async/async" + "github.com/xgo-dev/llgo/_demo/go/async/async" ) func Timeout(d time.Duration) async.Future[async.Void] { diff --git a/_demo/go/atomicfn/main.go b/_demo/go/atomicfn/main.go new file mode 100644 index 0000000000..1b9dd051d3 --- /dev/null +++ b/_demo/go/atomicfn/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "sync/atomic" +) + +func main() { + demo(atomic.AddInt32) +} + +func demo(fn func(addr *int32, delta int32) (new int32)) { + var a int32 + fn(&a, 1) + println(a) +} diff --git a/_demo/go/cabi/main.go b/_demo/go/cabi/main.go new file mode 100644 index 0000000000..63b6db628f --- /dev/null +++ b/_demo/go/cabi/main.go @@ -0,0 +1,22 @@ +package main + +type R struct { + data any +} + +func (r *R) read() any { + data := r.data + r.data = nil + return data +} + +func main() { + r := R{data: 1} + data := r.read() + if data != 1 { + panic("abi data error") + } + if r.data != nil { + panic("abi r.data error") + } +} diff --git a/_demo/go/cgo/main.go b/_demo/go/cgo/main.go new file mode 100644 index 0000000000..9241cc28e5 --- /dev/null +++ b/_demo/go/cgo/main.go @@ -0,0 +1,60 @@ +package main + +/* +#include + +typedef struct { int a; } s4; +typedef struct { int a; int b; } s8; +typedef struct { int a; int b; int c; } s12; +typedef struct { int a; int b; int c; int d; } s16; +typedef struct { int a; int b; int c; int d; int e; } s20; + +static int c_add(int a, int b) { + return a + b; +} + +static int sum_structs(s4* a, s8* b, s12* c, s16* d, s20* e) { + return a->a + b->a + b->b + c->a + c->b + c->c + + d->a + d->b + d->c + d->d + + e->a + e->b + e->c + e->d + e->e; +} + +static int c_errno_wrap(int x) { + if (x < 0) { + errno = ERANGE; + return -1; + } + errno = 0; + return x + 1; +} +*/ +import "C" + +import "fmt" + +func main() { + fmt.Println("c_add:", int(C.c_add(20, 22))) + + a := C.s4{a: 1} + b := C.s8{a: 1, b: 2} + c := C.s12{a: 1, b: 2, c: 3} + d := C.s16{a: 1, b: 2, c: 3, d: 4} + e := C.s20{a: 1, b: 2, c: 3, d: 4, e: 5} + sum, err := C.sum_structs(&a, &b, &c, &d, &e) + if err != nil { + panic(err) + } + fmt.Println("sum_structs:", int(sum)) + + _, err = C.c_errno_wrap(-1) + if err == nil { + panic("expected errno for c_errno_wrap(-1)") + } + fmt.Println("errno_path:", err) + + v, err := C.c_errno_wrap(9) + if err != nil { + panic(err) + } + fmt.Println("ok_path:", int(v)) +} diff --git a/_demo/checkfile/demo.go b/_demo/go/checkfile/demo.go similarity index 100% rename from _demo/checkfile/demo.go rename to _demo/go/checkfile/demo.go diff --git a/_demo/commandrun/commandrun.go b/_demo/go/commandrun/commandrun.go similarity index 100% rename from _demo/commandrun/commandrun.go rename to _demo/go/commandrun/commandrun.go diff --git a/_demo/complex/cmplx.go b/_demo/go/complex/cmplx.go similarity index 100% rename from _demo/complex/cmplx.go rename to _demo/go/complex/cmplx.go diff --git a/_demo/go/createtemp-1654/main.go b/_demo/go/createtemp-1654/main.go new file mode 100644 index 0000000000..419d6c13ee --- /dev/null +++ b/_demo/go/createtemp-1654/main.go @@ -0,0 +1,72 @@ +package main + +import ( + "fmt" + "os" + "sync" +) + +// Regression stress for darwin/amd64 create-temp failure path. +// If open failure does not return EEXIST correctly, os.CreateTemp may return +// a file with an invalid fd and later operations can fail with EBADF. +const ( + goroutines = 4 + iterations = 5000 +) + +func worker(dir string, errs chan<- error) { + for i := 0; i < iterations; i++ { + f, err := os.CreateTemp(dir, "tmpfile-*.tmp") + if err != nil { + errs <- fmt.Errorf("CreateTemp: %w", err) + return + } + + name := f.Name() + if _, err := f.WriteString("x"); err != nil { + _ = f.Close() + errs <- fmt.Errorf("WriteString %s: %w", name, err) + return + } + if _, err := f.Stat(); err != nil { + _ = f.Close() + errs <- fmt.Errorf("Stat %s: %w", name, err) + return + } + if err := f.Close(); err != nil { + errs <- fmt.Errorf("Close %s: %w", name, err) + return + } + if err := os.Remove(name); err != nil { + errs <- fmt.Errorf("Remove %s: %w", name, err) + return + } + } +} + +func main() { + dir, err := os.MkdirTemp("", "llgo-1654-*") + if err != nil { + panic(fmt.Sprintf("mktemp dir failed: %v", err)) + } + defer os.RemoveAll(dir) + + errs := make(chan error, goroutines) + var wg sync.WaitGroup + for i := 0; i < goroutines; i++ { + wg.Add(1) + go func() { + defer wg.Done() + worker(dir, errs) + }() + } + wg.Wait() + close(errs) + + for err := range errs { + if err != nil { + panic(err) + } + } + fmt.Println("ok") +} diff --git a/_demo/defer/main.go b/_demo/go/defer/main.go similarity index 100% rename from _demo/defer/main.go rename to _demo/go/defer/main.go diff --git a/_demo/go/embedunexport-1598/main.go b/_demo/go/embedunexport-1598/main.go new file mode 100644 index 0000000000..09d3c3cde1 --- /dev/null +++ b/_demo/go/embedunexport-1598/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "go/token" + "go/types" +) + +// wrappedFunc embeds *types.Func to implement types.Object +type wrappedFunc struct { + *types.Func +} + +func main() { + pkg := types.NewPackage("test", "test") + scope := pkg.Scope() + + sig := types.NewSignatureType(nil, nil, nil, nil, nil, false) + fn := types.NewFunc(token.NoPos, pkg, "testFunc", sig) + + wrapped := &wrappedFunc{Func: fn} + var obj types.Object = wrapped + + scope.Insert(obj) +} diff --git a/_demo/go/export/.gitignore b/_demo/go/export/.gitignore new file mode 100644 index 0000000000..a2750a6ca5 --- /dev/null +++ b/_demo/go/export/.gitignore @@ -0,0 +1 @@ +libexport.h diff --git a/_demo/go/export/c/c.go b/_demo/go/export/c/c.go new file mode 100644 index 0000000000..5063a7691a --- /dev/null +++ b/_demo/go/export/c/c.go @@ -0,0 +1,29 @@ +package C + +// XType - struct for export.go to use +type XType struct { + ID int32 `json:"id"` + Name string `json:"name"` + Value float64 `json:"value"` + Flag bool `json:"flag"` +} + +func XAdd(a, b int) int { + return a + b +} + +func Sub(a, b int64) int64 { + return a - b +} + +func sub(a, b uint32) uint32 { + return a - b +} + +func Xmul(a, b float32) float32 { + return a * b +} + +func Concat(a, b string) string { + return a + b +} diff --git a/_demo/go/export/export.go b/_demo/go/export/export.go new file mode 100644 index 0000000000..19f0af7f52 --- /dev/null +++ b/_demo/go/export/export.go @@ -0,0 +1,770 @@ +package main + +import ( + "fmt" + "runtime" + "unsafe" + + C "github.com/xgo-dev/llgo/_demo/go/export/c" +) + +// assert helper function for testing +func assert[T comparable](got, expected T, message string) { + if got != expected { + println("ASSERTION FAILED:", message) + println(" Expected:", expected) + println(" Got: ", got) + panic("assertion failed: " + message) + } + println("✓", message) +} + +// Small struct +type SmallStruct struct { + ID int8 `json:"id"` + Flag bool `json:"flag"` +} + +// Large struct +type LargeStruct struct { + ID int64 `json:"id"` + Name string `json:"name"` + Values [10]float64 `json:"values"` + Metadata map[string]int `json:"metadata"` + Children []SmallStruct `json:"children"` + Extra1 int32 `json:"extra1"` + Extra2 uint64 `json:"extra2"` + Extra3 float32 `json:"extra3"` + Extra4 bool `json:"extra4"` + Extra5 uintptr `json:"extra5"` +} + +// Self-referential struct +type Node struct { + Data int `json:"data"` + Next *Node `json:"next"` +} + +// Named types +type MyInt int +type MyString string + +// FuncInfoResult exposes the information resolved from a Callers PC so the C +// consumer can verify that funcinfo remains usable after loading a c-shared +// library (and from the equivalent c-archive output). +type FuncInfoResult struct { + CallersCount int32 + FramePC uintptr + FrameFunction string + FrameFile string + FrameLine int32 + FuncName string + FuncEntry uintptr + FuncFile string + FuncLine int32 +} + +// Function types for callbacks +// +//llgo:type C +type IntCallback func(int) int + +//llgo:type C +type StringCallback func(string) string + +//llgo:type C +type VoidCallback func() + +// Complex struct with mixed arrays and slices +type ComplexData struct { + Matrix [3][4]int32 `json:"matrix"` // 2D array + Slices [][]string `json:"slices"` // slice of slices - commented out + IntArray [5]int `json:"int_array"` // 1D array + DataList []float64 `json:"data_list"` // slice - commented out +} + +//export HelloWorld +func HelloWorld() { + println("Hello, World!") +} + +//go:noinline +func captureCFuncInfo() FuncInfoResult { + var pcs [16]uintptr + n := runtime.Callers(0, pcs[:]) + result := FuncInfoResult{CallersCount: int32(n)} + frames := runtime.CallersFrames(pcs[:n]) + for { + frame, more := frames.Next() + if frame.Function == "main.captureCFuncInfo" { + result.FramePC = frame.PC + result.FrameFunction = frame.Function + result.FrameFile = frame.File + result.FrameLine = int32(frame.Line) + + // Callers PCs are return addresses. Apply Go's pc-1 convention + // before asking FuncForPC for the containing function. + if frame.PC > 0 { + if fn := runtime.FuncForPC(frame.PC - 1); fn != nil { + result.FuncName = fn.Name() + result.FuncEntry = fn.Entry() + result.FuncFile, frame.Line = fn.FileLine(frame.PC - 1) + result.FuncLine = int32(frame.Line) + } + } + break + } + if !more { + break + } + } + return result +} + +//export GetFuncInfo +func GetFuncInfo() FuncInfoResult { + return captureCFuncInfo() +} + +//export RunGoroutine +func RunGoroutine(value int) int { + ch := make(chan int, 1) + go func() { + ch <- value + 1 + }() + return <-ch +} + +//export FormatValue +func FormatValue(value string, number int) string { + return fmt.Sprintf("%s:%d", value, number) +} + +// Functions with small struct parameters and return values + +//export CreateSmallStruct +func CreateSmallStruct(id int8, flag bool) SmallStruct { + return SmallStruct{ID: id, Flag: flag} +} + +//export ProcessSmallStruct +func ProcessSmallStruct(s SmallStruct) SmallStruct { + s.ID += 1 + s.Flag = !s.Flag + return s +} + +//export ProcessSmallStructPtr +func ProcessSmallStructPtr(s *SmallStruct) *SmallStruct { + if s != nil { + s.ID *= 2 + s.Flag = !s.Flag + } + return s +} + +// Functions with large struct parameters and return values + +//export CreateLargeStruct +func CreateLargeStruct(id int64, name string) LargeStruct { + return LargeStruct{ + ID: id, + Name: name, + Values: [10]float64{1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.0}, + Metadata: map[string]int{"count": 42, "size": 100}, + Children: []SmallStruct{{ID: 1, Flag: true}, {ID: 2, Flag: false}}, + Extra1: 12345, + Extra2: 67890, + Extra3: 3.14, + Extra4: true, + Extra5: 0x1000, + } +} + +//export ProcessLargeStruct +func ProcessLargeStruct(ls LargeStruct) int64 { + total := ls.ID + int64(len(ls.Name)) + for _, v := range ls.Values { + total += int64(v) + } + total += int64(len(ls.Children)) + total += int64(ls.Extra1) + int64(ls.Extra2) + int64(ls.Extra3) + if ls.Extra4 { + total += 1000 + } + total += int64(ls.Extra5) + return total +} + +//export ProcessLargeStructPtr +func ProcessLargeStructPtr(ls *LargeStruct) *LargeStruct { + if ls != nil { + ls.ID += 100 + ls.Name = "processed_" + ls.Name + ls.Extra1 *= 2 + ls.Extra4 = !ls.Extra4 + } + return ls +} + +// Functions with self-referential struct + +//export CreateNode +func CreateNode(data int) *Node { + return &Node{Data: data, Next: nil} +} + +//export LinkNodes +func LinkNodes(first, second *Node) int { + if first != nil && second != nil { + first.Next = second + return first.Data + second.Data // Return sum for verification + } + if first != nil { + return first.Data + 1000 // Return data + offset if only first exists + } + return 2000 // Return fixed value if both are nil +} + +//export TraverseNodes +func TraverseNodes(head *Node) int { + count := 0 + current := head + for current != nil { + count++ + current = current.Next + if count > 100 { // Safety check + break + } + } + return count +} + +// Functions covering all basic types + +//export ProcessBool +func ProcessBool(b bool) bool { + return !b +} + +//export ProcessInt8 +func ProcessInt8(x int8) int8 { + return x + 1 +} + +//export ProcessUint8 +func ProcessUint8(x uint8) uint8 { + return x + 1 +} + +//export ProcessInt16 +func ProcessInt16(x int16) int16 { + return x * 2 +} + +//export ProcessUint16 +func ProcessUint16(x uint16) uint16 { + return x * 2 +} + +//export ProcessInt32 +func ProcessInt32(x int32) int32 { + return x * 3 +} + +//export ProcessUint32 +func ProcessUint32(x uint32) uint32 { + return x * 3 +} + +//export ProcessInt64 +func ProcessInt64(x int64) int64 { + return x * 4 +} + +//export ProcessUint64 +func ProcessUint64(x uint64) uint64 { + return x * 4 +} + +//export ProcessInt +func ProcessInt(x int) int { + return x * 11 +} + +//export ProcessUint +func ProcessUint(x uint) uint { + return x * 21 +} + +//export ProcessUintptr +func ProcessUintptr(x uintptr) uintptr { + return x + 300 +} + +//export ProcessFloat32 +func ProcessFloat32(x float32) float32 { + return x * 1.5 +} + +//export ProcessFloat64 +func ProcessFloat64(x float64) float64 { + return x * 2.5 +} + +//export ProcessString +func ProcessString(s string) string { + return "processed_" + s +} + +//export ProcessUnsafePointer +func ProcessUnsafePointer(p unsafe.Pointer) unsafe.Pointer { + return p +} + +// Functions with named types + +//export ProcessMyInt +func ProcessMyInt(x MyInt) MyInt { + return x * 10 +} + +//export ProcessMyString +func ProcessMyString(s MyString) MyString { + return MyString("modified_" + string(s)) +} + +// Functions with arrays, slices, maps, channels + +//export ProcessIntArray +func ProcessIntArray(arr [5]int) int { + total := 0 + for _, v := range arr { + total += v + } + return total +} + +//export CreateComplexData +func CreateComplexData() ComplexData { + return ComplexData{ + Matrix: [3][4]int32{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}, + Slices: [][]string{{"helo"}}, + IntArray: [5]int{10, 20, 30, 40, 50}, + DataList: []float64{1.0}, + } +} + +//export ProcessComplexData +func ProcessComplexData(data ComplexData) int32 { + // Sum all matrix elements + var sum int32 + for i := 0; i < 3; i++ { + for j := 0; j < 4; j++ { + sum += data.Matrix[i][j] + } + } + return sum +} + +// Functions with multidimensional arrays as parameters and return values + +//export ProcessMatrix2D +func ProcessMatrix2D(matrix [3][4]int32) int32 { + var sum int32 + for i := 0; i < 3; i++ { + for j := 0; j < 4; j++ { + sum += matrix[i][j] + } + } + return sum +} + +//export CreateMatrix1D +func CreateMatrix1D() [4]int32 { + return [4]int32{1, 2, 3, 4} +} + +//export CreateMatrix2D +func CreateMatrix2D() [3][4]int32 { + return [3][4]int32{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}} +} + +//export ProcessMatrix3D +func ProcessMatrix3D(cube [2][3][4]uint8) uint32 { + var sum uint32 + for i := 0; i < 2; i++ { + for j := 0; j < 3; j++ { + for k := 0; k < 4; k++ { + sum += uint32(cube[i][j][k]) + } + } + } + return sum +} + +//export CreateMatrix3D +func CreateMatrix3D() [2][3][4]uint8 { + var cube [2][3][4]uint8 + val := uint8(1) + for i := 0; i < 2; i++ { + for j := 0; j < 3; j++ { + for k := 0; k < 4; k++ { + cube[i][j][k] = val + val++ + } + } + } + return cube +} + +//export ProcessGrid5x4 +func ProcessGrid5x4(grid [5][4]float64) float64 { + var sum float64 + for i := 0; i < 5; i++ { + for j := 0; j < 4; j++ { + sum += grid[i][j] + } + } + return sum +} + +//export CreateGrid5x4 +func CreateGrid5x4() [5][4]float64 { + var grid [5][4]float64 + val := 1.0 + for i := 0; i < 5; i++ { + for j := 0; j < 4; j++ { + grid[i][j] = val + val += 0.5 + } + } + return grid +} + +//export ProcessIntSlice +func ProcessIntSlice(slice []int) int { + total := 0 + for _, v := range slice { + total += v + } + return total +} + +//export CreateIntSlice +func CreateIntSlice() []int { + return []int{2, 4, 6, 8} +} + +//export ProcessStringMap +func ProcessStringMap(m map[string]int) int { + total := 0 + for _, v := range m { + total += v + } + return total +} + +//export CreateStringMap +func CreateStringMap() map[string]int { + return map[string]int{"one": 10, "two": 20, "three": 30} +} + +//export ProcessIntChannel +func ProcessIntChannel(ch chan int) int { + select { + case val := <-ch: + return val + default: + return -1 + } +} + +//export CreateIntChannel +func CreateIntChannel() chan int { + ch := make(chan int, 1) + ch <- 321 + return ch +} + +// Functions with function callbacks + +//export ProcessWithIntCallback +func ProcessWithIntCallback(x int, callback IntCallback) int { + if callback != nil { + return callback(x) + } + return x +} + +//export ProcessWithStringCallback +func ProcessWithStringCallback(s string, callback StringCallback) string { + if callback != nil { + return callback(s) + } + return s +} + +//export ProcessWithVoidCallback +func ProcessWithVoidCallback(callback VoidCallback) int { + if callback != nil { + callback() + return 123 // Return non-zero to indicate callback was called + } + return 456 // Return different value if callback is nil +} + +//export ProcessThreeUnnamedParams +func ProcessThreeUnnamedParams(a int, s string, b bool) float64 { + result := float64(a) + float64(len(s)) + if b { + result *= 1.5 + } + return result +} + +// Functions with interface + +//export ProcessInterface +func ProcessInterface(i interface{}) int { + switch v := i.(type) { + case int: + return v + 100 + case string: + return len(v) * 10 + default: + return 999 // Non-zero default to avoid false positives + } +} + +//export CreateIntInterface +func CreateIntInterface() interface{} { + return 23 +} + +//export CreateStringInterface +func CreateStringInterface() interface{} { + return "llgo" +} + +// Functions with various parameter counts + +//export NoParams +func NoParams() int { + return 42 +} + +//export OneParam +func OneParam(x int) int { + return x * 2 +} + +//export TwoParams +func TwoParams(a int, b string) string { + return string(rune(a)) + b +} + +//export ThreeParams +func ThreeParams(a int32, b float64, c bool) float64 { + result := float64(a) + b + if c { + result *= 2 + } + return result +} + +//export MultipleParams +func MultipleParams(a int8, b uint16, c int32, d uint64, e float32, f float64, g string, h bool) string { + result := g + "_" + string(rune('A'+a)) + string(rune('0'+b%10)) + string(rune('0'+c%10)) + if h { + result += "_true" + } + return result + "_" + string(rune('0'+int(d%10))) + "_" + string(rune('0'+int(e)%10)) + "_" + string(rune('0'+int(f)%10)) +} + +//export NoParamNames +func NoParamNames(int8, int16, bool) int32 { + return 789 // Return non-zero value for testing, params are unnamed by design +} + +// Functions returning no value + +//export NoReturn +func NoReturn(message string) { + println("Message:", message) +} + +// Functions using XType from c package + +//export CreateXType +func CreateXType(id int32, name string, value float64, flag bool) C.XType { + return C.XType{ + ID: id, + Name: name, + Value: value, + Flag: flag, + } +} + +//export ProcessXType +func ProcessXType(x C.XType) C.XType { + x.ID += 100 + x.Name = "processed_" + x.Name + x.Value *= 2.0 + x.Flag = !x.Flag + return x +} + +//export ProcessXTypePtr +func ProcessXTypePtr(x *C.XType) *C.XType { + if x != nil { + x.ID *= 2 + x.Name = "ptr_" + x.Name + x.Value += 10.0 + x.Flag = !x.Flag + } + return x +} + +func main() { + println("=== Export Demo ===") + + // Test small struct + small := CreateSmallStruct(5, true) + assert(small.ID, int8(5), "CreateSmallStruct ID should be 5") + assert(small.Flag, true, "CreateSmallStruct Flag should be true") + println("Small struct:", small.ID, small.Flag) + + processed := ProcessSmallStruct(small) + assert(processed.ID, int8(6), "ProcessSmallStruct should increment ID to 6") + assert(processed.Flag, false, "ProcessSmallStruct should flip Flag to false") + println("Processed small:", processed.ID, processed.Flag) + + // Test large struct + large := CreateLargeStruct(12345, "test") + assert(large.ID, int64(12345), "CreateLargeStruct ID should be 12345") + assert(large.Name, "test", "CreateLargeStruct Name should be 'test'") + println("Large struct ID:", large.ID, "Name:", large.Name) + + total := ProcessLargeStruct(large) + // Expected calculation: + // ID: 12345, Name len: 4, Values: 1+2+3+4+5+6+7+8+9+10=55, Children len: 2 + // Extra1: 12345, Extra2: 67890, Extra3: 3, Extra4: +1000, Extra5: 4096 + expectedTotal := int64(12345 + 4 + 55 + 2 + 12345 + 67890 + 3 + 1000 + 4096) + assert(total, expectedTotal, "ProcessLargeStruct total should match expected calculation") + println("Large struct total:", total) + + // Test self-referential struct + node1 := CreateNode(100) + node2 := CreateNode(200) + linkResult := LinkNodes(node1, node2) + assert(linkResult, 300, "LinkNodes should return sum of node data (100 + 200)") + + count := TraverseNodes(node1) + assert(count, 2, "TraverseNodes should count 2 linked nodes") + println("Node count:", count) + + // Test basic types with assertions + assert(ProcessBool(true), false, "ProcessBool(true) should return false") + assert(ProcessInt8(10), int8(11), "ProcessInt8(10) should return 11") + f32Result := ProcessFloat32(3.14) + // Float comparison with tolerance + if f32Result < 4.7 || f32Result > 4.72 { + println("ASSERTION FAILED: ProcessFloat32(3.14) should return ~4.71, got:", f32Result) + panic("float assertion failed") + } + println("✓ ProcessFloat32(3.14) returns ~4.71") + + assert(ProcessString("hello"), "processed_hello", "ProcessString should prepend 'processed_'") + + println("Bool:", ProcessBool(true)) + println("Int8:", ProcessInt8(10)) + println("Float32:", ProcessFloat32(3.14)) + println("String:", ProcessString("hello")) + + // Test named types + myInt := ProcessMyInt(MyInt(42)) + assert(myInt, MyInt(420), "ProcessMyInt(42) should return 420") + println("MyInt:", int(myInt)) + + myStr := ProcessMyString(MyString("world")) + assert(myStr, MyString("modified_world"), "ProcessMyString should prepend 'modified_'") + println("MyString:", string(myStr)) + + // Test collections + arr := [5]int{1, 2, 3, 4, 5} + arrSum := ProcessIntArray(arr) + assert(arrSum, 15, "ProcessIntArray([1,2,3,4,5]) should return 15") + println("Array sum:", arrSum) + + slice := []int{10, 20, 30} + sliceSum := ProcessIntSlice(slice) + assert(sliceSum, 60, "ProcessIntSlice([10,20,30]) should return 60") + println("Slice sum:", sliceSum) + + m := make(map[string]int) + m["a"] = 100 + m["b"] = 200 + mapSum := ProcessStringMap(m) + assert(mapSum, 300, "ProcessStringMap({'a':100,'b':200}) should return 300") + println("Map sum:", mapSum) + + // Test multidimensional arrays + matrix2d := CreateMatrix2D() + matrix2dSum := ProcessMatrix2D(matrix2d) + assert(matrix2dSum, int32(78), "ProcessMatrix2D should return 78 (sum of 1+2+...+12)") + println("Matrix2D sum:", matrix2dSum) + + matrix3d := CreateMatrix3D() + matrix3dSum := ProcessMatrix3D(matrix3d) + assert(matrix3dSum, uint32(300), "ProcessMatrix3D should return 300") + println("Matrix3D sum:", matrix3dSum) + + grid5x4 := CreateGrid5x4() + gridSum := ProcessGrid5x4(grid5x4) + assert(gridSum, 115.0, "ProcessGrid5x4 should return 115.0") + println("Grid5x4 sum:", gridSum) + + // Test complex data with multidimensional arrays + complexData := CreateComplexData() + complexSum := ProcessComplexData(complexData) + assert(complexSum, int32(78), "ProcessComplexData should return 78") + println("ComplexData matrix sum:", complexSum) + + // Test various parameter counts + assert(NoParams(), 42, "NoParams should return 42") + assert(OneParam(5), 10, "OneParam(5) should return 10") + assert(TwoParams(65, "_test"), "A_test", "TwoParams should return 'A_test'") + assert(ThreeParams(10, 2.5, true), 25.0, "ThreeParams should return 25.0") + assert(NoParamNames(1, 2, false), int32(789), "NoParamNames should return 789") + + println("NoParams:", NoParams()) + println("OneParam:", OneParam(5)) + println("TwoParams:", TwoParams(65, "_test")) + println("ThreeParams:", ThreeParams(10, 2.5, true)) + println("MultipleParams:", MultipleParams(1, 2, 3, 4, 5.0, 6.0, "result", true)) + println("NoParamNames:", NoParamNames(1, 2, false)) + + // Test XType from c package + xtype := CreateXType(42, "test", 3.14, true) + println("XType:", xtype.ID, xtype.Name, xtype.Value, xtype.Flag) + + processedX := ProcessXType(xtype) + println("Processed XType:", processedX.ID, processedX.Name, processedX.Value, processedX.Flag) + + ptrX := ProcessXTypePtr(&xtype) + if ptrX != nil { + println("Ptr XType:", ptrX.ID, ptrX.Name, ptrX.Value, ptrX.Flag) + } + + // Test callback functions + intResult := ProcessWithIntCallback(10, func(x int) int { return x * 3 }) + println("IntCallback result:", intResult) + + stringResult := ProcessWithStringCallback("hello", func(s string) string { return s + "_callback" }) + println("StringCallback result:", stringResult) + + ProcessWithVoidCallback(func() { println("VoidCallback executed") }) + + NoReturn("demo completed") +} diff --git a/_demo/go/export/libexport.h.want b/_demo/go/export/libexport.h.want new file mode 100644 index 0000000000..f9f635f1c4 --- /dev/null +++ b/_demo/go/export/libexport.h.want @@ -0,0 +1,328 @@ +/* Code generated by llgo; DO NOT EDIT. */ + +#ifndef __LIBEXPORT_H_ +#define __LIBEXPORT_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Platform-specific symbol renaming macro +#ifdef __APPLE__ + #define GO_SYMBOL_RENAME(go_name) __asm("_" go_name); +#else + #define GO_SYMBOL_RENAME(go_name) __asm(go_name); +#endif + +// Go runtime types +typedef struct { const char *p; intptr_t n; } GoString; +typedef struct { void *data; intptr_t len; intptr_t cap; } GoSlice; +typedef struct { void *data; } GoMap; +typedef struct { void *data; } GoChan; +typedef struct { void *data; void *type; } GoInterface; +typedef struct { float real; float imag; } GoComplex64; +typedef struct { double real; double imag; } GoComplex128; + +typedef struct { + int32_t Matrix[3][4]; + GoSlice Slices; + intptr_t IntArray[5]; + GoSlice DataList; +} main_ComplexData; + +typedef struct { + double data[5][4]; +} Array_double_5_4; + +typedef struct { + int8_t ID; + _Bool Flag; +} main_SmallStruct; + +typedef struct { + int64_t ID; + GoString Name; + double Values[10]; + GoMap Metadata; + GoSlice Children; + int32_t Extra1; + uint64_t Extra2; + float Extra3; + _Bool Extra4; + uintptr_t Extra5; +} main_LargeStruct; + +typedef struct { + int32_t data[4]; +} Array_int32_t_4; + +typedef struct { + int32_t data[3][4]; +} Array_int32_t_3_4; + +typedef struct { + uint8_t data[2][3][4]; +} Array_uint8_t_2_3_4; + +typedef struct main_Node main_Node; +struct main_Node { + intptr_t Data; + main_Node* Next; +}; + +typedef struct { + int32_t ID; + GoString Name; + double Value; + _Bool Flag; +} C_XType; + +typedef struct { + int32_t CallersCount; + uintptr_t FramePC; + GoString FrameFunction; + GoString FrameFile; + int32_t FrameLine; + GoString FuncName; + uintptr_t FuncEntry; + GoString FuncFile; + int32_t FuncLine; +} main_FuncInfoResult; + +typedef struct { + intptr_t data[5]; +} Array_intptr_t_5; + +typedef intptr_t main_MyInt; + +typedef GoString main_MyString; + +typedef intptr_t (*main_IntCallback)(intptr_t); + +typedef GoString (*main_StringCallback)(GoString); + +typedef void (*main_VoidCallback)(void); + +GoString +Concat(GoString a, GoString b); + +int64_t +Sub(int64_t a, int64_t b); + +intptr_t +Add(intptr_t a, intptr_t b); + +float +mul(float a, float b); + +void +github_com_xgo_dev_llgo__demo_go_export_c_init(void) GO_SYMBOL_RENAME("github.com/xgo-dev/llgo/_demo/go/export/c.init") + +intptr_t +AllThreadsSyscallStatus(void); + +main_ComplexData +CreateComplexData(void); + +Array_double_5_4 +CreateGrid5x4(void); + +GoChan +CreateIntChannel(void); + +GoInterface +CreateIntInterface(void); + +GoSlice +CreateIntSlice(void); + +main_LargeStruct +CreateLargeStruct(int64_t id, GoString name); + +Array_int32_t_4 +CreateMatrix1D(void); + +Array_int32_t_3_4 +CreateMatrix2D(void); + +Array_uint8_t_2_3_4 +CreateMatrix3D(void); + +main_Node* +CreateNode(intptr_t data); + +main_SmallStruct +CreateSmallStruct(int8_t id, _Bool flag); + +GoInterface +CreateStringInterface(void); + +GoMap +CreateStringMap(void); + +C_XType +CreateXType(int32_t id, GoString name, double value, _Bool flag); + +GoString +FormatValue(GoString value, intptr_t number); + +main_FuncInfoResult +GetFuncInfo(void); + +void +HelloWorld(void); + +intptr_t +LinkNodes(main_Node* first, main_Node* second); + +GoString +MultipleParams(int8_t a, uint16_t b, int32_t c, uint64_t d, float e, double f, GoString g, _Bool h); + +int32_t +NoParamNames(int8_t, int16_t, _Bool); + +intptr_t +NoParams(void); + +void +NoReturn(GoString message); + +intptr_t +OneParam(intptr_t x); + +_Bool +ProcessBool(_Bool b); + +int32_t +ProcessComplexData(main_ComplexData data); + +float +ProcessFloat32(float x); + +double +ProcessFloat64(double x); + +double +ProcessGrid5x4(Array_double_5_4 grid); + +intptr_t +ProcessInt(intptr_t x); + +int16_t +ProcessInt16(int16_t x); + +int32_t +ProcessInt32(int32_t x); + +int64_t +ProcessInt64(int64_t x); + +int8_t +ProcessInt8(int8_t x); + +intptr_t +ProcessIntArray(Array_intptr_t_5 arr); + +intptr_t +ProcessIntChannel(GoChan ch); + +intptr_t +ProcessIntSlice(GoSlice slice); + +intptr_t +ProcessInterface(GoInterface i); + +int64_t +ProcessLargeStruct(main_LargeStruct ls); + +main_LargeStruct* +ProcessLargeStructPtr(main_LargeStruct* ls); + +int32_t +ProcessMatrix2D(Array_int32_t_3_4 matrix); + +uint32_t +ProcessMatrix3D(Array_uint8_t_2_3_4 cube); + +main_MyInt +ProcessMyInt(main_MyInt x); + +main_MyString +ProcessMyString(main_MyString s); + +main_SmallStruct +ProcessSmallStruct(main_SmallStruct s); + +main_SmallStruct* +ProcessSmallStructPtr(main_SmallStruct* s); + +GoString +ProcessString(GoString s); + +intptr_t +ProcessStringMap(GoMap m); + +double +ProcessThreeUnnamedParams(intptr_t a, GoString s, _Bool b); + +uintptr_t +ProcessUint(uintptr_t x); + +uint16_t +ProcessUint16(uint16_t x); + +uint32_t +ProcessUint32(uint32_t x); + +uint64_t +ProcessUint64(uint64_t x); + +uint8_t +ProcessUint8(uint8_t x); + +uintptr_t +ProcessUintptr(uintptr_t x); + +void* +ProcessUnsafePointer(void* p); + +intptr_t +ProcessWithIntCallback(intptr_t x, main_IntCallback callback); + +GoString +ProcessWithStringCallback(GoString s, main_StringCallback callback); + +intptr_t +ProcessWithVoidCallback(main_VoidCallback callback); + +C_XType +ProcessXType(C_XType x); + +C_XType* +ProcessXTypePtr(C_XType* x); + +intptr_t +RunGoroutine(intptr_t value); + +double +ThreeParams(int32_t a, double b, _Bool c); + +intptr_t +TraverseNodes(main_Node* head); + +GoString +TwoParams(intptr_t a, GoString b); + +void +main_init(void) GO_SYMBOL_RENAME("main.init") + + + +#ifdef __cplusplus +} +#endif + +#endif /* __LIBEXPORT_H_ */ diff --git a/_demo/go/export/runtime_hooks_linux.go b/_demo/go/export/runtime_hooks_linux.go new file mode 100644 index 0000000000..5ecf8aa427 --- /dev/null +++ b/_demo/go/export/runtime_hooks_linux.go @@ -0,0 +1,28 @@ +//go:build linux + +package main + +import "syscall" + +func gettimeofdayStatus() int { + var tv syscall.Timeval + if err := syscall.Gettimeofday(&tv); err != nil { + if errno, ok := err.(syscall.Errno); ok { + return int(errno) + } + return int(syscall.EINVAL) + } + if tv.Sec <= 0 { + return int(syscall.EINVAL) + } + return 0 +} + +//export AllThreadsSyscallStatus +func AllThreadsSyscallStatus() int { + if status := gettimeofdayStatus(); status != 0 { + return status + } + _, _, err := syscall.AllThreadsSyscall(syscall.SYS_GETPID, 0, 0, 0) + return int(err) +} diff --git a/_demo/go/export/runtime_hooks_other.go b/_demo/go/export/runtime_hooks_other.go new file mode 100644 index 0000000000..2d5176f5ea --- /dev/null +++ b/_demo/go/export/runtime_hooks_other.go @@ -0,0 +1,8 @@ +//go:build !linux + +package main + +//export AllThreadsSyscallStatus +func AllThreadsSyscallStatus() int { + return 0 +} diff --git a/_demo/go/export/test.sh b/_demo/go/export/test.sh new file mode 100755 index 0000000000..094c528b3d --- /dev/null +++ b/_demo/go/export/test.sh @@ -0,0 +1,366 @@ +#!/bin/bash + +# Test script for C header generation in different build modes +# This script tests the header generation functionality with various buildmode options + +set -e # Exit on any error + +# Get script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to print colored output +print_status() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Function to check if file exists and is not empty +check_file() { + local file="$1" + local description="$2" + + if [[ -f "$file" ]]; then + if [[ -s "$file" ]]; then + print_status "$description exists and is not empty" + return 0 + else + print_error "$description exists but is empty" + return 1 + fi + else + print_error "$description does not exist" + return 1 + fi +} + +# Check that explicitly enabled LLGo DWARF is usable for a Go function reached +# through the C library. Darwin uses LLDB because Mach-O debug maps may refer +# back to archive members; ELF keeps the DWARF in the linked artifact. +check_c_library_debug_info() { + local build_mode="$1" + local artifact="$2" + local executable="$3" + local output + + if [[ "$OSTYPE" == "darwin"* ]]; then + if ! output=$(DYLD_LIBRARY_PATH=.. lldb --batch \ + -o "settings set target.env-vars DYLD_LIBRARY_PATH=.." \ + -o "breakpoint set -n main" \ + -o run \ + -o "breakpoint set -r captureCFuncInfo" \ + -o continue \ + -o "frame info" \ + -o "process kill" \ + "./$(basename "$executable")" 2>&1); then + print_error "$build_mode: LLDB verification failed" + echo "$output" + return 1 + fi + if ! grep -Eq 'captureCFuncInfo at .*export\.go:[0-9]+' <<< "$output"; then + print_error "$build_mode: LLDB did not resolve the Go source location" + echo "$output" + return 1 + fi + else + if [[ "$build_mode" == "c-archive" ]]; then + artifact="$executable" + fi + if ! output=$(llvm-dwarfdump --regex --name captureCFuncInfo "$artifact" 2>&1); then + print_error "$build_mode: DWARF inspection failed" + echo "$output" + return 1 + fi + if ! grep -q 'DW_TAG_subprogram' <<< "$output" || \ + ! grep -q 'export.go' <<< "$output"; then + print_error "$build_mode: DWARF does not resolve the Go function and source file" + echo "$output" + return 1 + fi + fi + + print_status "$build_mode: debug information resolved captureCFuncInfo in export.go" +} + +# Function to compare header with expected content +compare_header() { + local header_file="$1" + local expected_file="$2" + local test_name="$3" + + if [[ -f "$expected_file" ]]; then + if diff -q "$header_file" "$expected_file" >/dev/null 2>&1; then + print_status "$test_name: Header content matches expected" + return 0 + else + print_warning "$test_name: Header content differs from expected" + print_warning "Run 'diff $header_file $expected_file' to see differences" + return 1 + fi + else + print_warning "$test_name: No expected file found at $expected_file" + print_status "Generated header content:" + echo "--- START OF HEADER ---" + cat "$header_file" + echo "--- END OF HEADER ---" + return 0 + fi +} + +# Function to cleanup generated files +cleanup() { + local files=("$@") + for file in "${files[@]}"; do + if [[ -f "$file" ]]; then + rm -f "$file" + print_status "Cleaned up $file" + fi + done +} + +# Check if the llgo wrapper exists +LLGO_SCRIPT="../../../dev/llgo.sh" +if [[ ! -f "$LLGO_SCRIPT" ]]; then + print_error "llgo wrapper not found at $LLGO_SCRIPT" + exit 1 +fi + +print_status "Starting C header generation tests..." +print_status "Working directory: $SCRIPT_DIR" + +echo "" +build_failures=0 +run_build_mode_tests=true + +# The LLGo build wrapper loads the repository module, so these tests require a +# Go toolchain new enough for the go directive in the root go.mod. Older Go +# versions remain in the CI matrix for compatibility testing of installed LLGo. +CURRENT_GO_VERSION="$(go env GOVERSION)" +CURRENT_GO_VERSION="${CURRENT_GO_VERSION#go}" +REQUIRED_GO_VERSION="$(awk '$1 == "go" { print $2; exit }' ../../../go.mod)" +IFS=. read -r current_go_major current_go_minor _ <<< "$CURRENT_GO_VERSION" +IFS=. read -r required_go_major required_go_minor _ <<< "$REQUIRED_GO_VERSION" +if (( current_go_major < required_go_major || + (current_go_major == required_go_major && current_go_minor < required_go_minor) )); then + run_build_mode_tests=false + print_warning "Skipping c-shared/c-archive tests: Go $CURRENT_GO_VERSION is older than the module requirement $REQUIRED_GO_VERSION" +fi + +if [[ "$run_build_mode_tests" == true ]]; then + +# Test 1: c-shared mode +print_status "=== Test 1: Building with -buildmode c-shared ===" +if [[ "$OSTYPE" == "darwin"* ]]; then + SHARED_LIB="libexport.dylib" +else + SHARED_LIB="libexport.so" +fi +if $LLGO_SCRIPT build -buildmode c-shared -o "$SHARED_LIB" .; then + print_status "Build succeeded" + + # -o is an exact file name, matching cmd/go. Choose the platform suffix + # explicitly instead of relying on the build driver to rewrite it. + check_file "$SHARED_LIB" "Dynamic library ($SHARED_LIB)" + + check_file "libexport.h" "C header (libexport.h)" + + # Compare with expected header if it exists + if [[ -f "libexport.h" ]]; then + compare_header "libexport.h" "libexport.h.want" "c-shared" + fi + + # Test C demo with shared library + print_status "=== Testing C demo with shared library ===" + if cd use; then + if LINK_TYPE=shared make clean && LINK_TYPE=shared LLGOFLAGS=-ldflags=-w=false make; then + print_status "C demo build succeeded with shared library" + if LINK_TYPE=shared make run; then + print_status "C demo execution succeeded with shared library" + else + print_error "C demo execution failed with shared library" + build_failures=$((build_failures + 1)) + fi + if ! check_c_library_debug_info "c-shared" "../$SHARED_LIB" "main.out"; then + build_failures=$((build_failures + 1)) + fi + else + print_error "C demo build failed with shared library" + build_failures=$((build_failures + 1)) + fi + cd .. + else + print_error "Failed to enter use directory" + fi + + # Cleanup + cleanup "$SHARED_LIB" "libexport.h" +else + print_error "Build failed for c-shared mode" + build_failures=$((build_failures + 1)) +fi + +# Test 2: c-archive mode +print_status "=== Test 2: Building with -buildmode c-archive ===" +if $LLGO_SCRIPT build -buildmode c-archive -o libexport.a .; then + print_status "Build succeeded" + + # Check generated files + check_file "libexport.a" "Static library (libexport.a)" + check_file "libexport.h" "C header (libexport.h)" + + # Compare with expected header if it exists + if [[ -f "libexport.h" ]]; then + compare_header "libexport.h" "libexport.h.want" "c-archive" + fi + + # Test C demo with static library + print_status "=== Testing C demo with static library ===" + if cd use; then + if make clean && LLGOFLAGS=-ldflags=-w=false make; then + print_status "C demo build succeeded with static library" + if make run; then + print_status "C demo execution succeeded with static library" + else + print_error "C demo execution failed with static library" + build_failures=$((build_failures + 1)) + fi + if ! check_c_library_debug_info "c-archive" "../libexport.a" "main.out"; then + build_failures=$((build_failures + 1)) + fi + else + print_error "C demo build failed with static library" + build_failures=$((build_failures + 1)) + fi + cd .. + else + print_error "Failed to enter use directory" + fi + + # # Cleanup + # cleanup "libexport.a" "libexport.h" +else + print_error "Build failed for c-archive mode" + build_failures=$((build_failures + 1)) +fi + +fi + +echo "" + +# TODO(lijie): Uncomment if https://github.com/xgo-dev/llgo/pull/1268 merged +# # Test 3: ESP32 target with c-archive mode +# print_status "=== Test 3: Building with -target esp32 -buildmode c-archive ===" +# if $LLGO_SCRIPT build -target esp32 -buildmode c-archive -o export .; then +# print_status "Build succeeded" + +# # Check generated files +# check_file "libexport.a" "Static library for ESP32 (libexport.a)" +# check_file "libexport.h" "C header for ESP32 (libexport.h)" + +# # Compare with expected header if it exists +# if [[ -f "libexport.h" ]]; then +# compare_header "libexport.h" "libexport.h.want" "esp32-c-archive" +# fi + +# # Don't cleanup ESP32 files - keep them for inspection +# print_status "ESP32 build files kept for inspection" +# else +# print_error "Build failed for ESP32 target" +# fi + +# echo "" + +# Test 3: Go export demo execution +print_status "=== Test 3: Running Go export demo ===" +if go run export.go > /tmp/go_export_output.log 2>&1; then + print_status "Go export demo execution succeeded" + + # Check if output contains expected success indicators + if grep -q "✓" /tmp/go_export_output.log; then + SUCCESS_COUNT=$(grep -c "✓" /tmp/go_export_output.log) + print_status "All $SUCCESS_COUNT assertions passed in Go export demo" + else + print_warning "No assertion markers found in Go export demo output" + fi + + # Show key output lines + print_status "Go export demo output summary:" + if grep -q "ASSERTION FAILED" /tmp/go_export_output.log; then + print_error "Found assertion failures in Go export demo" + grep "ASSERTION FAILED" /tmp/go_export_output.log + else + print_status " ✅ No assertion failures detected" + echo " 📊 First few lines of output:" + head -5 /tmp/go_export_output.log | sed 's/^/ /' + echo " 📊 Last few lines of output:" + tail -5 /tmp/go_export_output.log | sed 's/^/ /' + fi +else + print_error "Go export demo execution failed" + print_error "Error output:" + cat /tmp/go_export_output.log | sed 's/^/ /' +fi + +# Cleanup temporary file +rm -f /tmp/go_export_output.log + +echo "" + +# Final summary +print_status "=== Test Summary ===" +if [[ "$run_build_mode_tests" != true ]]; then + print_status "Build-mode tests were skipped because the active Go toolchain is older than the module requirement" +elif [[ "$build_failures" -eq 0 ]] && [[ -f "libexport.a" ]] && [[ -f "libexport.h" ]]; then + print_status "All required build-mode tests completed successfully:" + print_status " ✅ Go export demo execution with assertions" + print_status " ✅ C header generation (c-archive and c-shared modes)" + print_status " ✅ C consumer compilation and execution with shared and static libraries" + print_status " ✅ Explicit -w=false DWARF source lookup for c-archive and c-shared" + print_status " ✅ Cross-platform symbol renaming" + print_status " ✅ Init function export and calling" + print_status " ✅ Function callback types with proper typedef syntax" + print_status " ✅ Multidimensional array parameter handling" + print_status "" + print_status "Final files available:" + print_status " - libexport.a (static library)" + print_status " - libexport.h (C header file)" + if [[ -f "use/main.out" ]]; then + print_status " - use/main.out (C demo executable)" + fi + + echo "" + echo "===================" +else + print_error "Some tests may have failed. Check the output above." +fi + +# Show file sizes for reference when the build-mode tests ran. +if [[ "$run_build_mode_tests" == true ]] && [[ -f "libexport.a" ]]; then + SIZE=$(wc -c < libexport.a) + print_status "Static library size: $SIZE bytes" +fi + +if [[ "$run_build_mode_tests" == true ]] && [[ -f "libexport.h" ]]; then + LINES=$(wc -l < libexport.h) + print_status "Header file lines: $LINES" +fi + +if [[ "$build_failures" -ne 0 ]]; then + print_error "$build_failures build-mode test(s) failed" + exit 1 +fi + +print_status "C header generation and demo tests completed!" diff --git a/_demo/go/export/use/Makefile b/_demo/go/export/use/Makefile new file mode 100644 index 0000000000..5691dcb62a --- /dev/null +++ b/_demo/go/export/use/Makefile @@ -0,0 +1,89 @@ +# Makefile for C demo using Go exported library +# Use LINK_TYPE environment variable to choose library type: +# LINK_TYPE=static - Link with static library (default) +# LINK_TYPE=shared - Link with shared library + +CC = clang +CFLAGS = -Wall -Wextra -std=c99 +INCLUDES = -I.. +TARGET = main.out +SOURCES = main.c +HEADER = ../libexport.h +RUNTIME_LIBS = -lpthread -lm $(shell pkg-config --libs bdw-gc || echo -lgc) $(shell pkg-config --libs libuv || echo -luv) + +# Default to static linking +LINK_TYPE ?= static +LLGOFLAGS ?= + +# Platform detection +UNAME_S := $(shell uname -s) +ifeq ($(UNAME_S),Darwin) + SHARED_EXT = dylib + PLATFORM_LIBS = +else + SHARED_EXT = so + PLATFORM_LIBS = $(shell pkg-config --libs libunwind 2>/dev/null || echo -lunwind) +endif +STATIC_STDLIB_LIBS = $(shell pkg-config --libs libffi 2>/dev/null || echo -lffi) -lresolv + +# Library and flags based on link type +ifeq ($(LINK_TYPE),shared) + BUILDMODE = c-shared + OUTPUT = libexport.$(SHARED_EXT) + LIBRARY = ../$(OUTPUT) + LDFLAGS = -L.. -lexport $(RUNTIME_LIBS) $(PLATFORM_LIBS) + BUILD_MSG = "Building Go shared library..." + LINK_MSG = "Linking with shared library..." +else + BUILDMODE = c-archive + OUTPUT = libexport.a + LIBRARY = ../$(OUTPUT) + LDFLAGS = $(LIBRARY) $(RUNTIME_LIBS) $(PLATFORM_LIBS) $(STATIC_STDLIB_LIBS) + BUILD_MSG = "Building Go static library..." + LINK_MSG = "Linking with static library..." +endif + +.PHONY: all clean run build-go + +all: build-go $(TARGET) + +# Build the Go library first +build-go: + @echo $(BUILD_MSG) + cd .. && ../../../dev/llgo.sh build -buildmode $(BUILDMODE) $(LLGOFLAGS) -o $(OUTPUT) . + +# Build the C executable +$(TARGET): $(SOURCES) $(LIBRARY) $(HEADER) + @echo $(LINK_MSG) + $(CC) $(CFLAGS) $(INCLUDES) -o $(TARGET) $(SOURCES) $(LDFLAGS) + +# Run the executable +run: $(TARGET) + @echo "Running C demo..." +ifeq ($(LINK_TYPE),shared) + @echo "Setting library path for shared library..." + LD_LIBRARY_PATH=.. DYLD_LIBRARY_PATH=.. ./$(TARGET) +else + ./$(TARGET) +endif + +# Clean build artifacts +clean: + rm -f $(TARGET) + rm -f ../libexport.a ../libexport.h ../libexport.so ../libexport.dylib + +# Help target +help: + @echo "Available targets:" + @echo " all - Build Go library and C executable" + @echo " build-go - Build only the Go library" + @echo " run - Build and run the C demo" + @echo " clean - Clean all build artifacts" + @echo " help - Show this help message" + @echo "" + @echo "Environment variables:" + @echo " LINK_TYPE - Library type: 'static' (default) or 'shared'" + @echo "" + @echo "Examples:" + @echo " make run # Use static library" + @echo " LINK_TYPE=shared make run # Use shared library" diff --git a/_demo/go/export/use/main.c b/_demo/go/export/use/main.c new file mode 100644 index 0000000000..bdb681bcd0 --- /dev/null +++ b/_demo/go/export/use/main.c @@ -0,0 +1,333 @@ +#include +#include +#include +#include +#include +#include +#include "../libexport.h" + +static int go_string_equals(GoString got, const char *want) { + size_t want_len = strlen(want); + return got.n == (intptr_t)want_len && memcmp(got.p, want, want_len) == 0; +} + +static int go_string_has_suffix(GoString got, const char *suffix) { + size_t suffix_len = strlen(suffix); + return got.n >= (intptr_t)suffix_len && + memcmp(got.p + got.n - suffix_len, suffix, suffix_len) == 0; +} + +static intptr_t int_callback(intptr_t value) { + return value + 700; +} + +static GoString string_callback(GoString value) { + return value; +} + +static int void_callback_count; + +static void void_callback(void) { + void_callback_count++; +} + +int main() { + printf("=== C Export Demo ===\n"); + fflush(stdout); // Force output + + // Initialize packages - call init functions first + github_com_xgo_dev_llgo__demo_go_export_c_init(); + main_init(); + + // Verify that funcinfo is not merely linkable: runtime.Callers must yield + // a symbolized frame and runtime.FuncForPC must resolve that PC's details. + main_FuncInfoResult func_info = GetFuncInfo(); + assert(func_info.CallersCount > 0); + assert(func_info.FramePC != 0); + assert(go_string_equals(func_info.FrameFunction, "main.captureCFuncInfo")); + assert(go_string_has_suffix(func_info.FrameFile, "export.go")); + assert(func_info.FrameLine > 0); + assert(go_string_equals(func_info.FuncName, "main.captureCFuncInfo")); + assert(func_info.FuncEntry != 0); + assert(go_string_has_suffix(func_info.FuncFile, "export.go")); + assert(func_info.FuncLine == func_info.FrameLine); + printf("FuncInfo: %.*s %.*s:%d\n", + (int)func_info.FuncName.n, func_info.FuncName.p, + (int)func_info.FuncFile.n, func_info.FuncFile.p, + func_info.FuncLine); + + // Test HelloWorld + HelloWorld(); + printf("\n"); + + // Verify that a C library can initialize and call standard-library paths + // that depend on the runtime hooks supplied by LLGo. + GoString formatted = FormatValue((GoString){"answer", 6}, 42); + assert(go_string_equals(formatted, "answer:42")); +#ifdef __linux__ + assert(AllThreadsSyscallStatus() == ENOTSUP); +#else + assert(AllThreadsSyscallStatus() == 0); +#endif + + // Test small struct + main_SmallStruct small = CreateSmallStruct(5, 1); // 1 for true + assert(small.ID == 5); + assert(small.Flag == 1); + printf("Small struct: %d %d\n", small.ID, small.Flag); + + main_SmallStruct processed = ProcessSmallStruct(small); + assert(processed.ID == 6); + assert(processed.Flag == 0); + printf("Processed small: %d %d\n", processed.ID, processed.Flag); + + main_SmallStruct* ptrSmall = ProcessSmallStructPtr(&small); + if (ptrSmall != NULL) { + printf("Ptr small: %d %d\n", ptrSmall->ID, ptrSmall->Flag); + } + + // Test large struct - create GoString for name parameter + GoString name = {"test_large", 10}; // name and length + main_LargeStruct large = CreateLargeStruct(12345, name); + assert(large.ID == 12345); + printf("Large struct ID: %" PRId64 "\n", large.ID); + + int64_t total = ProcessLargeStruct(large); + printf("Large struct total: %" PRId64 "\n", total); + + main_LargeStruct* ptrLarge = ProcessLargeStructPtr(&large); + if (ptrLarge != NULL) { + printf("Ptr large ID: %" PRId64 "\n", ptrLarge->ID); + } + + // Test self-referential struct + main_Node* node1 = CreateNode(100); + main_Node* node2 = CreateNode(200); + int link_result = LinkNodes(node1, node2); + assert(link_result == 300); // LinkNodes returns 100 + 200 = 300 + printf("LinkNodes result: %d\n", link_result); + + int count = TraverseNodes(node1); + assert(count == 2); // Should traverse 2 nodes + printf("Node count: %d\n", count); + + // Test basic types with assertions + assert(ProcessBool(1) == 0); // ProcessBool(true) returns !true = false + printf("Bool: %d\n", ProcessBool(1)); + + assert(ProcessInt8(10) == 11); // ProcessInt8(x) returns x + 1 + printf("Int8: %d\n", ProcessInt8(10)); + + assert(ProcessUint8(10) == 11); // ProcessUint8(x) returns x + 1 + printf("Uint8: %d\n", ProcessUint8(10)); + + assert(ProcessInt16(10) == 20); // ProcessInt16(x) returns x * 2 + printf("Int16: %d\n", ProcessInt16(10)); + + assert(ProcessUint16(10) == 20); // ProcessUint16(x) returns x * 2 + printf("Uint16: %d\n", ProcessUint16(10)); + + assert(ProcessInt32(10) == 30); // ProcessInt32(x) returns x * 3 + printf("Int32: %d\n", ProcessInt32(10)); + + assert(ProcessUint32(10) == 30); // ProcessUint32(x) returns x * 3 + printf("Uint32: %u\n", ProcessUint32(10)); + + assert(ProcessInt64(10) == 40); // ProcessInt64(x) returns x * 4 + printf("Int64: %" PRId64 "\n", ProcessInt64(10)); + + assert(ProcessUint64(10) == 40); // ProcessUint64(x) returns x * 4 + printf("Uint64: %" PRIu64 "\n", ProcessUint64(10)); + + assert(ProcessInt(10) == 110); // ProcessInt(x) returns x * 11 + printf("Int: %ld\n", ProcessInt(10)); + + assert(ProcessUint(10) == 210); // ProcessUint(x) returns x * 21 + printf("Uint: %lu\n", ProcessUint(10)); + + assert(ProcessUintptr(0x1000) == 4396); // ProcessUintptr(x) returns x + 300 = 4096 + 300 + printf("Uintptr: %lu\n", ProcessUintptr(0x1000)); + + // Float comparisons with tolerance + float f32_result = ProcessFloat32(3.14f); + assert(f32_result > 4.7f && f32_result < 4.72f); // ProcessFloat32(x) returns x * 1.5 ≈ 4.71 + printf("Float32: %f\n", f32_result); + + double f64_result = ProcessFloat64(3.14); + assert(f64_result > 7.84 && f64_result < 7.86); // ProcessFloat64(x) returns x * 2.5 ≈ 7.85 + printf("Float64: %f\n", f64_result); + + GoString raw_string = {"value", 5}; + GoString processed_string = ProcessString(raw_string); + assert(go_string_equals(processed_string, "processed_value")); + assert(RunGoroutine(41) == 42); + assert(go_string_equals(ProcessMyString(raw_string), "modified_value")); + assert(go_string_equals(TwoParams(65, (GoString){"bc", 2}), "Abc")); + assert(go_string_equals(MultipleParams(1, 2, 3, 4, 5.0f, 6.0, + (GoString){"multi", 5}, 1), "multi_B23_true_4_5_6")); + + // Test unsafe pointer + int test_val = 42; + void* ptr_result = ProcessUnsafePointer(&test_val); + printf("UnsafePointer: %p\n", ptr_result); + + // Test named types + main_MyInt myInt = ProcessMyInt(42); + printf("MyInt: %ld\n", (long)myInt); + + // Test arrays + Array_intptr_t_5 arr = {.data = {1, 2, 3, 4, 5}}; + intptr_t arr_sum = ProcessIntArray(arr); + assert(arr_sum == 15); + printf("Array sum: %ld\n", (long)arr_sum); + + // Test complex data with multidimensional arrays + main_ComplexData complex = CreateComplexData(); + assert(ProcessComplexData(complex) == 78); + assert(complex.IntArray[0] == 10 && complex.IntArray[4] == 50); + assert(complex.Slices.len == 1); + assert(complex.DataList.len == 1); + assert(((double*)complex.DataList.data)[0] == 1.0); + printf("Complex data matrix sum: %" PRId32 "\n", ProcessComplexData(complex)); + + intptr_t c_slice_values[] = {3, 6, 9}; + GoSlice c_slice = {c_slice_values, 3, 3}; + assert(ProcessIntSlice(c_slice) == 18); + GoSlice int_slice = CreateIntSlice(); + assert(int_slice.len == 4); + assert(ProcessIntSlice(int_slice) == 20); + GoMap string_map = CreateStringMap(); + assert(string_map.data != NULL); + assert(ProcessStringMap(string_map) == 60); + GoChan int_channel = CreateIntChannel(); + assert(int_channel.data != NULL); + assert(ProcessIntChannel(int_channel) == 321); + GoInterface int_interface = CreateIntInterface(); + GoInterface string_interface = CreateStringInterface(); + assert(ProcessInterface(int_interface) == 123); + assert(ProcessInterface(string_interface) == 40); + + // Test various parameter counts + assert(NoParams() == 42); // NoParams() always returns 42 + printf("NoParams: %ld\n", NoParams()); + + assert(OneParam(5) == 10); // OneParam(x) returns x * 2 + printf("OneParam: %ld\n", OneParam(5)); + + assert(ThreeParams(10, 2.5, 1) == 25.0); // ThreeParams calculates result + printf("ThreeParams: %f\n", ThreeParams(10, 2.5, 1)); // 1 for true + + // Test ProcessThreeUnnamedParams - now uses all parameters + GoString test_str = {"hello", 5}; + double unnamed_result = ProcessThreeUnnamedParams(10, test_str, 1); + assert(unnamed_result == 22.5); // (10 + 5) * 1.5 = 22.5 + printf("ProcessThreeUnnamedParams: %f\n", unnamed_result); + + assert(ProcessWithIntCallback(23, int_callback) == 723); + assert(go_string_equals(ProcessWithStringCallback(raw_string, string_callback), "value")); + assert(ProcessWithVoidCallback(void_callback) == 123); + assert(void_callback_count == 1); + + // Test ProcessWithVoidCallback - now returns int + int void_callback_result = ProcessWithVoidCallback(NULL); + assert(void_callback_result == 456); // Returns 456 when callback is nil + printf("ProcessWithVoidCallback(NULL): %d\n", void_callback_result); + + // Test NoParamNames - function with unnamed parameters + int32_t no_names_result = NoParamNames(5, 10, 0); + assert(no_names_result == 789); // Returns fixed value 789 + printf("NoParamNames: %d\n", no_names_result); + + // Test XType from c package - create GoString for name parameter + GoString xname = {"test_x", 6}; // name and length + C_XType xtype = CreateXType(42, xname, 3.14, 1); // 1 for true + printf("XType: %d %f %d\n", xtype.ID, xtype.Value, xtype.Flag); + + C_XType processedX = ProcessXType(xtype); + printf("Processed XType: %d %f %d\n", processedX.ID, processedX.Value, processedX.Flag); + + C_XType* ptrX = ProcessXTypePtr(&xtype); + if (ptrX != NULL) { + printf("Ptr XType: %d %f %d\n", ptrX->ID, ptrX->Value, ptrX->Flag); + } + + // Test multidimensional arrays + printf("\n=== Multidimensional Array Tests ===\n"); + + // Create and test 2D matrix [3][4] + // Note: CreateMatrix2D returns [3][4]int32, but function returns need special handling in C + printf("Testing 2D matrix functions...\n"); + + // Create a test 2D matrix [3][4]int32 + Array_int32_t_3_4 test_matrix = {.data = { + {1, 2, 3, 4}, + {5, 6, 7, 8}, + {9, 10, 11, 12} + }}; + int32_t matrix_sum = ProcessMatrix2D(test_matrix); + assert(matrix_sum == 78); // Sum of 1+2+3+...+12 = 78 + printf("Matrix2D sum: %d\n", matrix_sum); + + // Create a test 3D cube [2][3][4]uint8 + Array_uint8_t_2_3_4 test_cube; + uint8_t val = 1; + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 3; j++) { + for (int k = 0; k < 4; k++) { + test_cube.data[i][j][k] = val++; + } + } + } + uint32_t cube_sum = ProcessMatrix3D(test_cube); + assert(cube_sum == 300); // Sum of 1+2+3+...+24 = 300 + printf("Matrix3D (cube) sum: %u\n", cube_sum); + + // Create a test 5x4 grid [5][4]double + Array_double_5_4 test_grid; + double grid_val = 1.0; + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 4; j++) { + test_grid.data[i][j] = grid_val; + grid_val += 0.5; + } + } + double grid_sum = ProcessGrid5x4(test_grid); + assert(grid_sum == 115.0); // Sum of 1.0+1.5+2.0+...+10.5 = 115.0 + printf("Grid5x4 sum: %f\n", grid_sum); + + // Test functions that return multidimensional arrays (as multi-level pointers) + printf("\n=== Testing Return Value Functions ===\n"); + + // Test CreateMatrix1D() which returns Array_int32_t_4 + printf("About to call CreateMatrix1D()...\n"); + fflush(stdout); + Array_int32_t_4 matrix1d = CreateMatrix1D(); + assert(matrix1d.data[0] == 1); + printf("CreateMatrix1D() call completed\n"); + printf("CreateMatrix1D() returned struct, first element: %d\n", matrix1d.data[0]); + + // Test CreateMatrix2D() which returns Array_int32_t_3_4 + printf("About to call CreateMatrix2D()...\n"); + fflush(stdout); + Array_int32_t_3_4 matrix2d = CreateMatrix2D(); + assert(matrix2d.data[0][0] == 1); + printf("CreateMatrix2D() call completed\n"); + printf("CreateMatrix2D() returned struct, first element: %d\n", matrix2d.data[0][0]); + + // Test CreateMatrix3D() which returns Array_uint8_t_2_3_4 + Array_uint8_t_2_3_4 cube = CreateMatrix3D(); + assert(cube.data[0][0][0] == 1); + printf("CreateMatrix3D() returned struct, first element: %u\n", cube.data[0][0][0]); + + // Test CreateGrid5x4() which returns Array_double_5_4 + Array_double_5_4 grid = CreateGrid5x4(); + assert(grid.data[0][0] == 1.0); + printf("CreateGrid5x4() returned struct, first element: %f\n", grid.data[0][0]); + + // Test a void function with a string parameter. + NoReturn((GoString){"called from C", 13}); + + printf("C demo completed!\n"); + + return 0; +} diff --git a/_demo/go/failed/stacktrace/main.go b/_demo/go/failed/stacktrace/main.go new file mode 100644 index 0000000000..089b496003 --- /dev/null +++ b/_demo/go/failed/stacktrace/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" +) + +type MyStruct[T any] struct { + value T +} + +func (m *MyStruct[T]) Method() { + fmt.Println("In generic method") + genericFunc[T](m.value) +} + +func genericFunc[T any](v T) { + fmt.Println("In generic function") + normalFunc() +} + +func normalFunc() { + fmt.Println("In normal function") + panic("panic occurs here") +} + +func main() { + m := &MyStruct[string]{value: "hello"} + m.Method() +} + +//Expected: +// In generic method +// In generic function +// In normal function +// panic: panic occurs here + +// [0x00C6D310 github.com/xgo-dev/llgo/internal/runtime.Rethrow+0x2f, SP = 0x60] +// [0x00C6CF44 github.com/xgo-dev/llgo/internal/runtime.Panic+0x2d, SP = 0x50] +// [0x00C69420 main.normalFunc+0xf, SP = 0xa8] +// [0x00C69564 main.genericFunc[string]+0x18, SP = 0x74] +// [0x00C694A8 main.(*MyStruct[string]).Method+0x1f, SP = 0x84] +// [0x00C6936C main+0x4, SP = 0x40] diff --git a/_demo/go/go.mod b/_demo/go/go.mod new file mode 100644 index 0000000000..29365f8665 --- /dev/null +++ b/_demo/go/go.mod @@ -0,0 +1,5 @@ +module github.com/xgo-dev/llgo/_demo/go + +go 1.20 + +require github.com/goplus/lib v0.3.1 diff --git a/_demo/go/go.sum b/_demo/go/go.sum new file mode 100644 index 0000000000..ef2a0923de --- /dev/null +++ b/_demo/go/go.sum @@ -0,0 +1,2 @@ +github.com/goplus/lib v0.3.1 h1:Xws4DBVvgOMu58awqB972wtvTacDbk3nqcbHjdx9KSg= +github.com/goplus/lib v0.3.1/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0= diff --git a/_demo/go/gobuild-1389/main.go b/_demo/go/gobuild-1389/main.go new file mode 100644 index 0000000000..7fc4beba66 --- /dev/null +++ b/_demo/go/gobuild-1389/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "go/build" +) + +func main() { + _ = &build.Default +} diff --git a/_demo/go/gobuild/demo.go b/_demo/go/gobuild/demo.go new file mode 100644 index 0000000000..b2177544ce --- /dev/null +++ b/_demo/go/gobuild/demo.go @@ -0,0 +1,147 @@ +package main + +import ( + "fmt" + "go/build" + "runtime" + "strings" +) + +func main() { + fmt.Printf("runtime.Compiler = %q\n", runtime.Compiler) + + // Test 1: Check build.Default context + ctx := build.Default + fmt.Printf("build.Default.Compiler = %q\n", ctx.Compiler) + if ctx.Compiler != "gc" { + panic(fmt.Sprintf("expected build.Default.Compiler to be \"gc\", got %q", ctx.Compiler)) + } + + if len(ctx.ToolTags) == 0 { + panic("expected build.Default.ToolTags to be non-empty") + } + fmt.Printf("build.Default.ToolTags = %v\n", ctx.ToolTags) + + if len(ctx.ReleaseTags) == 0 { + panic("expected build.Default.ReleaseTags to be non-empty") + } + fmt.Printf("build.Default.ReleaseTags count = %d\n", len(ctx.ReleaseTags)) + + // Validate GOOS and GOARCH are set + if ctx.GOOS == "" { + panic("expected build.Default.GOOS to be non-empty") + } + if ctx.GOARCH == "" { + panic("expected build.Default.GOARCH to be non-empty") + } + fmt.Printf("build.Default.GOOS = %q, GOARCH = %q\n", ctx.GOOS, ctx.GOARCH) + + // Test 2: Import standard library package with FindOnly + pkg, err := build.Import("fmt", "", build.FindOnly) + if err != nil { + panic(fmt.Sprintf("build.Import(\"fmt\") failed: %v", err)) + } + if pkg.ImportPath != "fmt" { + panic(fmt.Sprintf("expected ImportPath \"fmt\", got %q", pkg.ImportPath)) + } + if !pkg.Goroot { + panic("expected fmt package to be in GOROOT") + } + fmt.Printf("build.Import(\"fmt\"): ImportPath=%s, Goroot=%v\n", pkg.ImportPath, pkg.Goroot) + + // Test 3: Import nested standard library package + osPkg, err := build.Import("os/exec", "", build.FindOnly) + if err != nil { + panic(fmt.Sprintf("build.Import(\"os/exec\") failed: %v", err)) + } + if osPkg.ImportPath != "os/exec" { + panic(fmt.Sprintf("expected ImportPath \"os/exec\", got %q", osPkg.ImportPath)) + } + if !osPkg.Goroot { + panic("expected os/exec package to be in GOROOT") + } + fmt.Printf("build.Import(\"os/exec\"): ImportPath=%s, Goroot=%v\n", osPkg.ImportPath, osPkg.Goroot) + + // Test 4: Import internal package (should succeed with FindOnly) + internalPkg, err := build.Import("internal/cpu", "", build.FindOnly) + if err != nil { + panic(fmt.Sprintf("build.Import(\"internal/cpu\") failed: %v", err)) + } + if internalPkg.ImportPath != "internal/cpu" { + panic(fmt.Sprintf("expected ImportPath \"internal/cpu\", got %q", internalPkg.ImportPath)) + } + fmt.Printf("build.Import(\"internal/cpu\"): ImportPath=%s\n", internalPkg.ImportPath) + + // Test 5: Import with srcDir parameter + runtimePkg, err := build.Import("runtime", "", build.FindOnly) + if err != nil { + panic(fmt.Sprintf("build.Import(\"runtime\") failed: %v", err)) + } + if runtimePkg.ImportPath != "runtime" { + panic(fmt.Sprintf("expected ImportPath \"runtime\", got %q", runtimePkg.ImportPath)) + } + if runtimePkg.Dir == "" { + panic("expected runtime package Dir to be non-empty") + } + fmt.Printf("build.Import(\"runtime\"): ImportPath=%s, Dir exists=%v\n", runtimePkg.ImportPath, runtimePkg.Dir != "") + + // Test 6: ImportDir with current directory + dirPkg, err := build.ImportDir(".", build.FindOnly) + if err != nil { + panic(fmt.Sprintf("build.ImportDir(\".\") failed: %v", err)) + } + // Note: Name might be empty with FindOnly mode as it doesn't read source files + fmt.Printf("build.ImportDir(\".\"): Dir=%s, ImportPath=%s\n", dirPkg.Dir, dirPkg.ImportPath) + + // Test 7: IsLocalImport with various paths + testCases := []struct { + path string + expected bool + }{ + {"./foo", true}, + {"../bar", true}, + {"./", true}, + {"fmt", false}, + {"github.com/user/repo", false}, + {"", false}, + } + for _, tc := range testCases { + result := build.IsLocalImport(tc.path) + if result != tc.expected { + panic(fmt.Sprintf("build.IsLocalImport(%q): expected %v, got %v", tc.path, tc.expected, result)) + } + } + fmt.Printf("build.IsLocalImport: all test cases passed\n") + + // Test 8: Verify Context has expected fields + if ctx.GOPATH == "" && ctx.GOROOT == "" { + panic("expected either GOPATH or GOROOT to be set") + } + fmt.Printf("build.Default.GOROOT exists = %v\n", ctx.GOROOT != "") + + // Test 9: Import with AllowBinary flag + binaryPkg, err := build.Import("fmt", "", build.FindOnly|build.AllowBinary) + if err != nil { + panic(fmt.Sprintf("build.Import with AllowBinary failed: %v", err)) + } + if binaryPkg.ImportPath != "fmt" { + panic(fmt.Sprintf("expected ImportPath \"fmt\", got %q", binaryPkg.ImportPath)) + } + fmt.Printf("build.Import(\"fmt\") with AllowBinary: success\n") + + // Test 10: Verify compiler tag in build context + hasCompilerTag := false + for _, tag := range ctx.ReleaseTags { + if strings.HasPrefix(tag, "go1.") { + hasCompilerTag = true + break + } + } + if !hasCompilerTag { + panic("expected at least one go1.x release tag") + } + fmt.Printf("build.Default.ReleaseTags: contains go1.x tags = %v\n", hasCompilerTag) + + fmt.Printf("\nSuccess! All go/build public functions work correctly with llgo\n") + fmt.Printf("Total tests passed: 10\n") +} diff --git a/_demo/go/goimporter-1389/main.go b/_demo/go/goimporter-1389/main.go new file mode 100644 index 0000000000..8c8ed9e2b1 --- /dev/null +++ b/_demo/go/goimporter-1389/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "go/importer" + "go/token" +) + +func main() { + fset := token.NewFileSet() + imp := importer.ForCompiler(fset, "gc", nil) + _ = imp +} diff --git a/_demo/goroutine/goroutine.go b/_demo/go/goroutine/goroutine.go similarity index 100% rename from _demo/goroutine/goroutine.go rename to _demo/go/goroutine/goroutine.go diff --git a/_demo/gotime/time.go b/_demo/go/gotime/time.go similarity index 100% rename from _demo/gotime/time.go rename to _demo/go/gotime/time.go diff --git a/_demo/go/gotoken/main.go b/_demo/go/gotoken/main.go new file mode 100644 index 0000000000..1ccf5c10bb --- /dev/null +++ b/_demo/go/gotoken/main.go @@ -0,0 +1,356 @@ +package main + +import ( + "fmt" + "go/token" +) + +func main() { + testPos() + testToken() + testFileSet() + testFile() + testPosition() + testTokenPrecedence() + testTokenKeywords() + testUtilityFunctions() +} + +func testPos() { + fmt.Println("=== Test Pos ===") + + pos1 := token.Pos(100) + pos2 := token.Pos(200) + + if pos1 != 100 { + panic(fmt.Sprintf("Expected pos1 to be 100, got %d", pos1)) + } + if pos2 != 200 { + panic(fmt.Sprintf("Expected pos2 to be 200, got %d", pos2)) + } + fmt.Printf("Pos1: %d, Pos2: %d\n", pos1, pos2) + + if !pos1.IsValid() { + panic("Expected pos1.IsValid() to be true") + } + fmt.Printf("Pos1.IsValid(): %v\n", pos1.IsValid()) + + noPos := token.NoPos + if noPos != 0 { + panic(fmt.Sprintf("Expected NoPos to be 0, got %d", noPos)) + } + if noPos.IsValid() { + panic("Expected NoPos.IsValid() to be false") + } + fmt.Printf("NoPos: %d, IsValid: %v\n", noPos, noPos.IsValid()) + + fmt.Println("SUCCESS: Pos operations work correctly\n") +} + +func testToken() { + fmt.Println("\n=== Test Token Types ===") + + expectedStrings := map[token.Token]string{ + token.ADD: "+", + token.SUB: "-", + token.MUL: "*", + token.QUO: "/", + token.LPAREN: "(", + token.RPAREN: ")", + token.EQL: "==", + token.NEQ: "!=", + } + + for tok, expected := range expectedStrings { + if tok.String() != expected { + panic(fmt.Sprintf("Expected %v.String() to be %q, got %q", tok, expected, tok.String())) + } + } + + tokens := []token.Token{ + token.ILLEGAL, + token.EOF, + token.COMMENT, + token.IDENT, + token.INT, + token.FLOAT, + token.IMAG, + token.CHAR, + token.STRING, + token.ADD, + token.SUB, + token.MUL, + token.QUO, + token.REM, + token.AND, + token.OR, + token.XOR, + token.SHL, + token.SHR, + token.AND_NOT, + token.ADD_ASSIGN, + token.SUB_ASSIGN, + token.MUL_ASSIGN, + token.QUO_ASSIGN, + token.REM_ASSIGN, + token.AND_ASSIGN, + token.OR_ASSIGN, + token.XOR_ASSIGN, + token.SHL_ASSIGN, + token.SHR_ASSIGN, + token.AND_NOT_ASSIGN, + token.LAND, + token.LOR, + token.ARROW, + token.INC, + token.DEC, + token.EQL, + token.LSS, + token.GTR, + token.ASSIGN, + token.NOT, + token.NEQ, + token.LEQ, + token.GEQ, + token.DEFINE, + token.ELLIPSIS, + token.LPAREN, + token.LBRACK, + token.LBRACE, + token.COMMA, + token.PERIOD, + token.RPAREN, + token.RBRACK, + token.RBRACE, + token.SEMICOLON, + token.COLON, + } + + for _, tok := range tokens { + fmt.Printf("Token: %s (String: %q)\n", tok, tok.String()) + } + + fmt.Println("SUCCESS: Token types work correctly\n") +} + +func testTokenKeywords() { + fmt.Println("\n=== Test Keywords ===") + + keywords := []token.Token{ + token.BREAK, + token.CASE, + token.CHAN, + token.CONST, + token.CONTINUE, + token.DEFAULT, + token.DEFER, + token.ELSE, + token.FALLTHROUGH, + token.FOR, + token.FUNC, + token.GO, + token.GOTO, + token.IF, + token.IMPORT, + token.INTERFACE, + token.MAP, + token.PACKAGE, + token.RANGE, + token.RETURN, + token.SELECT, + token.STRUCT, + token.SWITCH, + token.TYPE, + token.VAR, + } + + for _, kw := range keywords { + if !kw.IsKeyword() { + panic(fmt.Sprintf("Expected %s to be a keyword", kw)) + } + fmt.Printf("Keyword: %s, IsKeyword: %v\n", kw, kw.IsKeyword()) + } + + if token.ADD.IsKeyword() { + panic("Expected ADD operator to not be a keyword") + } + if token.IDENT.IsKeyword() { + panic("Expected IDENT token to not be a keyword") + } + + fmt.Println("SUCCESS: Keyword checks work correctly\n") +} + +func testTokenPrecedence() { + fmt.Println("\n=== Test Token Precedence ===") + + if token.MUL.Precedence() <= token.ADD.Precedence() { + panic("Expected MUL to have higher precedence than ADD") + } + if token.LAND.Precedence() <= token.LOR.Precedence() { + panic("Expected LAND to have higher precedence than LOR") + } + if token.MUL.Precedence() != token.QUO.Precedence() { + panic("Expected MUL and QUO to have same precedence") + } + if token.ADD.Precedence() != token.SUB.Precedence() { + panic("Expected ADD and SUB to have same precedence") + } + + operators := []token.Token{ + token.ADD, + token.SUB, + token.MUL, + token.QUO, + token.REM, + token.LAND, + token.LOR, + token.EQL, + token.LSS, + token.GTR, + } + + for _, op := range operators { + fmt.Printf("Operator: %s, Precedence: %d\n", op, op.Precedence()) + } + + fmt.Println("SUCCESS: Precedence operations work correctly\n") +} + +func testFileSet() { + fmt.Println("\n=== Test FileSet ===") + + fset := token.NewFileSet() + + file1 := fset.AddFile("file1.go", -1, 1000) + file2 := fset.AddFile("file2.go", -1, 2000) + + fmt.Printf("Added file1: %s, Base: %d, Size: %d\n", file1.Name(), file1.Base(), file1.Size()) + fmt.Printf("Added file2: %s, Base: %d, Size: %d\n", file2.Name(), file2.Base(), file2.Size()) + + pos := file1.Pos(100) + retrievedFile := fset.File(pos) + if retrievedFile != file1 { + panic("FileSet.File failed to retrieve correct file") + } + + position := fset.Position(pos) + fmt.Printf("Position at offset 100: %s\n", position) + + fmt.Println("SUCCESS: FileSet operations work correctly\n") +} + +func testFile() { + fmt.Println("\n=== Test File ===") + + fset := token.NewFileSet() + file := fset.AddFile("test.go", -1, 1000) + + if file.Name() != "test.go" { + panic(fmt.Sprintf("Expected file name to be 'test.go', got %q", file.Name())) + } + if file.Size() != 1000 { + panic(fmt.Sprintf("Expected file size to be 1000, got %d", file.Size())) + } + + file.AddLine(0) + file.AddLine(50) + file.AddLine(100) + + if file.LineCount() != 3 { + panic(fmt.Sprintf("Expected line count to be 3, got %d", file.LineCount())) + } + + fmt.Printf("File name: %s\n", file.Name()) + fmt.Printf("File base: %d\n", file.Base()) + fmt.Printf("File size: %d\n", file.Size()) + fmt.Printf("File line count: %d\n", file.LineCount()) + + pos := file.Pos(50) + fmt.Printf("Pos at offset 50: %d\n", pos) + + offset := file.Offset(pos) + if offset != 50 { + panic(fmt.Sprintf("Expected offset to be 50, got %d", offset)) + } + fmt.Printf("Offset of pos: %d\n", offset) + + line := file.Line(pos) + if line != 2 { + panic(fmt.Sprintf("Expected line to be 2, got %d", line)) + } + fmt.Printf("Line number at pos: %d\n", line) + + lineStart := file.LineStart(2) + fmt.Printf("Line 2 starts at pos: %d\n", lineStart) + + position := file.Position(pos) + fmt.Printf("Position: %s\n", position) + + fmt.Println("SUCCESS: File operations work correctly\n") +} + +func testPosition() { + fmt.Println("\n=== Test Position ===") + + pos := token.Position{ + Filename: "test.go", + Offset: 100, + Line: 5, + Column: 10, + } + + if !pos.IsValid() { + panic("Expected valid position to be valid") + } + if pos.Filename != "test.go" { + panic(fmt.Sprintf("Expected filename to be 'test.go', got %q", pos.Filename)) + } + if pos.Line != 5 { + panic(fmt.Sprintf("Expected line to be 5, got %d", pos.Line)) + } + if pos.Column != 10 { + panic(fmt.Sprintf("Expected column to be 10, got %d", pos.Column)) + } + + fmt.Printf("Position: %s\n", pos.String()) + fmt.Printf("Filename: %s, Line: %d, Column: %d, Offset: %d\n", + pos.Filename, pos.Line, pos.Column, pos.Offset) + fmt.Printf("IsValid: %v\n", pos.IsValid()) + + invalidPos := token.Position{} + if invalidPos.IsValid() { + panic("Expected empty position to be invalid") + } + fmt.Printf("Invalid position IsValid: %v\n", invalidPos.IsValid()) + + fmt.Println("SUCCESS: Position operations work correctly\n") +} + +func testUtilityFunctions() { + fmt.Println("\n=== Test Utility Functions ===") + + fmt.Printf("IsExported(\"Foo\"): %v\n", token.IsExported("Foo")) + fmt.Printf("IsExported(\"foo\"): %v\n", token.IsExported("foo")) + fmt.Printf("IsExported(\"_foo\"): %v\n", token.IsExported("_foo")) + + fmt.Printf("IsIdentifier(\"foo\"): %v\n", token.IsIdentifier("foo")) + fmt.Printf("IsIdentifier(\"foo123\"): %v\n", token.IsIdentifier("foo123")) + fmt.Printf("IsIdentifier(\"123foo\"): %v\n", token.IsIdentifier("123foo")) + fmt.Printf("IsIdentifier(\"foo-bar\"): %v\n", token.IsIdentifier("foo-bar")) + + fmt.Printf("IsKeyword(\"func\"): %v\n", token.IsKeyword("func")) + fmt.Printf("IsKeyword(\"if\"): %v\n", token.IsKeyword("if")) + fmt.Printf("IsKeyword(\"foo\"): %v\n", token.IsKeyword("foo")) + + lookupFunc := token.Lookup("func") + fmt.Printf("Lookup(\"func\"): %s\n", lookupFunc) + + lookupIdent := token.Lookup("myVar") + fmt.Printf("Lookup(\"myVar\"): %s\n", lookupIdent) + + lookupFor := token.Lookup("for") + fmt.Printf("Lookup(\"for\"): %s\n", lookupFor) + + fmt.Println("SUCCESS: Utility functions work correctly\n") +} diff --git a/_demo/go/gotypes/main.go b/_demo/go/gotypes/main.go new file mode 100644 index 0000000000..f4ca41bae4 --- /dev/null +++ b/_demo/go/gotypes/main.go @@ -0,0 +1,488 @@ +package main + +import ( + "fmt" + "go/token" + "go/types" +) + +func main() { + testBasicTypes() + testObjects() + testScope() + testPackage() + testNamed() + testInterface() + testStruct() + testSignature() + testTuple() + testArray() + testSlice() + testPointer() + testMap() + testChan() + testTypeComparison() + testTypeChecking() + testStringFunctions() + testLookupFunctions() + testUtilityFunctions() +} + +func testBasicTypes() { + fmt.Println("=== Test Basic Types ===") + + intType := types.Typ[types.Int] + fmt.Printf("Int type: %v, Kind: %v\n", intType, intType.Kind()) + if intType.Kind() != types.Int { + panic(fmt.Sprintf("Int type kind mismatch: expected %v, got %v", types.Int, intType.Kind())) + } + + stringType := types.Typ[types.String] + fmt.Printf("String type: %v, Kind: %v\n", stringType, stringType.Kind()) + if stringType.Kind() != types.String { + panic(fmt.Sprintf("String type kind mismatch: expected %v, got %v", types.String, stringType.Kind())) + } + + boolType := types.Typ[types.Bool] + fmt.Printf("Bool type: %v, Kind: %v\n", boolType, boolType.Kind()) + if boolType.Kind() != types.Bool { + panic(fmt.Sprintf("Bool type kind mismatch: expected %v, got %v", types.Bool, boolType.Kind())) + } + + float64Type := types.Typ[types.Float64] + fmt.Printf("Float64 type: %v, Kind: %v\n", float64Type, float64Type.Kind()) + if float64Type.Kind() != types.Float64 { + panic(fmt.Sprintf("Float64 type kind mismatch: expected %v, got %v", types.Float64, float64Type.Kind())) + } + + fmt.Println("SUCCESS: Basic types work correctly\n") +} + +func testObjects() { + fmt.Println("\n=== Test Objects (Var, Const, Func, TypeName) ===") + + varObj := types.NewVar(token.NoPos, nil, "x", types.Typ[types.Int]) + fmt.Printf("Var: Name=%s, Type=%v\n", varObj.Name(), varObj.Type()) + if varObj.Name() != "x" { + panic(fmt.Sprintf("Var name mismatch: expected x, got %s", varObj.Name())) + } + if varObj.Type() != types.Typ[types.Int] { + panic(fmt.Sprintf("Var type mismatch: expected int, got %v", varObj.Type())) + } + + constObj := types.NewConst(token.NoPos, nil, "pi", types.Typ[types.Float64], nil) + fmt.Printf("Const: Name=%s, Type=%v\n", constObj.Name(), constObj.Type()) + if constObj.Name() != "pi" { + panic(fmt.Sprintf("Const name mismatch: expected pi, got %s", constObj.Name())) + } + if constObj.Type() != types.Typ[types.Float64] { + panic(fmt.Sprintf("Const type mismatch: expected float64, got %v", constObj.Type())) + } + + sig := types.NewSignatureType(nil, nil, nil, nil, nil, false) + funcObj := types.NewFunc(token.NoPos, nil, "foo", sig) + fmt.Printf("Func: Name=%s, Type=%v\n", funcObj.Name(), funcObj.Type()) + if funcObj.Name() != "foo" { + panic(fmt.Sprintf("Func name mismatch: expected foo, got %s", funcObj.Name())) + } + + typeObj := types.NewTypeName(token.NoPos, nil, "MyInt", types.Typ[types.Int]) + fmt.Printf("TypeName: Name=%s, Type=%v\n", typeObj.Name(), typeObj.Type()) + if typeObj.Name() != "MyInt" { + panic(fmt.Sprintf("TypeName name mismatch: expected MyInt, got %s", typeObj.Name())) + } + + var obj types.Object = varObj + if obj.Name() != "x" { + panic("Object interface conversion failed") + } + fmt.Println("SUCCESS: Object interface works correctly\n") +} + +func testScope() { + fmt.Println("\n=== Test Scope ===") + + scope := types.NewScope(nil, 0, 0, "test") + obj := types.NewVar(0, nil, "x", types.Typ[types.Int]) + + scope.Insert(obj) + result := scope.Lookup("x") + if result != obj { + panic("Scope.Lookup failed") + } + + names := scope.Names() + if len(names) != 1 || names[0] != "x" { + panic("Scope.Names failed") + } + + num := scope.Len() + if num != 1 { + panic("Scope.Len failed") + } + + fmt.Printf("Scope contains %d object(s): %v\n", num, names) + fmt.Println("SUCCESS: Scope operations work correctly\n") +} + +func testPackage() { + fmt.Println("\n=== Test Package ===") + + pkg := types.NewPackage("example.com/test", "test") + fmt.Printf("Package: Path=%s, Name=%s\n", pkg.Path(), pkg.Name()) + if pkg.Path() != "example.com/test" { + panic(fmt.Sprintf("Package path mismatch: expected example.com/test, got %s", pkg.Path())) + } + if pkg.Name() != "test" { + panic(fmt.Sprintf("Package name mismatch: expected test, got %s", pkg.Name())) + } + + scope := pkg.Scope() + if scope == nil { + panic("Package.Scope returned nil") + } + + varObj := types.NewVar(token.NoPos, pkg, "x", types.Typ[types.Int]) + scope.Insert(varObj) + + result := pkg.Scope().Lookup("x") + if result != varObj { + panic("Package scope lookup failed") + } + + fmt.Println("SUCCESS: Package operations work correctly\n") +} + +func testNamed() { + fmt.Println("\n=== Test Named Types ===") + + pkg := types.NewPackage("example.com/test", "test") + typeName := types.NewTypeName(token.NoPos, pkg, "MyInt", nil) + named := types.NewNamed(typeName, types.Typ[types.Int], nil) + + fmt.Printf("Named type: %v, Underlying: %v\n", named, named.Underlying()) + + if named.Obj() != typeName { + panic("Named.Obj failed") + } + + fmt.Println("SUCCESS: Named type operations work correctly\n") +} + +func testInterface() { + fmt.Println("\n=== Test Interface ===") + + pkg := types.NewPackage("example.com/test", "test") + + posMethod := types.NewFunc(token.NoPos, pkg, "Pos", types.NewSignatureType(nil, nil, nil, nil, types.NewTuple(types.NewVar(0, pkg, "", types.Typ[types.Int])), false)) + endMethod := types.NewFunc(token.NoPos, pkg, "End", types.NewSignatureType(nil, nil, nil, nil, types.NewTuple(types.NewVar(0, pkg, "", types.Typ[types.Int])), false)) + + methods := []*types.Func{posMethod, endMethod} + iface := types.NewInterfaceType(methods, nil) + iface.Complete() + + fmt.Printf("Interface with %d methods\n", iface.NumMethods()) + if iface.NumMethods() != 2 { + panic(fmt.Sprintf("Interface method count mismatch: expected 2, got %d", iface.NumMethods())) + } + + method := iface.Method(0) + fmt.Printf("Method 0: %s\n", method.Name()) + if method.Name() != "End" && method.Name() != "Pos" { + panic(fmt.Sprintf("Unexpected method name: %s", method.Name())) + } + + fmt.Println("SUCCESS: Interface operations work correctly\n") +} + +func testStruct() { + fmt.Println("\n=== Test Struct ===") + + fields := []*types.Var{ + types.NewField(token.NoPos, nil, "X", types.Typ[types.Int], false), + types.NewField(token.NoPos, nil, "Y", types.Typ[types.String], false), + } + + structType := types.NewStruct(fields, nil) + fmt.Printf("Struct with %d fields\n", structType.NumFields()) + if structType.NumFields() != 2 { + panic(fmt.Sprintf("Struct field count mismatch: expected 2, got %d", structType.NumFields())) + } + + field0 := structType.Field(0) + fmt.Printf("Field 0: Name=%s, Type=%v\n", field0.Name(), field0.Type()) + if field0.Name() != "X" { + panic(fmt.Sprintf("Field 0 name mismatch: expected X, got %s", field0.Name())) + } + if field0.Type() != types.Typ[types.Int] { + panic(fmt.Sprintf("Field 0 type mismatch: expected int, got %v", field0.Type())) + } + + fmt.Println("SUCCESS: Struct operations work correctly\n") +} + +func testSignature() { + fmt.Println("\n=== Test Signature ===") + + params := types.NewTuple( + types.NewVar(token.NoPos, nil, "x", types.Typ[types.Int]), + types.NewVar(token.NoPos, nil, "y", types.Typ[types.String]), + ) + + results := types.NewTuple( + types.NewVar(token.NoPos, nil, "", types.Typ[types.Bool]), + ) + + sig := types.NewSignatureType(nil, nil, nil, params, results, false) + + fmt.Printf("Signature: %d params, %d results\n", sig.Params().Len(), sig.Results().Len()) + if sig.Params().Len() != 2 { + panic(fmt.Sprintf("Signature param count mismatch: expected 2, got %d", sig.Params().Len())) + } + if sig.Results().Len() != 1 { + panic(fmt.Sprintf("Signature result count mismatch: expected 1, got %d", sig.Results().Len())) + } + + param0 := sig.Params().At(0) + fmt.Printf("Param 0: Name=%s, Type=%v\n", param0.Name(), param0.Type()) + if param0.Name() != "x" { + panic(fmt.Sprintf("Param 0 name mismatch: expected x, got %s", param0.Name())) + } + if param0.Type() != types.Typ[types.Int] { + panic(fmt.Sprintf("Param 0 type mismatch: expected int, got %v", param0.Type())) + } + + fmt.Println("SUCCESS: Signature operations work correctly\n") +} + +func testTuple() { + fmt.Println("\n=== Test Tuple ===") + + tuple := types.NewTuple( + types.NewVar(token.NoPos, nil, "a", types.Typ[types.Int]), + types.NewVar(token.NoPos, nil, "b", types.Typ[types.String]), + ) + + fmt.Printf("Tuple length: %d\n", tuple.Len()) + if tuple.Len() != 2 { + panic(fmt.Sprintf("Tuple length mismatch: expected 2, got %d", tuple.Len())) + } + + var0 := tuple.At(0) + fmt.Printf("Element 0: Name=%s, Type=%v\n", var0.Name(), var0.Type()) + if var0.Name() != "a" { + panic(fmt.Sprintf("Tuple element 0 name mismatch: expected a, got %s", var0.Name())) + } + if var0.Type() != types.Typ[types.Int] { + panic(fmt.Sprintf("Tuple element 0 type mismatch: expected int, got %v", var0.Type())) + } + + fmt.Println("SUCCESS: Tuple operations work correctly\n") +} + +func testArray() { + fmt.Println("\n=== Test Array ===") + + arrayType := types.NewArray(types.Typ[types.Int], 10) + fmt.Printf("Array type: %v, Elem: %v, Len: %d\n", arrayType, arrayType.Elem(), arrayType.Len()) + if arrayType.Len() != 10 { + panic(fmt.Sprintf("Array length mismatch: expected 10, got %d", arrayType.Len())) + } + if arrayType.Elem() != types.Typ[types.Int] { + panic(fmt.Sprintf("Array element type mismatch: expected int, got %v", arrayType.Elem())) + } + + fmt.Println("SUCCESS: Array operations work correctly\n") +} + +func testSlice() { + fmt.Println("\n=== Test Slice ===") + + sliceType := types.NewSlice(types.Typ[types.String]) + fmt.Printf("Slice type: %v, Elem: %v\n", sliceType, sliceType.Elem()) + if sliceType.Elem() != types.Typ[types.String] { + panic(fmt.Sprintf("Slice element type mismatch: expected string, got %v", sliceType.Elem())) + } + + fmt.Println("SUCCESS: Slice operations work correctly\n") +} + +func testPointer() { + fmt.Println("\n=== Test Pointer ===") + + ptrType := types.NewPointer(types.Typ[types.Int]) + fmt.Printf("Pointer type: %v, Elem: %v\n", ptrType, ptrType.Elem()) + if ptrType.Elem() != types.Typ[types.Int] { + panic(fmt.Sprintf("Pointer element type mismatch: expected int, got %v", ptrType.Elem())) + } + + fmt.Println("SUCCESS: Pointer operations work correctly\n") +} + +func testMap() { + fmt.Println("\n=== Test Map ===") + + mapType := types.NewMap(types.Typ[types.String], types.Typ[types.Int]) + fmt.Printf("Map type: %v, Key: %v, Elem: %v\n", mapType, mapType.Key(), mapType.Elem()) + if mapType.Key() != types.Typ[types.String] { + panic(fmt.Sprintf("Map key type mismatch: expected string, got %v", mapType.Key())) + } + if mapType.Elem() != types.Typ[types.Int] { + panic(fmt.Sprintf("Map element type mismatch: expected int, got %v", mapType.Elem())) + } + + fmt.Println("SUCCESS: Map operations work correctly\n") +} + +func testChan() { + fmt.Println("\n=== Test Chan ===") + + chanType := types.NewChan(types.SendRecv, types.Typ[types.Int]) + fmt.Printf("Chan type: %v, Dir: %v, Elem: %v\n", chanType, chanType.Dir(), chanType.Elem()) + if chanType.Dir() != types.SendRecv { + panic(fmt.Sprintf("Chan direction mismatch: expected SendRecv, got %v", chanType.Dir())) + } + if chanType.Elem() != types.Typ[types.Int] { + panic(fmt.Sprintf("Chan element type mismatch: expected int, got %v", chanType.Elem())) + } + + sendChan := types.NewChan(types.SendOnly, types.Typ[types.String]) + fmt.Printf("SendOnly chan: %v, Dir: %v\n", sendChan, sendChan.Dir()) + if sendChan.Dir() != types.SendOnly { + panic(fmt.Sprintf("SendOnly chan direction mismatch: expected SendOnly, got %v", sendChan.Dir())) + } + + recvChan := types.NewChan(types.RecvOnly, types.Typ[types.Bool]) + fmt.Printf("RecvOnly chan: %v, Dir: %v\n", recvChan, recvChan.Dir()) + if recvChan.Dir() != types.RecvOnly { + panic(fmt.Sprintf("RecvOnly chan direction mismatch: expected RecvOnly, got %v", recvChan.Dir())) + } + + fmt.Println("SUCCESS: Chan operations work correctly\n") +} + +func testTypeComparison() { + fmt.Println("\n=== Test Type Comparison Functions ===") + + t1 := types.Typ[types.Int] + t2 := types.Typ[types.Int] + t3 := types.Typ[types.String] + + if !types.Identical(t1, t2) { + panic("Identical failed: int should be identical to int") + } + fmt.Printf("Identical(int, int): %v\n", types.Identical(t1, t2)) + fmt.Printf("Identical(int, string): %v\n", types.Identical(t1, t3)) + + if !types.AssignableTo(t1, t2) { + panic("AssignableTo failed") + } + fmt.Printf("AssignableTo(int, int): %v\n", types.AssignableTo(t1, t2)) + fmt.Printf("AssignableTo(int, string): %v\n", types.AssignableTo(t1, t3)) + + fmt.Printf("Comparable(int): %v\n", types.Comparable(t1)) + fmt.Printf("Comparable(string): %v\n", types.Comparable(t3)) + + fmt.Printf("ConvertibleTo(int, int): %v\n", types.ConvertibleTo(t1, t2)) + + fmt.Println("SUCCESS: Type comparison functions work correctly\n") +} + +func testTypeChecking() { + fmt.Println("\n=== Test Type Checking Functions ===") + + pkg := types.NewPackage("example.com/test", "test") + + m1 := types.NewFunc(token.NoPos, pkg, "Method1", types.NewSignatureType(nil, nil, nil, nil, nil, false)) + m2 := types.NewFunc(token.NoPos, pkg, "Method2", types.NewSignatureType(nil, nil, nil, nil, nil, false)) + iface := types.NewInterfaceType([]*types.Func{m1, m2}, nil) + iface.Complete() + + fields := []*types.Var{ + types.NewField(token.NoPos, nil, "x", types.Typ[types.Int], false), + } + structType := types.NewStruct(fields, nil) + + fmt.Printf("Implements(struct, interface): %v\n", types.Implements(structType, iface)) + fmt.Printf("Implements(int, interface): %v\n", types.Implements(types.Typ[types.Int], iface)) + + emptyIface := types.NewInterfaceType(nil, nil) + emptyIface.Complete() + fmt.Printf("Implements(int, empty interface): %v\n", types.Implements(types.Typ[types.Int], emptyIface)) + + fmt.Printf("AssertableTo(interface, int): %v\n", types.AssertableTo(iface, types.Typ[types.Int])) + + fmt.Println("SUCCESS: Type checking functions work correctly\n") +} + +func testStringFunctions() { + fmt.Println("\n=== Test String Functions ===") + + pkg := types.NewPackage("example.com/test", "test") + varObj := types.NewVar(token.NoPos, pkg, "myVar", types.Typ[types.Int]) + + objStr := types.ObjectString(varObj, nil) + fmt.Printf("ObjectString: %s\n", objStr) + + objStrQual := types.ObjectString(varObj, types.RelativeTo(pkg)) + fmt.Printf("ObjectString (qualified): %s\n", objStrQual) + + typeStr := types.TypeString(types.Typ[types.Int], nil) + fmt.Printf("TypeString(int): %s\n", typeStr) + + sliceType := types.NewSlice(types.Typ[types.String]) + sliceStr := types.TypeString(sliceType, nil) + fmt.Printf("TypeString([]string): %s\n", sliceStr) + + fmt.Println("SUCCESS: String functions work correctly\n") +} + +func testLookupFunctions() { + fmt.Println("\n=== Test Lookup Functions ===") + + pkg := types.NewPackage("example.com/test", "test") + + fields := []*types.Var{ + types.NewField(token.NoPos, pkg, "X", types.Typ[types.Int], false), + types.NewField(token.NoPos, pkg, "Y", types.Typ[types.String], false), + } + structType := types.NewStruct(fields, nil) + + obj, index, indirect := types.LookupFieldOrMethod(structType, false, pkg, "X") + if obj == nil { + panic("LookupFieldOrMethod failed to find X") + } + fmt.Printf("LookupFieldOrMethod found: %s, index: %v, indirect: %v\n", obj.Name(), index, indirect) + + obj2, index2, indirect2 := types.LookupFieldOrMethod(structType, false, pkg, "NonExistent") + fmt.Printf("LookupFieldOrMethod (non-existent): found=%v, index=%v, indirect=%v\n", obj2 != nil, index2, indirect2) + + mset := types.NewMethodSet(structType) + fmt.Printf("NewMethodSet: %d methods\n", mset.Len()) + + fmt.Println("SUCCESS: Lookup functions work correctly\n") +} + +func testUtilityFunctions() { + fmt.Println("\n=== Test Utility Functions ===") + + pkg := types.NewPackage("example.com/test", "test") + + iface := types.NewInterfaceType(nil, nil) + iface.Complete() + + fmt.Printf("IsInterface(interface): %v\n", types.IsInterface(iface)) + fmt.Printf("IsInterface(int): %v\n", types.IsInterface(types.Typ[types.Int])) + + typedNil := types.Typ[types.UntypedNil] + defaultType := types.Default(typedNil) + fmt.Printf("Default(UntypedNil): %v\n", defaultType) + + intDefault := types.Default(types.Typ[types.Int]) + fmt.Printf("Default(int): %v\n", intDefault) + + idStr := types.Id(pkg, "MyType") + fmt.Printf("Id(pkg, \"MyType\"): %s\n", idStr) + + fmt.Println("SUCCESS: Utility functions work correctly\n") +} diff --git a/_demo/ifaceconv/main.go b/_demo/go/ifaceconv/main.go similarity index 100% rename from _demo/ifaceconv/main.go rename to _demo/go/ifaceconv/main.go diff --git a/_demo/go/ifaceprom-1559/foo/foo.go b/_demo/go/ifaceprom-1559/foo/foo.go new file mode 100644 index 0000000000..59fad6538c --- /dev/null +++ b/_demo/go/ifaceprom-1559/foo/foo.go @@ -0,0 +1,14 @@ +package foo + +type Gamer interface { + initGame() + Load() +} + +type Game struct{} + +func (g *Game) initGame() {} + +func (g *Game) Load() { + println("load") +} diff --git a/_demo/go/ifaceprom-1559/main.go b/_demo/go/ifaceprom-1559/main.go new file mode 100644 index 0000000000..3adb9d327a --- /dev/null +++ b/_demo/go/ifaceprom-1559/main.go @@ -0,0 +1,25 @@ +package main + +import "github.com/xgo-dev/llgo/_demo/go/ifaceprom-1559/foo" + +type Game1 struct { + *foo.Game +} + +type Game2 struct { +} + +func (p *Game2) initGame() { +} + +func main() { + var g1 any = &Game1{&foo.Game{}} + var g2 any = &Game2{} + v1, ok := g1.(foo.Gamer) + println("OK", v1, ok) + if ok { + v1.Load() + } + v2, ok := g2.(foo.Gamer) + println("FAIL", v2, ok) +} diff --git a/_demo/go/issue1538-floatcvtuint-over/main.go b/_demo/go/issue1538-floatcvtuint-over/main.go new file mode 100644 index 0000000000..eb903d9a6f --- /dev/null +++ b/_demo/go/issue1538-floatcvtuint-over/main.go @@ -0,0 +1,21 @@ +package main + +func main() { + cvt32Fto8U(-1, 255) + cvt32Fto32U(4294967295.1, 0) + cvt32Fto32U(5294967295.1, 1000000000) + cvt32Fto32U(-1294967295.1, 3000000000) + cvt32Fto32U(-1.1, 4294967295) +} + +func cvt32Fto8U(a float32, b uint8) { + if uint8(a) != b { + panic("error") + } +} + +func cvt32Fto32U(a float32, b uint32) { + if uint32(a) != b { + panic("error") + } +} diff --git a/_demo/go/issue1538/main.go b/_demo/go/issue1538/main.go new file mode 100644 index 0000000000..c1c0e654a4 --- /dev/null +++ b/_demo/go/issue1538/main.go @@ -0,0 +1,153 @@ +package main + +func main() { + cvt64to8(0, 0) + cvt64to8(127, 127) + cvt64to8(128, -128) + cvt64to8(-128, -128) + cvt64to8(-129, 127) + cvt64to8(256, 0) + + cvt64to8U(0, 0) + cvt64to8U(255, 255) + cvt64to8U(256, 0) + cvt64to8U(257, 1) + cvt64to8U(-1, 255) + + cvt32Fto8(0.1, 0) + cvt32Fto8(127.1, 127) + cvt32Fto8(128.1, -128) + cvt32Fto8(-128.1, -128) + cvt32Fto8(-129.1, 127) + cvt32Fto8(256.1, 0) + + cvt32Fto8U(0, 0) + cvt32Fto8U(255, 255) + cvt32Fto8U(256, 0) + cvt32Fto8U(257, 1) + cvt32Fto8U(-1, 255) + + // MaxInt32 = 1<<31 - 1 // 2147483647 + // MinInt32 = -1 << 31 // -2147483648 + cvt32Fto32(0, 0) + cvt32Fto32(1.5, 1) + cvt32Fto32(1147483647.1, 1147483648) + cvt32Fto32(-2147483648.1, -2147483648) + + // MaxUint32 = 1<<32 - 1 // 4294967295 + cvt32Fto32U(0, 0) + cvt32Fto32U(1.5, 1) + cvt32Fto32U(4294967295.1, 0) + cvt32Fto32U(5294967295.1, 1000000000) + cvt32Fto32U(-4294967295.1, 0) + cvt32Fto32U(-1294967295.1, 3000000000) + cvt32Fto32U(-1.1, 4294967295) + + // MaxFloat32 = 0x1p127 * (1 + (1 - 0x1p-23)) + // SmallestNonzeroFloat32 = 0x1p-126 * 0x1p-23 + // MaxFloat64 = 0x1p1023 * (1 + (1 - 0x1p-52)) + // SmallestNonzeroFloat64 = 0x1p-1022 * 0x1p-52 + + cvt32Fto64F(0, 0) + cvt32Fto64F(1.5, 1.5) + cvt32Fto64F(1e10, 1e10) + cvt32Fto64F(-1e10, -1e10) + + cvt64Fto32F(0, 0) + cvt64Fto32F(1.5, 1.5) + cvt64Fto32F(1e10, 1e10) + cvt64Fto32F(-1e10, -1e10) + + // MaxInt64 = 1<<63 - 1 // 9223372036854775807 + // MinInt64 = -1 << 63 // -9223372036854775808 + cvt64to64F(0, 0) + cvt64to64F(1e10, 1e10) + cvt64to64F(9223372036854775807, 9223372036854775807) + cvt64to64F(-9223372036854775807, -9223372036854775807) + + // MaxUint64 = 1<<64 - 1 // 18446744073709551615 + cvt64Uto64F(0, 0) + cvt64Uto64F(1e10, 1e10) + cvt64Uto64F(9223372036854775807, 9223372036854775807) + cvt64Uto64F(18446744073709551615, 18446744073709551615) + + cvt32to64(0, 0) + cvt32to64(2147483647, 2147483647) + + cvtUinptr(1024, 1024) +} + +func cvtUinptr(a int32, b uintptr) { + if uintptr(a) != b { + panic("error") + } + if int32(b) != a { + panic("error") + } +} + +func cvt32to64(a int32, b int64) { + if int64(a) != b { + panic("error") + } +} + +func cvt64to64F(a int64, b float64) { + if float64(a) != b { + panic("error") + } +} + +func cvt64Uto64F(a uint64, b float64) { + if float64(a) != b { + panic("error") + } +} + +func cvt64Fto32F(a float64, b float32) { + if float32(a) != b { + panic("error") + } +} + +func cvt32Fto64F(a float32, b float64) { + if float64(a) != b { + panic("error") + } +} + +func cvt32Fto32(a float32, b int32) { + if int32(a) != b { + panic("error") + } +} + +func cvt32Fto32U(a float32, b uint32) { + if uint32(a) != b { + panic("error") + } +} + +func cvt32Fto8(a float32, b int8) { + if int8(a) != b { + panic("error") + } +} + +func cvt32Fto8U(a float32, b uint8) { + if uint8(a) != b { + panic("error") + } +} + +func cvt64to8(a int64, b int8) { + if int8(a) != b { + panic("error") + } +} + +func cvt64to8U(a int, b uint8) { + if uint8(a) != b { + panic("error") + } +} diff --git a/_demo/go/linkname/link.go b/_demo/go/linkname/link.go new file mode 100644 index 0000000000..bed6c03dcc --- /dev/null +++ b/_demo/go/linkname/link.go @@ -0,0 +1,11 @@ +package main + +func crossFile() + +func crossFileImpl() { + println("cross-file") +} + +func afterImpl() { + println("after") +} diff --git a/_demo/go/linkname/main.go b/_demo/go/linkname/main.go new file mode 100644 index 0000000000..bfd856aace --- /dev/null +++ b/_demo/go/linkname/main.go @@ -0,0 +1,22 @@ +package main + +import _ "unsafe" + +func main() { + beforeAlias() + after() + crossFile() +} + +func before() { + println("before") +} + +//go:linkname beforeAlias main.before +func beforeAlias() + +func after() + +//go:linkname after main.afterImpl + +//go:linkname crossFile main.crossFileImpl diff --git a/_demo/logdemo/log.go b/_demo/go/logdemo/log.go similarity index 100% rename from _demo/logdemo/log.go rename to _demo/go/logdemo/log.go diff --git a/_demo/go/mainlink/main.go b/_demo/go/mainlink/main.go new file mode 100644 index 0000000000..5be31d544b --- /dev/null +++ b/_demo/go/mainlink/main.go @@ -0,0 +1,14 @@ +package main + +import _ "unsafe" + +func main() { + linkdemo() +} + +func demo() { + println("demo") +} + +//go:linkname linkdemo main.demo +func linkdemo() diff --git a/_demo/go/mapclosure/main.go b/_demo/go/mapclosure/main.go new file mode 100644 index 0000000000..085a2f7cb2 --- /dev/null +++ b/_demo/go/mapclosure/main.go @@ -0,0 +1,33 @@ +package main + +type Type interface { + String() string +} + +func demo(t Type) string { + return t.String() +} + +type typ struct { + s string +} + +func (t *typ) String() string { + return t.s +} + +var ( + op = map[string]func(Type) string{ + "demo": demo, + } + list = []func(Type) string{demo} +) + +func main() { + t := &typ{"hello"} + fn1 := op["demo"] + fn2 := list[0] + if fn1(t) != fn2(t) { + panic("error") + } +} diff --git a/_demo/go/maphash/maphash.go b/_demo/go/maphash/maphash.go new file mode 100644 index 0000000000..a8ce49cfb1 --- /dev/null +++ b/_demo/go/maphash/maphash.go @@ -0,0 +1,124 @@ +package main + +import ( + "fmt" + "hash/maphash" +) + +func main() { + testHashBasics() + testMakeSeed() + testSetSeed() + testWriteMethods() + testBytes() + testString() +} + +func testHashBasics() { + fmt.Println("=== Test Hash Basics ===") + var h maphash.Hash + n, err := h.WriteString("hello") + if err != nil { + panic(fmt.Sprintf("WriteString failed: %v", err)) + } + if n != 5 { + panic(fmt.Sprintf("WriteString returned %d, expected 5", n)) + } + hash1 := h.Sum64() + fmt.Printf("Hash of 'hello': 0x%x\n", hash1) + + h.Reset() + n, err = h.WriteString("world") + if err != nil { + panic(fmt.Sprintf("WriteString failed: %v", err)) + } + hash2 := h.Sum64() + fmt.Printf("Hash of 'world': 0x%x\n", hash2) + + h.Reset() + n, err = h.WriteString("hello") + if err != nil { + panic(fmt.Sprintf("WriteString failed: %v", err)) + } + hash3 := h.Sum64() + if hash1 != hash3 { + panic(fmt.Sprintf("Hash mismatch: 0x%x != 0x%x", hash1, hash3)) + } + fmt.Printf("Hash consistency verified: 0x%x == 0x%x\n", hash1, hash3) +} + +func testMakeSeed() { + fmt.Println("\n=== Test MakeSeed ===") + seed1 := maphash.MakeSeed() + seed2 := maphash.MakeSeed() + fmt.Printf("Seed 1: %v\n", seed1) + fmt.Printf("Seed 2: %v\n", seed2) + if seed1 == seed2 { + fmt.Println("Warning: Seeds are identical (rare but possible)") + } +} + +func testSetSeed() { + fmt.Println("\n=== Test SetSeed ===") + var h1, h2 maphash.Hash + seed := maphash.MakeSeed() + + h1.SetSeed(seed) + _, err := h1.WriteString("test") + if err != nil { + panic(fmt.Sprintf("WriteString failed: %v", err)) + } + hash1 := h1.Sum64() + + h2.SetSeed(seed) + _, err = h2.WriteString("test") + if err != nil { + panic(fmt.Sprintf("WriteString failed: %v", err)) + } + hash2 := h2.Sum64() + + if hash1 != hash2 { + panic(fmt.Sprintf("Hashes with same seed should match: 0x%x != 0x%x", hash1, hash2)) + } + fmt.Printf("Same seed produces same hash: 0x%x == 0x%x\n", hash1, hash2) +} + +func testWriteMethods() { + fmt.Println("\n=== Test Write Methods ===") + var h maphash.Hash + + data := []byte("hello") + n, err := h.Write(data) + if err != nil { + panic(fmt.Sprintf("Write failed: %v", err)) + } + if n != len(data) { + panic(fmt.Sprintf("Write returned %d, expected %d", n, len(data))) + } + hash1 := h.Sum64() + fmt.Printf("Hash after Write: 0x%x\n", hash1) + + h.Reset() + err = h.WriteByte('A') + if err != nil { + panic(fmt.Sprintf("WriteByte failed: %v", err)) + } + hash2 := h.Sum64() + fmt.Printf("Hash after WriteByte('A'): 0x%x\n", hash2) +} + +func testBytes() { + fmt.Println("\n=== Test Bytes Function ===") + seed := maphash.MakeSeed() + data := []byte("test data") + hash := maphash.Bytes(seed, data) + fmt.Printf("Bytes hash: 0x%x\n", hash) +} + +func testString() { + fmt.Println("\n=== Test String Function ===") + seed := maphash.MakeSeed() + str := "test string" + hash := maphash.String(seed, str) + fmt.Printf("String hash: 0x%x\n", hash) +} diff --git a/_demo/math/math.go b/_demo/go/math/math.go similarity index 100% rename from _demo/math/math.go rename to _demo/go/math/math.go diff --git a/_demo/mimeheader/mimeheader.go b/_demo/go/mimeheader/mimeheader.go similarity index 100% rename from _demo/mimeheader/mimeheader.go rename to _demo/go/mimeheader/mimeheader.go diff --git a/_demo/mkdirdemo/mkdir.go b/_demo/go/mkdirdemo/mkdir.go similarity index 100% rename from _demo/mkdirdemo/mkdir.go rename to _demo/go/mkdirdemo/mkdir.go diff --git a/_demo/go/netip/main.go b/_demo/go/netip/main.go new file mode 100644 index 0000000000..ccaaf6ed60 --- /dev/null +++ b/_demo/go/netip/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" + "net/netip" +) + +func main() { + s := netip.MustParseAddrPort("127.0.0.1:80") + fmt.Println(s, s.Port()) +} diff --git a/_demo/go/osfile/demo.go b/_demo/go/osfile/demo.go new file mode 100644 index 0000000000..21248c65cc --- /dev/null +++ b/_demo/go/osfile/demo.go @@ -0,0 +1,126 @@ +package main + +import ( + "os" +) + +func main() { + // Test file operations + testFile := "test_file.txt" + + // Clean up at the end + defer os.Remove(testFile) + + // Test Write and WriteString + f, err := os.Create(testFile) + if err != nil { + panic("Create failed: " + err.Error()) + } + + // Test Write + data := []byte("Hello, World!\n") + n, err := f.Write(data) + if err != nil || n != len(data) { + panic("Write failed") + } + + // Test WriteString + n, err = f.WriteString("Test WriteString\n") + if err != nil || n != 17 { + panic("WriteString failed") + } + + f.Close() + + // Test ReadAt + f, err = os.Open(testFile) + if err != nil { + panic("Open failed: " + err.Error()) + } + + buf := make([]byte, 5) + n, err = f.ReadAt(buf, 0) + if err != nil || n != 5 || string(buf) != "Hello" { + panic("ReadAt failed: expected 'Hello'") + } + + n, err = f.ReadAt(buf, 7) + if err != nil || n != 5 || string(buf) != "World" { + panic("ReadAt failed: expected 'World'") + } + + f.Close() + + // Test WriteAt with offset 0 + f, err = os.OpenFile(testFile, os.O_RDWR, 0644) + if err != nil { + panic("OpenFile failed: " + err.Error()) + } + + n, err = f.WriteAt([]byte("XXXXX"), 0) + if err != nil || n != 5 { + panic("WriteAt at offset 0 failed") + } + + // Test WriteAt with non-zero offset + n, err = f.WriteAt([]byte("YYYYY"), 7) + if err != nil || n != 5 { + panic("WriteAt at offset 7 failed") + } + + f.Close() + + // Verify WriteAt results + f, err = os.Open(testFile) + if err != nil { + panic("Open failed: " + err.Error()) + } + + buf = make([]byte, 5) + n, err = f.ReadAt(buf, 0) + if err != nil || n != 5 || string(buf) != "XXXXX" { + panic("WriteAt verification at offset 0 failed: expected 'XXXXX'") + } + + buf = make([]byte, 5) + n, err = f.ReadAt(buf, 7) + if err != nil || n != 5 || string(buf) != "YYYYY" { + panic("WriteAt verification at offset 7 failed: expected 'YYYYY'") + } + + f.Close() + + // Test Seek + f, err = os.Open(testFile) + if err != nil { + panic("Open failed: " + err.Error()) + } + + // Seek to position 7 + pos, err := f.Seek(7, 0) // SEEK_SET = 0 + if err != nil || pos != 7 { + panic("Seek failed") + } + + buf = make([]byte, 5) + n, err = f.Read(buf) + if err != nil || n != 5 || string(buf) != "YYYYY" { + panic("Seek test failed: expected 'YYYYY'") + } + + // Seek from current position + pos, err = f.Seek(2, 1) // SEEK_CUR = 1 + if err != nil || pos != 14 { + panic("Seek from current failed") + } + + // Seek from end + pos, err = f.Seek(-5, 2) // SEEK_END = 2 + if err != nil { + panic("Seek from end failed") + } + + f.Close() + + println("All os.File tests passed!") +} diff --git a/_demo/oslookpath/lookpath.go b/_demo/go/oslookpath/lookpath.go similarity index 100% rename from _demo/oslookpath/lookpath.go rename to _demo/go/oslookpath/lookpath.go diff --git a/_demo/go/oswritestring/main.go b/_demo/go/oswritestring/main.go new file mode 100644 index 0000000000..83fd695dd4 --- /dev/null +++ b/_demo/go/oswritestring/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + f, err := os.CreateTemp("", "llgo-writestring-*.txt") + if err != nil { + panic(err) + } + defer os.Remove(f.Name()) + + const content = "hello writestring" + if n, err := f.WriteString(content); err != nil { + panic(err) + } else if n != len(content) { + panic(fmt.Sprintf("WriteString wrote %d bytes, want %d", n, len(content))) + } + + if err := f.Close(); err != nil { + panic(err) + } + + data, err := os.ReadFile(f.Name()) + if err != nil { + panic(err) + } + if string(data) != content { + panic(fmt.Sprintf("content mismatch: got %q, want %q", string(data), content)) + } + + fmt.Println("ok") +} diff --git a/_demo/randcrypt/rand.go b/_demo/go/randcrypt/rand.go similarity index 100% rename from _demo/randcrypt/rand.go rename to _demo/go/randcrypt/rand.go diff --git a/_demo/randdemo/rand.go b/_demo/go/randdemo/rand.go similarity index 100% rename from _demo/randdemo/rand.go rename to _demo/go/randdemo/rand.go diff --git a/_demo/readdir/main.go b/_demo/go/readdir/main.go similarity index 100% rename from _demo/readdir/main.go rename to _demo/go/readdir/main.go diff --git a/_demo/go/reflectcallfn/main.go b/_demo/go/reflectcallfn/main.go new file mode 100644 index 0000000000..432c609336 --- /dev/null +++ b/_demo/go/reflectcallfn/main.go @@ -0,0 +1,81 @@ +package main + +import ( + "reflect" +) + +type M struct { + M1 func(int) int + M2 func(int) int +} + +type N struct { + N1 func(int) int + N2 func(int) int + M M +} + +func demo2(fn func(n int) int) (func(int) int, func(int) int) { + return func(n int) int { + return fn(n + 100) + }, func(n int) int { + return fn(n + 200) + } +} + +func demo1(fn func(n int) int) func(n int) int { + return func(n int) int { + return fn(n + 100) + } +} + +func demo3(fn func(n int) int) N { + return N{ + N1: func(n int) int { return fn(n) + 100 }, + N2: func(n int) int { return fn(n) + 200 }, + M: M{ + M1: func(n int) int { return fn(n) + 300 }, + M2: func(n int) int { return fn(n) + 400 }, + }, + } +} + +func main() { + var base = 100 + fn := func(n int) int { + return n + base + } + // demo1 + f1 := reflect.ValueOf(demo1) + r1 := f1.Call([]reflect.Value{reflect.ValueOf(fn)}) + if r1[0].Call([]reflect.Value{reflect.ValueOf(100)})[0].Int() != 300 { + panic("call demo1 error") + } + // demo2 + f2 := reflect.ValueOf(demo2) + r2 := f2.Call([]reflect.Value{reflect.ValueOf(fn)}) + if r2[0].Call([]reflect.Value{reflect.ValueOf(100)})[0].Int() != 300 { + panic("call demo2 error") + } + if r2[1].Call([]reflect.Value{reflect.ValueOf(100)})[0].Int() != 400 { + panic("call demo2 error") + } + // demo3 + f3 := reflect.ValueOf(demo3) + r3 := f3.Call([]reflect.Value{reflect.ValueOf(fn)}) + if r3[0].Field(0).Call([]reflect.Value{reflect.ValueOf(100)})[0].Int() != 300 { + panic("call N.N1 error") + } + if r3[0].Field(1).Call([]reflect.Value{reflect.ValueOf(100)})[0].Int() != 400 { + panic("call N.N2 error") + } + if r3[0].Field(2).Field(0).Call([]reflect.Value{reflect.ValueOf(100)})[0].Int() != 500 { + panic("call N.M.M1 error") + } + if r3[0].Field(2).Field(1).Call([]reflect.Value{reflect.ValueOf(100)})[0].Int() != 600 { + panic("call N.M.M2 error") + } + if r3[0].Field(2).Field(0).Call([]reflect.Value{reflect.ValueOf(100)})[0].Int() != 500 { + panic("call N.M.M1 error") + } +} diff --git a/_demo/go/reflectchanof/main.go b/_demo/go/reflectchanof/main.go new file mode 100644 index 0000000000..a092e8571e --- /dev/null +++ b/_demo/go/reflectchanof/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "reflect" +) + +type T struct{} + +func main() { + ch := reflect.ChanOf(reflect.BothDir, reflect.TypeOf(T{})) + ptr := reflect.PointerTo(ch) + if ptr.Elem() != ch { + panic("error " + ptr.String()) + } +} diff --git a/_demo/go/reflectconv/main.go b/_demo/go/reflectconv/main.go new file mode 100644 index 0000000000..a7e10db95c --- /dev/null +++ b/_demo/go/reflectconv/main.go @@ -0,0 +1,765 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "log" + "math" + "reflect" + . "reflect" + "runtime" + "strings" + "unsafe" +) + +type testingT struct { +} + +func (t *testingT) Errorf(format string, args ...any) { + log.Panicf(format, args...) +} + +func (t *testingT) Fatal(args ...any) { + log.Panic(args...) +} + +func (t *testingT) Fatalf(format string, args ...any) { + log.Panicf(format, args...) +} + +func main() { + // TestConvert(&testingT{}) + // TestConvertNaNs(&testingT{}) + // TestConvertPanic(&testingT{}) + // TestConvertSlice2Array(&testingT{}) + TestConvertFunc(&testingT{}) +} + +var V = ValueOf + +func EmptyInterfaceV(x any) Value { + return ValueOf(&x).Elem() +} + +func ReaderV(x io.Reader) Value { + return ValueOf(&x).Elem() +} + +func ReadWriterV(x io.ReadWriter) Value { + return ValueOf(&x).Elem() +} + +type flag uintptr + +const flagStickyRO flag = 1 << 5 + +type _Value struct { + typ unsafe.Pointer + ptr unsafe.Pointer + flag +} + +// MakeRO returns a copy of v with the read-only flag set. +func MakeRO(v Value) Value { + (*_Value)(unsafe.Pointer(&v)).flag |= flagStickyRO + return v +} + +// IsRO reports whether v's read-only flag is set. +func IsRO(v Value) bool { + return (*_Value)(unsafe.Pointer(&v)).flag&flagStickyRO != 0 +} + +type integer int + +type Empty struct{} +type MyStruct struct { + x int `some:"tag"` +} +type MyStruct1 struct { + x struct { + int `some:"bar"` + } +} +type MyStruct2 struct { + x struct { + int `some:"foo"` + } +} +type MyString string +type MyBytes []byte +type MyBytesArrayPtr0 *[0]byte +type MyBytesArrayPtr *[4]byte +type MyBytesArray0 [0]byte +type MyBytesArray [4]byte +type MyRunes []int32 +type MyFunc func() +type MyByte byte + +type IntChan chan int +type IntChanRecv <-chan int +type IntChanSend chan<- int +type BytesChan chan []byte +type BytesChanRecv <-chan []byte +type BytesChanSend chan<- []byte + +var convertTests = []struct { + in Value + out Value +}{ + // numbers + /* + Edit .+1,/\*\//-1>cat >/tmp/x.go && go run /tmp/x.go + + package main + + import "fmt" + + var numbers = []string{ + "int8", "uint8", "int16", "uint16", + "int32", "uint32", "int64", "uint64", + "int", "uint", "uintptr", + "float32", "float64", + } + + func main() { + // all pairs but in an unusual order, + // to emit all the int8, uint8 cases + // before n grows too big. + n := 1 + for i, f := range numbers { + for _, g := range numbers[i:] { + fmt.Printf("\t{V(%s(%d)), V(%s(%d))},\n", f, n, g, n) + n++ + if f != g { + fmt.Printf("\t{V(%s(%d)), V(%s(%d))},\n", g, n, f, n) + n++ + } + } + } + } + */ + {V(int8(1)), V(int8(1))}, + {V(int8(2)), V(uint8(2))}, + {V(uint8(3)), V(int8(3))}, + {V(int8(4)), V(int16(4))}, + {V(int16(5)), V(int8(5))}, + {V(int8(6)), V(uint16(6))}, + {V(uint16(7)), V(int8(7))}, + {V(int8(8)), V(int32(8))}, + {V(int32(9)), V(int8(9))}, + {V(int8(10)), V(uint32(10))}, + {V(uint32(11)), V(int8(11))}, + {V(int8(12)), V(int64(12))}, + {V(int64(13)), V(int8(13))}, + {V(int8(14)), V(uint64(14))}, + {V(uint64(15)), V(int8(15))}, + {V(int8(16)), V(int(16))}, + {V(int(17)), V(int8(17))}, + {V(int8(18)), V(uint(18))}, + {V(uint(19)), V(int8(19))}, + {V(int8(20)), V(uintptr(20))}, + {V(uintptr(21)), V(int8(21))}, + {V(int8(22)), V(float32(22))}, + {V(float32(23)), V(int8(23))}, + {V(int8(24)), V(float64(24))}, + {V(float64(25)), V(int8(25))}, + {V(uint8(26)), V(uint8(26))}, + {V(uint8(27)), V(int16(27))}, + {V(int16(28)), V(uint8(28))}, + {V(uint8(29)), V(uint16(29))}, + {V(uint16(30)), V(uint8(30))}, + {V(uint8(31)), V(int32(31))}, + {V(int32(32)), V(uint8(32))}, + {V(uint8(33)), V(uint32(33))}, + {V(uint32(34)), V(uint8(34))}, + {V(uint8(35)), V(int64(35))}, + {V(int64(36)), V(uint8(36))}, + {V(uint8(37)), V(uint64(37))}, + {V(uint64(38)), V(uint8(38))}, + {V(uint8(39)), V(int(39))}, + {V(int(40)), V(uint8(40))}, + {V(uint8(41)), V(uint(41))}, + {V(uint(42)), V(uint8(42))}, + {V(uint8(43)), V(uintptr(43))}, + {V(uintptr(44)), V(uint8(44))}, + {V(uint8(45)), V(float32(45))}, + {V(float32(46)), V(uint8(46))}, + {V(uint8(47)), V(float64(47))}, + {V(float64(48)), V(uint8(48))}, + {V(int16(49)), V(int16(49))}, + {V(int16(50)), V(uint16(50))}, + {V(uint16(51)), V(int16(51))}, + {V(int16(52)), V(int32(52))}, + {V(int32(53)), V(int16(53))}, + {V(int16(54)), V(uint32(54))}, + {V(uint32(55)), V(int16(55))}, + {V(int16(56)), V(int64(56))}, + {V(int64(57)), V(int16(57))}, + {V(int16(58)), V(uint64(58))}, + {V(uint64(59)), V(int16(59))}, + {V(int16(60)), V(int(60))}, + {V(int(61)), V(int16(61))}, + {V(int16(62)), V(uint(62))}, + {V(uint(63)), V(int16(63))}, + {V(int16(64)), V(uintptr(64))}, + {V(uintptr(65)), V(int16(65))}, + {V(int16(66)), V(float32(66))}, + {V(float32(67)), V(int16(67))}, + {V(int16(68)), V(float64(68))}, + {V(float64(69)), V(int16(69))}, + {V(uint16(70)), V(uint16(70))}, + {V(uint16(71)), V(int32(71))}, + {V(int32(72)), V(uint16(72))}, + {V(uint16(73)), V(uint32(73))}, + {V(uint32(74)), V(uint16(74))}, + {V(uint16(75)), V(int64(75))}, + {V(int64(76)), V(uint16(76))}, + {V(uint16(77)), V(uint64(77))}, + {V(uint64(78)), V(uint16(78))}, + {V(uint16(79)), V(int(79))}, + {V(int(80)), V(uint16(80))}, + {V(uint16(81)), V(uint(81))}, + {V(uint(82)), V(uint16(82))}, + {V(uint16(83)), V(uintptr(83))}, + {V(uintptr(84)), V(uint16(84))}, + {V(uint16(85)), V(float32(85))}, + {V(float32(86)), V(uint16(86))}, + {V(uint16(87)), V(float64(87))}, + {V(float64(88)), V(uint16(88))}, + {V(int32(89)), V(int32(89))}, + {V(int32(90)), V(uint32(90))}, + {V(uint32(91)), V(int32(91))}, + {V(int32(92)), V(int64(92))}, + {V(int64(93)), V(int32(93))}, + {V(int32(94)), V(uint64(94))}, + {V(uint64(95)), V(int32(95))}, + {V(int32(96)), V(int(96))}, + {V(int(97)), V(int32(97))}, + {V(int32(98)), V(uint(98))}, + {V(uint(99)), V(int32(99))}, + {V(int32(100)), V(uintptr(100))}, + {V(uintptr(101)), V(int32(101))}, + {V(int32(102)), V(float32(102))}, + {V(float32(103)), V(int32(103))}, + {V(int32(104)), V(float64(104))}, + {V(float64(105)), V(int32(105))}, + {V(uint32(106)), V(uint32(106))}, + {V(uint32(107)), V(int64(107))}, + {V(int64(108)), V(uint32(108))}, + {V(uint32(109)), V(uint64(109))}, + {V(uint64(110)), V(uint32(110))}, + {V(uint32(111)), V(int(111))}, + {V(int(112)), V(uint32(112))}, + {V(uint32(113)), V(uint(113))}, + {V(uint(114)), V(uint32(114))}, + {V(uint32(115)), V(uintptr(115))}, + {V(uintptr(116)), V(uint32(116))}, + {V(uint32(117)), V(float32(117))}, + {V(float32(118)), V(uint32(118))}, + {V(uint32(119)), V(float64(119))}, + {V(float64(120)), V(uint32(120))}, + {V(int64(121)), V(int64(121))}, + {V(int64(122)), V(uint64(122))}, + {V(uint64(123)), V(int64(123))}, + {V(int64(124)), V(int(124))}, + {V(int(125)), V(int64(125))}, + {V(int64(126)), V(uint(126))}, + {V(uint(127)), V(int64(127))}, + {V(int64(128)), V(uintptr(128))}, + {V(uintptr(129)), V(int64(129))}, + {V(int64(130)), V(float32(130))}, + {V(float32(131)), V(int64(131))}, + {V(int64(132)), V(float64(132))}, + {V(float64(133)), V(int64(133))}, + {V(uint64(134)), V(uint64(134))}, + {V(uint64(135)), V(int(135))}, + {V(int(136)), V(uint64(136))}, + {V(uint64(137)), V(uint(137))}, + {V(uint(138)), V(uint64(138))}, + {V(uint64(139)), V(uintptr(139))}, + {V(uintptr(140)), V(uint64(140))}, + {V(uint64(141)), V(float32(141))}, + {V(float32(142)), V(uint64(142))}, + {V(uint64(143)), V(float64(143))}, + {V(float64(144)), V(uint64(144))}, + {V(int(145)), V(int(145))}, + {V(int(146)), V(uint(146))}, + {V(uint(147)), V(int(147))}, + {V(int(148)), V(uintptr(148))}, + {V(uintptr(149)), V(int(149))}, + {V(int(150)), V(float32(150))}, + {V(float32(151)), V(int(151))}, + {V(int(152)), V(float64(152))}, + {V(float64(153)), V(int(153))}, + {V(uint(154)), V(uint(154))}, + {V(uint(155)), V(uintptr(155))}, + {V(uintptr(156)), V(uint(156))}, + {V(uint(157)), V(float32(157))}, + {V(float32(158)), V(uint(158))}, + {V(uint(159)), V(float64(159))}, + {V(float64(160)), V(uint(160))}, + {V(uintptr(161)), V(uintptr(161))}, + {V(uintptr(162)), V(float32(162))}, + {V(float32(163)), V(uintptr(163))}, + {V(uintptr(164)), V(float64(164))}, + {V(float64(165)), V(uintptr(165))}, + {V(float32(166)), V(float32(166))}, + {V(float32(167)), V(float64(167))}, + {V(float64(168)), V(float32(168))}, + {V(float64(169)), V(float64(169))}, + + // truncation + {V(float64(1.5)), V(int(1))}, + + // complex + {V(complex64(1i)), V(complex64(1i))}, + {V(complex64(2i)), V(complex128(2i))}, + {V(complex128(3i)), V(complex64(3i))}, + {V(complex128(4i)), V(complex128(4i))}, + + // string + {V(string("hello")), V(string("hello"))}, + {V(string("bytes1")), V([]byte("bytes1"))}, + {V([]byte("bytes2")), V(string("bytes2"))}, + {V([]byte("bytes3")), V([]byte("bytes3"))}, + {V(string("runes♝")), V([]rune("runes♝"))}, + {V([]rune("runes♕")), V(string("runes♕"))}, + {V([]rune("runes🙈🙉🙊")), V([]rune("runes🙈🙉🙊"))}, + {V(int('a')), V(string("a"))}, + {V(int8('a')), V(string("a"))}, + {V(int16('a')), V(string("a"))}, + {V(int32('a')), V(string("a"))}, + {V(int64('a')), V(string("a"))}, + {V(uint('a')), V(string("a"))}, + {V(uint8('a')), V(string("a"))}, + {V(uint16('a')), V(string("a"))}, + {V(uint32('a')), V(string("a"))}, + {V(uint64('a')), V(string("a"))}, + {V(uintptr('a')), V(string("a"))}, + {V(int(-1)), V(string("\uFFFD"))}, + {V(int8(-2)), V(string("\uFFFD"))}, + {V(int16(-3)), V(string("\uFFFD"))}, + {V(int32(-4)), V(string("\uFFFD"))}, + {V(int64(-5)), V(string("\uFFFD"))}, + {V(int64(-1 << 32)), V(string("\uFFFD"))}, + {V(int64(1 << 32)), V(string("\uFFFD"))}, + {V(uint(0x110001)), V(string("\uFFFD"))}, + {V(uint32(0x110002)), V(string("\uFFFD"))}, + {V(uint64(0x110003)), V(string("\uFFFD"))}, + {V(uint64(1 << 32)), V(string("\uFFFD"))}, + {V(uintptr(0x110004)), V(string("\uFFFD"))}, + + // named string + {V(MyString("hello")), V(string("hello"))}, + {V(string("hello")), V(MyString("hello"))}, + {V(string("hello")), V(string("hello"))}, + {V(MyString("hello")), V(MyString("hello"))}, + {V(MyString("bytes1")), V([]byte("bytes1"))}, + {V([]byte("bytes2")), V(MyString("bytes2"))}, + {V([]byte("bytes3")), V([]byte("bytes3"))}, + {V(MyString("runes♝")), V([]rune("runes♝"))}, + {V([]rune("runes♕")), V(MyString("runes♕"))}, + {V([]rune("runes🙈🙉🙊")), V([]rune("runes🙈🙉🙊"))}, + {V([]rune("runes🙈🙉🙊")), V(MyRunes("runes🙈🙉🙊"))}, + {V(MyRunes("runes🙈🙉🙊")), V([]rune("runes🙈🙉🙊"))}, + {V(int('a')), V(MyString("a"))}, + {V(int8('a')), V(MyString("a"))}, + {V(int16('a')), V(MyString("a"))}, + {V(int32('a')), V(MyString("a"))}, + {V(int64('a')), V(MyString("a"))}, + {V(uint('a')), V(MyString("a"))}, + {V(uint8('a')), V(MyString("a"))}, + {V(uint16('a')), V(MyString("a"))}, + {V(uint32('a')), V(MyString("a"))}, + {V(uint64('a')), V(MyString("a"))}, + {V(uintptr('a')), V(MyString("a"))}, + {V(int(-1)), V(MyString("\uFFFD"))}, + {V(int8(-2)), V(MyString("\uFFFD"))}, + {V(int16(-3)), V(MyString("\uFFFD"))}, + {V(int32(-4)), V(MyString("\uFFFD"))}, + {V(int64(-5)), V(MyString("\uFFFD"))}, + {V(uint(0x110001)), V(MyString("\uFFFD"))}, + {V(uint32(0x110002)), V(MyString("\uFFFD"))}, + {V(uint64(0x110003)), V(MyString("\uFFFD"))}, + {V(uintptr(0x110004)), V(MyString("\uFFFD"))}, + + // named []byte + {V(string("bytes1")), V(MyBytes("bytes1"))}, + {V(MyBytes("bytes2")), V(string("bytes2"))}, + {V(MyBytes("bytes3")), V(MyBytes("bytes3"))}, + {V(MyString("bytes1")), V(MyBytes("bytes1"))}, + {V(MyBytes("bytes2")), V(MyString("bytes2"))}, + + // named []rune + {V(string("runes♝")), V(MyRunes("runes♝"))}, + {V(MyRunes("runes♕")), V(string("runes♕"))}, + {V(MyRunes("runes🙈🙉🙊")), V(MyRunes("runes🙈🙉🙊"))}, + {V(MyString("runes♝")), V(MyRunes("runes♝"))}, + {V(MyRunes("runes♕")), V(MyString("runes♕"))}, + + // slice to array + {V([]byte(nil)), V([0]byte{})}, + {V([]byte{}), V([0]byte{})}, + {V([]byte{1}), V([1]byte{1})}, + {V([]byte{1, 2}), V([2]byte{1, 2})}, + {V([]byte{1, 2, 3}), V([3]byte{1, 2, 3})}, + {V(MyBytes([]byte(nil))), V([0]byte{})}, + {V(MyBytes{}), V([0]byte{})}, + {V(MyBytes{1}), V([1]byte{1})}, + {V(MyBytes{1, 2}), V([2]byte{1, 2})}, + {V(MyBytes{1, 2, 3}), V([3]byte{1, 2, 3})}, + {V([]byte(nil)), V(MyBytesArray0{})}, + {V([]byte{}), V(MyBytesArray0([0]byte{}))}, + {V([]byte{1, 2, 3, 4}), V(MyBytesArray([4]byte{1, 2, 3, 4}))}, + {V(MyBytes{}), V(MyBytesArray0([0]byte{}))}, + {V(MyBytes{5, 6, 7, 8}), V(MyBytesArray([4]byte{5, 6, 7, 8}))}, + {V([]MyByte{}), V([0]MyByte{})}, + {V([]MyByte{1, 2}), V([2]MyByte{1, 2})}, + + // slice to array pointer + {V([]byte(nil)), V((*[0]byte)(nil))}, + {V([]byte{}), V(new([0]byte))}, + {V([]byte{7}), V(&[1]byte{7})}, + {V(MyBytes([]byte(nil))), V((*[0]byte)(nil))}, + {V(MyBytes([]byte{})), V(new([0]byte))}, + {V(MyBytes([]byte{9})), V(&[1]byte{9})}, + {V([]byte(nil)), V(MyBytesArrayPtr0(nil))}, + {V([]byte{}), V(MyBytesArrayPtr0(new([0]byte)))}, + {V([]byte{1, 2, 3, 4}), V(MyBytesArrayPtr(&[4]byte{1, 2, 3, 4}))}, + {V(MyBytes([]byte{})), V(MyBytesArrayPtr0(new([0]byte)))}, + {V(MyBytes([]byte{5, 6, 7, 8})), V(MyBytesArrayPtr(&[4]byte{5, 6, 7, 8}))}, + + {V([]byte(nil)), V((*MyBytesArray0)(nil))}, + {V([]byte{}), V((*MyBytesArray0)(new([0]byte)))}, + {V([]byte{1, 2, 3, 4}), V(&MyBytesArray{1, 2, 3, 4})}, + {V(MyBytes([]byte(nil))), V((*MyBytesArray0)(nil))}, + {V(MyBytes([]byte{})), V((*MyBytesArray0)(new([0]byte)))}, + {V(MyBytes([]byte{5, 6, 7, 8})), V(&MyBytesArray{5, 6, 7, 8})}, + {V(new([0]byte)), V(new(MyBytesArray0))}, + {V(new(MyBytesArray0)), V(new([0]byte))}, + {V(MyBytesArrayPtr0(nil)), V((*[0]byte)(nil))}, + {V((*[0]byte)(nil)), V(MyBytesArrayPtr0(nil))}, + + // named types and equal underlying types + {V(new(int)), V(new(integer))}, + {V(new(integer)), V(new(int))}, + {V(Empty{}), V(struct{}{})}, + {V(new(Empty)), V(new(struct{}))}, + {V(struct{}{}), V(Empty{})}, + {V(new(struct{})), V(new(Empty))}, + {V(Empty{}), V(Empty{})}, + {V(MyBytes{}), V([]byte{})}, + {V([]byte{}), V(MyBytes{})}, + {V((func())(nil)), V(MyFunc(nil))}, + {V((MyFunc)(nil)), V((func())(nil))}, + + // structs with different tags + {V(struct { + x int `some:"foo"` + }{}), V(struct { + x int `some:"bar"` + }{})}, + + {V(struct { + x int `some:"bar"` + }{}), V(struct { + x int `some:"foo"` + }{})}, + + {V(MyStruct{}), V(struct { + x int `some:"foo"` + }{})}, + + {V(struct { + x int `some:"foo"` + }{}), V(MyStruct{})}, + + {V(MyStruct{}), V(struct { + x int `some:"bar"` + }{})}, + + {V(struct { + x int `some:"bar"` + }{}), V(MyStruct{})}, + + {V(MyStruct1{}), V(MyStruct2{})}, + {V(MyStruct2{}), V(MyStruct1{})}, + + // can convert *byte and *MyByte + {V((*byte)(nil)), V((*MyByte)(nil))}, + {V((*MyByte)(nil)), V((*byte)(nil))}, + + // cannot convert mismatched array sizes + {V([2]byte{}), V([2]byte{})}, + {V([3]byte{}), V([3]byte{})}, + {V(MyBytesArray0{}), V([0]byte{})}, + {V([0]byte{}), V(MyBytesArray0{})}, + + // cannot convert other instances + {V((**byte)(nil)), V((**byte)(nil))}, + {V((**MyByte)(nil)), V((**MyByte)(nil))}, + {V((chan byte)(nil)), V((chan byte)(nil))}, + {V((chan MyByte)(nil)), V((chan MyByte)(nil))}, + {V(([]byte)(nil)), V(([]byte)(nil))}, + {V(([]MyByte)(nil)), V(([]MyByte)(nil))}, + {V((map[int]byte)(nil)), V((map[int]byte)(nil))}, + {V((map[int]MyByte)(nil)), V((map[int]MyByte)(nil))}, + {V((map[byte]int)(nil)), V((map[byte]int)(nil))}, + {V((map[MyByte]int)(nil)), V((map[MyByte]int)(nil))}, + {V([2]byte{}), V([2]byte{})}, + {V([2]MyByte{}), V([2]MyByte{})}, + + // other + {V((***int)(nil)), V((***int)(nil))}, + {V((***byte)(nil)), V((***byte)(nil))}, + {V((***int32)(nil)), V((***int32)(nil))}, + {V((***int64)(nil)), V((***int64)(nil))}, + {V((chan byte)(nil)), V((chan byte)(nil))}, + {V((chan MyByte)(nil)), V((chan MyByte)(nil))}, + {V((map[int]bool)(nil)), V((map[int]bool)(nil))}, + {V((map[int]byte)(nil)), V((map[int]byte)(nil))}, + {V((map[uint]bool)(nil)), V((map[uint]bool)(nil))}, + {V([]uint(nil)), V([]uint(nil))}, + {V([]int(nil)), V([]int(nil))}, + {V(new(any)), V(new(any))}, + {V(new(io.Reader)), V(new(io.Reader))}, + {V(new(io.Writer)), V(new(io.Writer))}, + + // channels + {V(IntChan(nil)), V((chan<- int)(nil))}, + {V(IntChan(nil)), V((<-chan int)(nil))}, + {V((chan int)(nil)), V(IntChanRecv(nil))}, + {V((chan int)(nil)), V(IntChanSend(nil))}, + {V(IntChanRecv(nil)), V((<-chan int)(nil))}, + {V((<-chan int)(nil)), V(IntChanRecv(nil))}, + {V(IntChanSend(nil)), V((chan<- int)(nil))}, + {V((chan<- int)(nil)), V(IntChanSend(nil))}, + {V(IntChan(nil)), V((chan int)(nil))}, + {V((chan int)(nil)), V(IntChan(nil))}, + {V((chan int)(nil)), V((<-chan int)(nil))}, + {V((chan int)(nil)), V((chan<- int)(nil))}, + {V(BytesChan(nil)), V((chan<- []byte)(nil))}, + {V(BytesChan(nil)), V((<-chan []byte)(nil))}, + {V((chan []byte)(nil)), V(BytesChanRecv(nil))}, + {V((chan []byte)(nil)), V(BytesChanSend(nil))}, + {V(BytesChanRecv(nil)), V((<-chan []byte)(nil))}, + {V((<-chan []byte)(nil)), V(BytesChanRecv(nil))}, + {V(BytesChanSend(nil)), V((chan<- []byte)(nil))}, + {V((chan<- []byte)(nil)), V(BytesChanSend(nil))}, + {V(BytesChan(nil)), V((chan []byte)(nil))}, + {V((chan []byte)(nil)), V(BytesChan(nil))}, + {V((chan []byte)(nil)), V((<-chan []byte)(nil))}, + {V((chan []byte)(nil)), V((chan<- []byte)(nil))}, + + // cannot convert other instances (channels) + {V(IntChan(nil)), V(IntChan(nil))}, + {V(IntChanRecv(nil)), V(IntChanRecv(nil))}, + {V(IntChanSend(nil)), V(IntChanSend(nil))}, + {V(BytesChan(nil)), V(BytesChan(nil))}, + {V(BytesChanRecv(nil)), V(BytesChanRecv(nil))}, + {V(BytesChanSend(nil)), V(BytesChanSend(nil))}, + + // interfaces + {V(int(1)), EmptyInterfaceV(int(1))}, + {V(string("hello")), EmptyInterfaceV(string("hello"))}, + {V(new(bytes.Buffer)), ReaderV(new(bytes.Buffer))}, + {ReadWriterV(new(bytes.Buffer)), ReaderV(new(bytes.Buffer))}, + {V(new(bytes.Buffer)), ReadWriterV(new(bytes.Buffer))}, +} + +func TestConvert(t *testingT) { + canConvert := map[[2]Type]bool{} + all := map[Type]bool{} + + for _, tt := range convertTests { + t1 := tt.in.Type() + if !t1.ConvertibleTo(t1) { + t.Errorf("(%s).ConvertibleTo(%s) = false, want true", t1, t1) + continue + } + + t2 := tt.out.Type() + if !t1.ConvertibleTo(t2) { + t.Errorf("(%s).ConvertibleTo(%s) = false, want true", t1, t2) + continue + } + + all[t1] = true + all[t2] = true + canConvert[[2]Type{t1, t2}] = true + + // vout1 represents the in value converted to the in type. + v1 := tt.in + if !v1.CanConvert(t1) { + t.Errorf("ValueOf(%T(%[1]v)).CanConvert(%s) = false, want true", tt.in.Interface(), t1) + } + vout1 := v1.Convert(t1) + out1 := vout1.Interface() + if vout1.Type() != tt.in.Type() || !DeepEqual(out1, tt.in.Interface()) { + t.Errorf("ValueOf(%T(%[1]v)).Convert(%s) = %T(%[3]v), want %T(%[4]v)", tt.in.Interface(), t1, out1, tt.in.Interface()) + } + + // vout2 represents the in value converted to the out type. + if !v1.CanConvert(t2) { + t.Errorf("ValueOf(%T(%[1]v)).CanConvert(%s) = false, want true", tt.in.Interface(), t2) + } + vout2 := v1.Convert(t2) + out2 := vout2.Interface() + if vout2.Type() != tt.out.Type() || !DeepEqual(out2, tt.out.Interface()) { + t.Errorf("ValueOf(%T(%[1]v)).Convert(%s) = %T(%[3]v), want %T(%[4]v)", tt.in.Interface(), t2, out2, tt.out.Interface()) + } + if got, want := vout2.Kind(), vout2.Type().Kind(); got != want { + t.Errorf("ValueOf(%T(%[1]v)).Convert(%s) has internal kind %v want %v", tt.in.Interface(), t1, got, want) + } + + // vout3 represents a new value of the out type, set to vout2. This makes + // sure the converted value vout2 is really usable as a regular value. + vout3 := New(t2).Elem() + vout3.Set(vout2) + out3 := vout3.Interface() + if vout3.Type() != tt.out.Type() || !DeepEqual(out3, tt.out.Interface()) { + t.Errorf("Set(ValueOf(%T(%[1]v)).Convert(%s)) = %T(%[3]v), want %T(%[4]v)", tt.in.Interface(), t2, out3, tt.out.Interface()) + } + + if IsRO(v1) { + t.Errorf("table entry %v is RO, should not be", v1) + } + if IsRO(vout1) { + t.Errorf("self-conversion output %v is RO, should not be", vout1) + } + if IsRO(vout2) { + t.Errorf("conversion output %v is RO, should not be", vout2) + } + if IsRO(vout3) { + t.Errorf("set(conversion output) %v is RO, should not be", vout3) + } + if !IsRO(MakeRO(v1).Convert(t1)) { + t.Errorf("RO self-conversion output %v is not RO, should be", v1) + } + if !IsRO(MakeRO(v1).Convert(t2)) { + t.Errorf("RO conversion output %v is not RO, should be", v1) + } + } + + // Assume that of all the types we saw during the tests, + // if there wasn't an explicit entry for a conversion between + // a pair of types, then it's not to be allowed. This checks for + // things like 'int64' converting to '*int'. + for t1 := range all { + for t2 := range all { + expectOK := t1 == t2 || canConvert[[2]Type{t1, t2}] || t2.Kind() == Interface && t2.NumMethod() == 0 + if ok := t1.ConvertibleTo(t2); ok != expectOK { + t.Errorf("(%s).ConvertibleTo(%s) = %v, want %v", t1, t2, ok, expectOK) + } + } + } +} + +func TestConvertPanic(t *testingT) { + s := make([]byte, 4) + p := new([8]byte) + v := ValueOf(s) + pt := TypeOf(p) + if !v.Type().ConvertibleTo(pt) { + t.Errorf("[]byte should be convertible to *[8]byte") + } + if v.CanConvert(pt) { + t.Errorf("slice with length 4 should not be convertible to *[8]byte") + } + shouldPanic("reflect: cannot convert slice with length 4 to pointer to array with length 8", func() { + _ = v.Convert(pt) + }) + + if v.CanConvert(pt.Elem()) { + t.Errorf("slice with length 4 should not be convertible to [8]byte") + } + shouldPanic("reflect: cannot convert slice with length 4 to array with length 8", func() { + _ = v.Convert(pt.Elem()) + }) +} + +func TestConvertSlice2Array(t *testingT) { + s := make([]int, 4) + p := [4]int{} + pt := TypeOf(p) + ov := ValueOf(s) + v := ov.Convert(pt) + // Converting a slice to non-empty array needs to return + // a non-addressable copy of the original memory. + if v.CanAddr() { + t.Fatalf("convert slice to non-empty array returns an addressable copy array") + } + for i := range s { + ov.Index(i).Set(ValueOf(i + 1)) + } + for i := range s { + if v.Index(i).Int() != 0 { + t.Fatalf("slice (%v) mutation visible in converted result (%v)", ov, v) + } + } +} + +var gFloat32 float32 + +const snan uint32 = 0x7f800001 + +func TestConvertNaNs(t *testingT) { + // Test to see if a store followed by a load of a signaling NaN + // maintains the signaling bit. (This used to fail on the 387 port.) + gFloat32 = math.Float32frombits(snan) + runtime.Gosched() // make sure we don't optimize the store/load away + if got := math.Float32bits(gFloat32); got != snan { + t.Errorf("store/load of sNaN not faithful, got %x want %x", got, snan) + } + // Test reflect's conversion between float32s. See issue 36400. + type myFloat32 float32 + x := V(myFloat32(math.Float32frombits(snan))) + y := x.Convert(TypeOf(float32(0))) + z := y.Interface().(float32) + if got := math.Float32bits(z); got != snan { + t.Errorf("signaling nan conversion got %x, want %x", got, snan) + } +} + +func TestConvertFunc(t *testingT) { + type MyFunc func(x int, y int) int + var a int = 100 + fn := func(x int, y int) int { + return x + y + a + } + v := reflect.ValueOf(fn) + mv := v.Convert(reflect.TypeOf(MyFunc(nil))) + r := mv.Call([]reflect.Value{reflect.ValueOf(1), reflect.ValueOf(2)}) + if len(r) != 1 || r[0].Int() != 103 { + t.Errorf("convert func got %v, want %v", r, 103) + } +} + +func shouldPanic(expect string, f func()) { + defer func() { + r := recover() + if r == nil { + panic("did not panic") + } + if expect != "" { + var s string + switch r := r.(type) { + case string: + s = r + case *ValueError: + s = r.Error() + default: + panic(fmt.Sprintf("panicked with unexpected type %T", r)) + } + if !strings.HasPrefix(s, "reflect") { + panic(`panic string does not start with "reflect": ` + s) + } + if !strings.Contains(s, expect) { + panic(`panic string does not contain "` + expect + `": ` + s) + } + } + }() + f() +} diff --git a/_demo/go/reflectcopy/main.go b/_demo/go/reflectcopy/main.go new file mode 100644 index 0000000000..eb03c424b9 --- /dev/null +++ b/_demo/go/reflectcopy/main.go @@ -0,0 +1,76 @@ +package main + +import ( + "fmt" + "reflect" + "strings" +) + +func expect(name string, got, want any) { + if !reflect.DeepEqual(got, want) { + panic(fmt.Sprintf("expect %s: got %v, want %v", name, got, want)) + } + fmt.Println("expect", name, "ok") +} + +func expectPanic(name, contains string, fn func()) { + defer func() { + r := recover() + if r == nil { + panic(fmt.Sprintf("panic %s: did not panic", name)) + } + msg := fmt.Sprint(r) + if contains != "" && !strings.Contains(msg, contains) { + panic(fmt.Sprintf("panic %s: got %q, want contains %q", name, msg, contains)) + } + fmt.Println("panic", name, "ok") + }() + fn() +} + +func main() { + { + dst := []int{1, 2, 3, 4} + src := []int{9, 8} + n := reflect.Copy(reflect.ValueOf(dst), reflect.ValueOf(src)) + expect("slice-slice n", n, 2) + expect("slice-slice dst", dst, []int{9, 8, 3, 4}) + } + + { + dst := []byte("xxxx") + n := reflect.Copy(reflect.ValueOf(dst), reflect.ValueOf("go")) + expect("string-byte n", n, 2) + expect("string-byte dst", dst, []byte("goxx")) + } + + { + arr := [3]int{0, 0, 0} + n := reflect.Copy(reflect.ValueOf(&arr).Elem(), reflect.ValueOf([]int{7, 8, 9, 10})) + expect("array-slice n", n, 3) + expect("array-slice dst", arr, [3]int{7, 8, 9}) + } + + { + src := [2]int{5, 6} + dst := []int{0, 0, 0} + n := reflect.Copy(reflect.ValueOf(dst), reflect.ValueOf(src)) + expect("slice-array n", n, 2) + expect("slice-array dst", dst, []int{5, 6, 0}) + } + + expectPanic("dst-kind", "reflect: call of reflect.Copy on int Value", func() { + reflect.Copy(reflect.ValueOf(1), reflect.ValueOf([]int{1})) + }) + + expectPanic("array-unsettable", "unaddressable value", func() { + arr := [2]int{} + reflect.Copy(reflect.ValueOf(arr), reflect.ValueOf([]int{1, 2})) + }) + + expectPanic("elem-mismatch", "reflect.Copy:", func() { + reflect.Copy(reflect.ValueOf([]int{0}), reflect.ValueOf([]int32{1})) + }) + + fmt.Println("DONE") +} diff --git a/_demo/go/reflectembed/main.go b/_demo/go/reflectembed/main.go new file mode 100644 index 0000000000..646664d732 --- /dev/null +++ b/_demo/go/reflectembed/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "fmt" + "reflect" +) + +func main() { + p1 := Point1{&Point{10, 20}} + testv(p1) + testv(&p1) + p2 := Point2{&Point{10, 20}, 0} + testv(p2) + testv(&p2) + p3 := &Point3{Point{10, 20}} + testp(p3) +} + +func testv(a any) { + v := reflect.ValueOf(a) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + m := v.MethodByName("Set") + m.Call([]reflect.Value{reflect.ValueOf(100), reflect.ValueOf(200)}) + if fmt.Sprint(v.Interface()) != "(100,200)" { + panic(fmt.Errorf("error: %#v", a)) + } +} + +func testp(a any) { + v := reflect.ValueOf(a) + m := v.MethodByName("Set") + m.Call([]reflect.Value{reflect.ValueOf(100), reflect.ValueOf(200)}) + if fmt.Sprint(v.Interface()) != "(100,200)" { + panic(fmt.Errorf("error: %#v", a)) + } +} + +type Point struct { + X int + Y int +} + +func (i Point) String() string { + return fmt.Sprintf("(%v,%v)", i.X, i.Y) +} + +func (i *Point) Set(x int, y int) { + i.X, i.Y = x, y +} + +type Point1 struct { + *Point +} + +type Point2 struct { + *Point + n int +} + +type Point3 struct { + Point +} diff --git a/_demo/go/reflectempty/main.go b/_demo/go/reflectempty/main.go new file mode 100644 index 0000000000..e567d5b97b --- /dev/null +++ b/_demo/go/reflectempty/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "reflect" +) + +var ( + emtpyStruct = reflect.TypeOf((*struct{})(nil)).Elem() + emtpyArray = reflect.TypeOf([0]int{}) + tyInt = reflect.TypeOf(0) + tyString = reflect.TypeOf("") +) + +func main() { + ftyp := reflect.FuncOf([]reflect.Type{emtpyStruct, tyInt, emtpyArray, emtpyStruct, tyString}, []reflect.Type{emtpyStruct, tyInt, emtpyArray, tyString}, false) + fn := reflect.MakeFunc(ftyp, func(args []reflect.Value) []reflect.Value { + if args[4].String() != "hello world" { + panic("error") + } + return []reflect.Value{args[0], reflect.ValueOf(int(args[1].Int()) + args[4].Len()), args[2], args[4]} + }) + r := fn.Call([]reflect.Value{reflect.ValueOf(struct{}{}), reflect.ValueOf(100), reflect.ValueOf([0]int{}), reflect.ValueOf(struct{}{}), reflect.ValueOf("hello world")}) + if r[0].Interface() != struct{}{} { + panic("error r0") + } + if r[1].Int() != 111 { + panic("error r1") + } + if r[2].Interface() != [0]int{} { + panic("error r2") + } + if r[3].Interface() != "hello world" { + panic("error r3") + } +} diff --git a/_demo/go/reflectfnconv/main.go b/_demo/go/reflectfnconv/main.go new file mode 100644 index 0000000000..777849dd09 --- /dev/null +++ b/_demo/go/reflectfnconv/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "reflect" + "strconv" +) + +type itoaFunc func(i int) string + +func (f itoaFunc) Itoa(i int) string { return f(i) } + +func main() { + typ := reflect.TypeOf((*itoaFunc)(nil)).Elem() + tyString := reflect.TypeOf("") + v := reflect.MakeFunc(typ, func(args []reflect.Value) []reflect.Value { + r := strconv.Itoa(int(args[0].Int())) + return []reflect.Value{reflect.ValueOf(r)} + }) + ftyp := reflect.FuncOf([]reflect.Type{v.Type()}, []reflect.Type{tyString}, false) + fn := reflect.MakeFunc(ftyp, func(args []reflect.Value) []reflect.Value { + r := args[0].Call([]reflect.Value{reflect.ValueOf(100)}) + return r + }) + r := fn.Call([]reflect.Value{v}) + if r[0].String() != "100" { + panic("func conv error: " + r[0].String()) + } +} diff --git a/_demo/go/reflectfntype/main.go b/_demo/go/reflectfntype/main.go new file mode 100644 index 0000000000..b4a40b7f94 --- /dev/null +++ b/_demo/go/reflectfntype/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "reflect" +) + +type Func func(int) int + +type T struct { + X int + Y int + Fn Func +} + +func demo(n int, f Func) Func { + return f +} + +func main() { + t := reflect.TypeOf((*T)(nil)).Elem() + if t.Field(2).Type.Kind() != reflect.Func { + panic("filed type error") + } + f := reflect.ValueOf(demo).Type() + if f.In(1).Kind() != reflect.Func { + panic("in type error") + } + if f.Out(0).Kind() != reflect.Func { + panic("in type error") + } +} diff --git a/_demo/reflectfunc/reflectfunc.go b/_demo/go/reflectfunc/reflectfunc.go similarity index 83% rename from _demo/reflectfunc/reflectfunc.go rename to _demo/go/reflectfunc/reflectfunc.go index 7b2f7f11dc..363ae3d26c 100644 --- a/_demo/reflectfunc/reflectfunc.go +++ b/_demo/go/reflectfunc/reflectfunc.go @@ -39,4 +39,12 @@ func main() { panic(fmt.Sprintf("not func: %T", fn)) } } + v := reflect.ValueOf(T{}) + if v.Field(0).Kind() != reflect.Func { + panic("must func") + } +} + +type T struct { + fn func(int) } diff --git a/_demo/go/reflectifacecall/main.go b/_demo/go/reflectifacecall/main.go new file mode 100644 index 0000000000..501183d402 --- /dev/null +++ b/_demo/go/reflectifacecall/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "reflect" +) + +func main() { + fn := reflect.ValueOf(reflect.New) + typ := reflect.TypeOf(0) + v := reflect.ValueOf(typ) + r := fn.Call([]reflect.Value{v}) + e := r[0].Interface().(reflect.Value).Elem() + if e.Kind() != reflect.Int { + panic("error kind") + } + e.SetInt(100) + if e.Interface().(int) != 100 { + panic("error value") + } +} diff --git a/_demo/go/reflectindirect/reflect-indirect.go b/_demo/go/reflectindirect/reflect-indirect.go new file mode 100644 index 0000000000..69332a47d7 --- /dev/null +++ b/_demo/go/reflectindirect/reflect-indirect.go @@ -0,0 +1,49 @@ +package main + +import ( + "reflect" +) + +func main() { + x := 42 + p := &x + + // Test 1: Non-pointer value - should return same value + v1 := reflect.Indirect(reflect.ValueOf(x)) + if !v1.IsValid() || v1.Interface() != 42 { + panic("Non-pointer test failed: expected 42") + } + + // Test 2: Pointer - should dereference + v2 := reflect.Indirect(reflect.ValueOf(p)) + if !v2.IsValid() || v2.Interface() != 42 { + panic("Pointer dereference test failed: expected 42") + } + + // Test 3: Nil pointer - should return invalid Value + var nilPtr *int + v3 := reflect.Indirect(reflect.ValueOf(nilPtr)) + if v3.IsValid() { + panic("Nil pointer test failed: expected invalid Value") + } + + // Test 4: Struct value - should return same value + type Person struct { + Name string + Age int + } + person := Person{Name: "Alice", Age: 30} + v4 := reflect.Indirect(reflect.ValueOf(person)) + if !v4.IsValid() || v4.Interface().(Person).Name != "Alice" || v4.Interface().(Person).Age != 30 { + panic("Struct value test failed: expected Person{Name: Alice, Age: 30}") + } + + // Test 5: Struct pointer - should dereference + personPtr := &Person{Name: "Bob", Age: 25} + v5 := reflect.Indirect(reflect.ValueOf(personPtr)) + if !v5.IsValid() || v5.Interface().(Person).Name != "Bob" || v5.Interface().(Person).Age != 25 { + panic("Struct pointer test failed: expected Person{Name: Bob, Age: 25}") + } + + println("PASS") +} diff --git a/_demo/go/reflectmake/main.go b/_demo/go/reflectmake/main.go new file mode 100644 index 0000000000..f121690343 --- /dev/null +++ b/_demo/go/reflectmake/main.go @@ -0,0 +1,1479 @@ +package main + +import ( + "fmt" + "go/token" + "log" + . "reflect" + "runtime" + "strconv" + "strings" + "time" +) + +type testingT struct { +} + +func (t *testingT) Errorf(format string, args ...any) { + log.Panicf(format, args...) +} + +func (t *testingT) Fatal(args ...any) { + log.Panic(args...) +} + +func (t *testingT) Fatalf(format string, args ...any) { + log.Panicf(format, args...) +} + +func main() { + var t testingT + TestArrayOf(&t) + TestArrayOfAlg(&t) + TestArrayOfGenericAlg(&t) + TestArrayOfDirectIface(&t) + TestArrayOfPanicOnNegativeLength(&t) + TestSliceOf(&t) + //TestSliceOverflow(&t) + TestSliceOfGC(&t) + TestStructOf(&t) + TestStructOfGC(&t) + TestStructOfAlg(&t) + TestStructOfGenericAlg(&t) + TestStructOfDirectIface(&t) + TestStructOfExportRules(&t) + TestStructOfFieldName(&t) + TestStructOfAnonymous(&t) + TestStructOfTooLarge(&t) + TestStructOfDifferentPkgPath(&t) + //TestStructOfTooManyFields(&t) + //TestStructOfWithInterface(&t) + //TestChanOf(&t) + TestChanOfDir(&t) + //TestChanOfGC(&t) + TestMapOf(&t) + TestFuncOf(&t) +} + +func checkSameType(t *testingT, x Type, y any) { + if x != TypeOf(y) || TypeOf(Zero(x).Interface()) != TypeOf(y) { + t.Errorf("did not find preexisting type for %s (vs %s)", TypeOf(x), TypeOf(y)) + } +} + +func TestArrayOf(t *testingT) { + // check construction and use of type not in binary + tests := []struct { + n int + value func(i int) any + comparable bool + want string + }{ + { + n: 0, + value: func(i int) any { type Tint int; return Tint(i) }, + comparable: true, + want: "[]", + }, + { + n: 10, + value: func(i int) any { type Tint int; return Tint(i) }, + comparable: true, + want: "[0 1 2 3 4 5 6 7 8 9]", + }, + { + n: 10, + value: func(i int) any { type Tfloat float64; return Tfloat(i) }, + comparable: true, + want: "[0 1 2 3 4 5 6 7 8 9]", + }, + { + n: 10, + value: func(i int) any { type Tstring string; return Tstring(strconv.Itoa(i)) }, + comparable: true, + want: "[0 1 2 3 4 5 6 7 8 9]", + }, + { + n: 10, + value: func(i int) any { type Tstruct struct{ V int }; return Tstruct{i} }, + comparable: true, + want: "[{0} {1} {2} {3} {4} {5} {6} {7} {8} {9}]", + }, + { + n: 10, + value: func(i int) any { type Tint int; return []Tint{Tint(i)} }, + comparable: false, + want: "[[0] [1] [2] [3] [4] [5] [6] [7] [8] [9]]", + }, + { + n: 10, + value: func(i int) any { type Tint int; return [1]Tint{Tint(i)} }, + comparable: true, + want: "[[0] [1] [2] [3] [4] [5] [6] [7] [8] [9]]", + }, + { + n: 10, + value: func(i int) any { + type Tstruct struct{ V [1]int } + return Tstruct{[1]int{i}} + }, + comparable: true, + want: "[{[0]} {[1]} {[2]} {[3]} {[4]} {[5]} {[6]} {[7]} {[8]} {[9]}]", + }, + { + n: 10, + value: func(i int) any { type Tstruct struct{ V []int }; return Tstruct{[]int{i}} }, + comparable: false, + want: "[{[0]} {[1]} {[2]} {[3]} {[4]} {[5]} {[6]} {[7]} {[8]} {[9]}]", + }, + { + n: 10, + value: func(i int) any { type TstructUV struct{ U, V int }; return TstructUV{i, i} }, + comparable: true, + want: "[{0 0} {1 1} {2 2} {3 3} {4 4} {5 5} {6 6} {7 7} {8 8} {9 9}]", + }, + { + n: 10, + value: func(i int) any { + type TstructUV struct { + U int + V float64 + } + return TstructUV{i, float64(i)} + }, + comparable: true, + want: "[{0 0} {1 1} {2 2} {3 3} {4 4} {5 5} {6 6} {7 7} {8 8} {9 9}]", + }, + } + + for _, table := range tests { + at := ArrayOf(table.n, TypeOf(table.value(0))) + v := New(at).Elem() + vok := New(at).Elem() + vnot := New(at).Elem() + for i := 0; i < v.Len(); i++ { + v.Index(i).Set(ValueOf(table.value(i))) + vok.Index(i).Set(ValueOf(table.value(i))) + j := i + if i+1 == v.Len() { + j = i + 1 + } + vnot.Index(i).Set(ValueOf(table.value(j))) // make it differ only by last element + } + s := fmt.Sprint(v.Interface()) + if s != table.want { + t.Errorf("constructed array = %s, want %s", s, table.want) + } + + if table.comparable != at.Comparable() { + t.Errorf("constructed array (%#v) is comparable=%v, want=%v", v.Interface(), at.Comparable(), table.comparable) + } + if table.comparable { + if table.n > 0 { + if DeepEqual(vnot.Interface(), v.Interface()) { + t.Errorf( + "arrays (%#v) compare ok (but should not)", + v.Interface(), + ) + } + } + if !DeepEqual(vok.Interface(), v.Interface()) { + t.Errorf( + "arrays (%#v) compare NOT-ok (but should)", + v.Interface(), + ) + } + } + } + + // check that type already in binary is found + type T int + checkSameType(t, ArrayOf(5, TypeOf(T(1))), [5]T{}) +} + +func TestArrayOfAlg(t *testingT) { + at := ArrayOf(6, TypeOf(byte(0))) + v1 := New(at).Elem() + v2 := New(at).Elem() + if v1.Interface() != v1.Interface() { + t.Errorf("constructed array %v not equal to itself", v1.Interface()) + } + v1.Index(5).Set(ValueOf(byte(1))) + if i1, i2 := v1.Interface(), v2.Interface(); i1 == i2 { + t.Errorf("constructed arrays %v and %v should not be equal", i1, i2) + } + + at = ArrayOf(6, TypeOf([]int(nil))) + v1 = New(at).Elem() + shouldPanic("", func() { _ = v1.Interface() == v1.Interface() }) +} + +func TestArrayOfGenericAlg(t *testingT) { + at1 := ArrayOf(5, TypeOf(string(""))) + at := ArrayOf(6, at1) + v1 := New(at).Elem() + v2 := New(at).Elem() + if v1.Interface() != v1.Interface() { + t.Errorf("constructed array %v not equal to itself", v1.Interface()) + } + + v1.Index(0).Index(0).Set(ValueOf("abc")) + v2.Index(0).Index(0).Set(ValueOf("efg")) + if i1, i2 := v1.Interface(), v2.Interface(); i1 == i2 { + t.Errorf("constructed arrays %v and %v should not be equal", i1, i2) + } + + v1.Index(0).Index(0).Set(ValueOf("abc")) + v2.Index(0).Index(0).Set(ValueOf((v1.Index(0).Index(0).String() + " ")[:3])) + if i1, i2 := v1.Interface(), v2.Interface(); i1 != i2 { + t.Errorf("constructed arrays %v and %v should be equal", i1, i2) + } + + // Test hash + m := MakeMap(MapOf(at, TypeOf(int(0)))) + m.SetMapIndex(v1, ValueOf(1)) + if i1, i2 := v1.Interface(), v2.Interface(); !m.MapIndex(v2).IsValid() { + t.Errorf("constructed arrays %v and %v have different hashes", i1, i2) + } +} + +func TestArrayOfDirectIface(t *testingT) { + { + type T [1]*byte + i1 := Zero(TypeOf(T{})).Interface() + v1 := ValueOf(&i1).Elem() + p1 := v1.InterfaceData()[1] + + i2 := Zero(ArrayOf(1, PointerTo(TypeOf(int8(0))))).Interface() + v2 := ValueOf(&i2).Elem() + p2 := v2.InterfaceData()[1] + + if p1 != 0 { + t.Errorf("got p1=%v. want=%v", p1, nil) + } + + if p2 != 0 { + t.Errorf("got p2=%v. want=%v", p2, nil) + } + } + { + type T [0]*byte + i1 := Zero(TypeOf(T{})).Interface() + v1 := ValueOf(&i1).Elem() + p1 := v1.InterfaceData()[1] + + i2 := Zero(ArrayOf(0, PointerTo(TypeOf(int8(0))))).Interface() + v2 := ValueOf(&i2).Elem() + p2 := v2.InterfaceData()[1] + + if p1 == 0 { + t.Errorf("got p1=%v. want=not-%v", p1, nil) + } + + if p2 == 0 { + t.Errorf("got p2=%v. want=not-%v", p2, nil) + } + } +} + +// Ensure passing in negative lengths panics. +// See https://golang.org/issue/43603 +func TestArrayOfPanicOnNegativeLength(t *testingT) { + shouldPanic("reflect: negative length passed to ArrayOf", func() { + ArrayOf(-1, TypeOf(byte(0))) + }) +} + +func TestSliceOf(t *testingT) { + // check construction and use of type not in binary + type T int + st := SliceOf(TypeOf(T(1))) + if got, want := st.String(), "[]main.T"; got != want { + t.Errorf("SliceOf(T(1)).String()=%q, want %q", got, want) + } + v := MakeSlice(st, 10, 10) + runtime.GC() + for i := 0; i < v.Len(); i++ { + v.Index(i).Set(ValueOf(T(i))) + runtime.GC() + } + s := fmt.Sprint(v.Interface()) + want := "[0 1 2 3 4 5 6 7 8 9]" + if s != want { + t.Errorf("constructed slice = %s, want %s", s, want) + } + + // check that type already in binary is found + type T1 int + checkSameType(t, SliceOf(TypeOf(T1(1))), []T1{}) +} + +/* +func TestSliceOverflow(t *testingT) { + // check that MakeSlice panics when size of slice overflows uint + const S = 1e6 + s := uint(S) + l := (1<<(unsafe.Sizeof((*byte)(nil))*8)-1)/s + 1 + if l*s >= s { + t.Fatal("slice size does not overflow") + } + var x [S]byte + st := SliceOf(TypeOf(x)) + defer func() { + err := recover() + if err == nil { + t.Fatal("slice overflow does not panic") + } + }() + MakeSlice(st, int(l), int(l)) +} +*/ + +func TestSliceOfGC(t *testingT) { + type T *uintptr + tt := TypeOf(T(nil)) + st := SliceOf(tt) + const n = 100 + var x []any + for i := 0; i < n; i++ { + v := MakeSlice(st, n, n) + for j := 0; j < v.Len(); j++ { + p := new(uintptr) + *p = uintptr(i*n + j) + v.Index(j).Set(ValueOf(p).Convert(tt)) + } + x = append(x, v.Interface()) + } + runtime.GC() + + for i, xi := range x { + v := ValueOf(xi) + for j := 0; j < v.Len(); j++ { + k := v.Index(j).Elem().Interface() + if k != uintptr(i*n+j) { + t.Errorf("lost x[%d][%d] = %d, want %d", i, j, k, i*n+j) + } + } + } +} + +func TestMapOf(t *testingT) { + // check construction and use of type not in binary + type K string + type V float64 + + v := MakeMap(MapOf(TypeOf(K("")), TypeOf(V(0)))) + runtime.GC() + v.SetMapIndex(ValueOf(K("a")), ValueOf(V(1))) + runtime.GC() + + s := fmt.Sprint(v.Interface()) + want := "map[a:1]" + if s != want { + t.Errorf("constructed map = %s, want %s", s, want) + } + + // check that type already in binary is found + checkSameType(t, MapOf(TypeOf(V(0)), TypeOf(K(""))), map[V]K(nil)) + + // check that invalid key type panics + shouldPanic("invalid key type", func() { MapOf(TypeOf((func())(nil)), TypeOf(false)) }) +} + +func TestStructOfFieldName(t *testingT) { + // invalid field name "1nvalid" + shouldPanic("has invalid name", func() { + StructOf([]StructField{ + {Name: "Valid", Type: TypeOf("")}, + {Name: "1nvalid", Type: TypeOf("")}, + }) + }) + + // invalid field name "+" + shouldPanic("has invalid name", func() { + StructOf([]StructField{ + {Name: "Val1d", Type: TypeOf("")}, + {Name: "+", Type: TypeOf("")}, + }) + }) + + // no field name + shouldPanic("has no name", func() { + StructOf([]StructField{ + {Name: "", Type: TypeOf("")}, + }) + }) + + // verify creation of a struct with valid struct fields + validFields := []StructField{ + { + Name: "φ", + Type: TypeOf(""), + }, + { + Name: "ValidName", + Type: TypeOf(""), + }, + { + Name: "Val1dNam5", + Type: TypeOf(""), + }, + } + + validStruct := StructOf(validFields) + + const structStr = `struct { φ string; ValidName string; Val1dNam5 string }` + if got, want := validStruct.String(), structStr; got != want { + t.Errorf("StructOf(validFields).String()=%q, want %q", got, want) + } +} + +func TestStructOf(t *testingT) { + // check construction and use of type not in binary + fields := []StructField{ + { + Name: "S", + Tag: "s", + Type: TypeOf(""), + }, + { + Name: "X", + Tag: "x", + Type: TypeOf(byte(0)), + }, + { + Name: "Y", + Type: TypeOf(uint64(0)), + }, + { + Name: "Z", + Type: TypeOf([3]uint16{}), + }, + } + + st := StructOf(fields) + v := New(st).Elem() + runtime.GC() + v.FieldByName("X").Set(ValueOf(byte(2))) + v.FieldByIndex([]int{1}).Set(ValueOf(byte(1))) + runtime.GC() + + s := fmt.Sprint(v.Interface()) + want := `{ 1 0 [0 0 0]}` + if s != want { + t.Errorf("constructed struct = %s, want %s", s, want) + } + const stStr = `struct { S string "s"; X uint8 "x"; Y uint64; Z [3]uint16 }` + if got, want := st.String(), stStr; got != want { + t.Errorf("StructOf(fields).String()=%q, want %q", got, want) + } + + // check the size, alignment and field offsets + stt := TypeOf(struct { + String string + X byte + Y uint64 + Z [3]uint16 + }{}) + if st.Size() != stt.Size() { + t.Errorf("constructed struct size = %v, want %v", st.Size(), stt.Size()) + } + if st.Align() != stt.Align() { + t.Errorf("constructed struct align = %v, want %v", st.Align(), stt.Align()) + } + if st.FieldAlign() != stt.FieldAlign() { + t.Errorf("constructed struct field align = %v, want %v", st.FieldAlign(), stt.FieldAlign()) + } + for i := 0; i < st.NumField(); i++ { + o1 := st.Field(i).Offset + o2 := stt.Field(i).Offset + if o1 != o2 { + t.Errorf("constructed struct field %v offset = %v, want %v", i, o1, o2) + } + } + + // Check size and alignment with a trailing zero-sized field. + st = StructOf([]StructField{ + { + Name: "F1", + Type: TypeOf(byte(0)), + }, + { + Name: "F2", + Type: TypeOf([0]*byte{}), + }, + }) + stt = TypeOf(struct { + G1 byte + G2 [0]*byte + }{}) + if st.Size() != stt.Size() { + t.Errorf("constructed zero-padded struct size = %v, want %v", st.Size(), stt.Size()) + } + if st.Align() != stt.Align() { + t.Errorf("constructed zero-padded struct align = %v, want %v", st.Align(), stt.Align()) + } + if st.FieldAlign() != stt.FieldAlign() { + t.Errorf("constructed zero-padded struct field align = %v, want %v", st.FieldAlign(), stt.FieldAlign()) + } + for i := 0; i < st.NumField(); i++ { + o1 := st.Field(i).Offset + o2 := stt.Field(i).Offset + if o1 != o2 { + t.Errorf("constructed zero-padded struct field %v offset = %v, want %v", i, o1, o2) + } + } + + // check duplicate names + shouldPanic("duplicate field", func() { + StructOf([]StructField{ + {Name: "string", PkgPath: "p", Type: TypeOf("")}, + {Name: "string", PkgPath: "p", Type: TypeOf("")}, + }) + }) + shouldPanic("has no name", func() { + StructOf([]StructField{ + {Type: TypeOf("")}, + {Name: "string", PkgPath: "p", Type: TypeOf("")}, + }) + }) + shouldPanic("has no name", func() { + StructOf([]StructField{ + {Type: TypeOf("")}, + {Type: TypeOf("")}, + }) + }) + + // check that type already in binary is found + checkSameType(t, StructOf(fields[2:3]), struct{ Y uint64 }{}) + + // gccgo used to fail this test. + type structFieldType any + checkSameType(t, + StructOf([]StructField{ + { + Name: "F", + Type: TypeOf((*structFieldType)(nil)).Elem(), + }, + }), + struct{ F structFieldType }{}) +} + +func TestStructOfExportRules(t *testingT) { + type S1 struct{} + type s2 struct{} + type ΦType struct{} + type φType struct{} + + testPanic := func(i int, mustPanic bool, f func()) { + defer func() { + err := recover() + if err == nil && mustPanic { + t.Errorf("test-%d did not panic", i) + } + if err != nil && !mustPanic { + t.Errorf("test-%d panicked: %v\n", i, err) + } + }() + f() + } + + tests := []struct { + field StructField + mustPanic bool + exported bool + }{ + { + field: StructField{Name: "S1", Anonymous: true, Type: TypeOf(S1{})}, + exported: true, + }, + { + field: StructField{Name: "S1", Anonymous: true, Type: TypeOf((*S1)(nil))}, + exported: true, + }, + { + field: StructField{Name: "s2", Anonymous: true, Type: TypeOf(s2{})}, + mustPanic: true, + }, + { + field: StructField{Name: "s2", Anonymous: true, Type: TypeOf((*s2)(nil))}, + mustPanic: true, + }, + { + field: StructField{Name: "Name", Type: nil, PkgPath: ""}, + mustPanic: true, + }, + { + field: StructField{Name: "", Type: TypeOf(S1{}), PkgPath: ""}, + mustPanic: true, + }, + { + field: StructField{Name: "S1", Anonymous: true, Type: TypeOf(S1{}), PkgPath: "other/pkg"}, + mustPanic: true, + }, + { + field: StructField{Name: "S1", Anonymous: true, Type: TypeOf((*S1)(nil)), PkgPath: "other/pkg"}, + mustPanic: true, + }, + { + field: StructField{Name: "s2", Anonymous: true, Type: TypeOf(s2{}), PkgPath: "other/pkg"}, + mustPanic: true, + }, + { + field: StructField{Name: "s2", Anonymous: true, Type: TypeOf((*s2)(nil)), PkgPath: "other/pkg"}, + mustPanic: true, + }, + { + field: StructField{Name: "s2", Type: TypeOf(int(0)), PkgPath: "other/pkg"}, + }, + { + field: StructField{Name: "s2", Type: TypeOf(int(0)), PkgPath: "other/pkg"}, + }, + { + field: StructField{Name: "S", Type: TypeOf(S1{})}, + exported: true, + }, + { + field: StructField{Name: "S", Type: TypeOf((*S1)(nil))}, + exported: true, + }, + { + field: StructField{Name: "S", Type: TypeOf(s2{})}, + exported: true, + }, + { + field: StructField{Name: "S", Type: TypeOf((*s2)(nil))}, + exported: true, + }, + { + field: StructField{Name: "s", Type: TypeOf(S1{})}, + mustPanic: true, + }, + { + field: StructField{Name: "s", Type: TypeOf((*S1)(nil))}, + mustPanic: true, + }, + { + field: StructField{Name: "s", Type: TypeOf(s2{})}, + mustPanic: true, + }, + { + field: StructField{Name: "s", Type: TypeOf((*s2)(nil))}, + mustPanic: true, + }, + { + field: StructField{Name: "s", Type: TypeOf(S1{}), PkgPath: "other/pkg"}, + }, + { + field: StructField{Name: "s", Type: TypeOf((*S1)(nil)), PkgPath: "other/pkg"}, + }, + { + field: StructField{Name: "s", Type: TypeOf(s2{}), PkgPath: "other/pkg"}, + }, + { + field: StructField{Name: "s", Type: TypeOf((*s2)(nil)), PkgPath: "other/pkg"}, + }, + { + field: StructField{Name: "", Type: TypeOf(ΦType{})}, + mustPanic: true, + }, + { + field: StructField{Name: "", Type: TypeOf(φType{})}, + mustPanic: true, + }, + { + field: StructField{Name: "Φ", Type: TypeOf(0)}, + exported: true, + }, + { + field: StructField{Name: "φ", Type: TypeOf(0)}, + exported: false, + }, + } + + for i, test := range tests { + testPanic(i, test.mustPanic, func() { + typ := StructOf([]StructField{test.field}) + if typ == nil { + t.Errorf("test-%d: error creating struct type", i) + return + } + field := typ.Field(0) + n := field.Name + if n == "" { + panic("field.Name must not be empty") + } + exported := token.IsExported(n) + if exported != test.exported { + t.Errorf("test-%d: got exported=%v want exported=%v", i, exported, test.exported) + } + if field.PkgPath != test.field.PkgPath { + t.Errorf("test-%d: got PkgPath=%q want pkgPath=%q", i, field.PkgPath, test.field.PkgPath) + } + }) + } +} + +func TestStructOfGC(t *testingT) { + type T *uintptr + tt := TypeOf(T(nil)) + fields := []StructField{ + {Name: "X", Type: tt}, + {Name: "Y", Type: tt}, + } + st := StructOf(fields) + + const n = 10000 + var x []any + for i := 0; i < n; i++ { + v := New(st).Elem() + for j := 0; j < v.NumField(); j++ { + p := new(uintptr) + *p = uintptr(i*n + j) + v.Field(j).Set(ValueOf(p).Convert(tt)) + } + x = append(x, v.Interface()) + } + runtime.GC() + + for i, xi := range x { + v := ValueOf(xi) + for j := 0; j < v.NumField(); j++ { + k := v.Field(j).Elem().Interface() + if k != uintptr(i*n+j) { + t.Errorf("lost x[%d].%c = %d, want %d", i, "XY"[j], k, i*n+j) + } + } + } +} + +func TestStructOfAlg(t *testingT) { + st := StructOf([]StructField{{Name: "X", Tag: "x", Type: TypeOf(int(0))}}) + v1 := New(st).Elem() + v2 := New(st).Elem() + if !DeepEqual(v1.Interface(), v1.Interface()) { + t.Errorf("constructed struct %v not equal to itself", v1.Interface()) + } + v1.FieldByName("X").Set(ValueOf(int(1))) + if i1, i2 := v1.Interface(), v2.Interface(); DeepEqual(i1, i2) { + t.Errorf("constructed structs %v and %v should not be equal", i1, i2) + } + + st = StructOf([]StructField{{Name: "X", Tag: "x", Type: TypeOf([]int(nil))}}) + v1 = New(st).Elem() + shouldPanic("", func() { _ = v1.Interface() == v1.Interface() }) +} + +func TestStructOfGenericAlg(t *testingT) { + st1 := StructOf([]StructField{ + {Name: "X", Tag: "x", Type: TypeOf(int64(0))}, + {Name: "Y", Type: TypeOf(string(""))}, + }) + st := StructOf([]StructField{ + {Name: "S0", Type: st1}, + {Name: "S1", Type: st1}, + }) + + tests := []struct { + rt Type + idx []int + }{ + { + rt: st, + idx: []int{0, 1}, + }, + { + rt: st1, + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf([0]int{})}, + {Name: "YY", Type: TypeOf("")}, + }, + ), + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf([0]int{})}, + {Name: "YY", Type: TypeOf("")}, + {Name: "ZZ", Type: TypeOf([2]int{})}, + }, + ), + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf([1]int{})}, + {Name: "YY", Type: TypeOf("")}, + }, + ), + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf([1]int{})}, + {Name: "YY", Type: TypeOf("")}, + {Name: "ZZ", Type: TypeOf([1]int{})}, + }, + ), + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf([2]int{})}, + {Name: "YY", Type: TypeOf("")}, + {Name: "ZZ", Type: TypeOf([2]int{})}, + }, + ), + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf(int64(0))}, + {Name: "YY", Type: TypeOf(byte(0))}, + {Name: "ZZ", Type: TypeOf("")}, + }, + ), + idx: []int{2}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf(int64(0))}, + {Name: "YY", Type: TypeOf(int64(0))}, + {Name: "ZZ", Type: TypeOf("")}, + {Name: "AA", Type: TypeOf([1]int64{})}, + }, + ), + idx: []int{2}, + }, + } + + for _, table := range tests { + v1 := New(table.rt).Elem() + v2 := New(table.rt).Elem() + + if !DeepEqual(v1.Interface(), v1.Interface()) { + t.Errorf("constructed struct %v not equal to itself", v1.Interface()) + } + + v1.FieldByIndex(table.idx).Set(ValueOf("abc")) + v2.FieldByIndex(table.idx).Set(ValueOf("def")) + if i1, i2 := v1.Interface(), v2.Interface(); DeepEqual(i1, i2) { + t.Errorf("constructed structs %v and %v should not be equal", i1, i2) + } + + abc := "abc" + v1.FieldByIndex(table.idx).Set(ValueOf(abc)) + val := "+" + abc + "-" + v2.FieldByIndex(table.idx).Set(ValueOf(val[1:4])) + if i1, i2 := v1.Interface(), v2.Interface(); !DeepEqual(i1, i2) { + t.Errorf("constructed structs %v and %v should be equal", i1, i2) + } + + // Test hash + m := MakeMap(MapOf(table.rt, TypeOf(int(0)))) + m.SetMapIndex(v1, ValueOf(1)) + if i1, i2 := v1.Interface(), v2.Interface(); !m.MapIndex(v2).IsValid() { + t.Errorf("constructed structs %#v and %#v have different hashes", i1, i2) + } + + v2.FieldByIndex(table.idx).Set(ValueOf("abc")) + if i1, i2 := v1.Interface(), v2.Interface(); !DeepEqual(i1, i2) { + t.Errorf("constructed structs %v and %v should be equal", i1, i2) + } + + if i1, i2 := v1.Interface(), v2.Interface(); !m.MapIndex(v2).IsValid() { + t.Errorf("constructed structs %v and %v have different hashes", i1, i2) + } + } +} + +func TestStructOfDirectIface(t *testingT) { + { + type T struct{ X [1]*byte } + i1 := Zero(TypeOf(T{})).Interface() + v1 := ValueOf(&i1).Elem() + p1 := v1.InterfaceData()[1] + + i2 := Zero(StructOf([]StructField{ + { + Name: "X", + Type: ArrayOf(1, TypeOf((*int8)(nil))), + }, + })).Interface() + v2 := ValueOf(&i2).Elem() + p2 := v2.InterfaceData()[1] + + if p1 != 0 { + t.Errorf("got p1=%v. want=%v", p1, nil) + } + + if p2 != 0 { + t.Errorf("got p2=%v. want=%v", p2, nil) + } + } + { + type T struct{ X [0]*byte } + i1 := Zero(TypeOf(T{})).Interface() + v1 := ValueOf(&i1).Elem() + p1 := v1.InterfaceData()[1] + + i2 := Zero(StructOf([]StructField{ + { + Name: "X", + Type: ArrayOf(0, TypeOf((*int8)(nil))), + }, + })).Interface() + v2 := ValueOf(&i2).Elem() + p2 := v2.InterfaceData()[1] + + if p1 == 0 { + t.Errorf("got p1=%v. want=not-%v", p1, nil) + } + + if p2 == 0 { + t.Errorf("got p2=%v. want=not-%v", p2, nil) + } + } +} + +type StructI int + +func (i StructI) Get() int { return int(i) } + +type StructIPtr int + +func (i *StructIPtr) Get() int { return int(*i) } +func (i *StructIPtr) Set(v int) { *(*int)(i) = v } + +type SettableStruct struct { + SettableField int +} + +func (p *SettableStruct) Set(v int) { p.SettableField = v } + +type SettablePointer struct { + SettableField *int +} + +func (p *SettablePointer) Set(v int) { *p.SettableField = v } + +func TestStructOfWithInterface(t *testingT) { + const want = 42 + type Iface interface { + Get() int + } + type IfaceSet interface { + Set(int) + } + tests := []struct { + name string + typ Type + val Value + impl bool + }{ + { + name: "StructI", + typ: TypeOf(StructI(want)), + val: ValueOf(StructI(want)), + impl: true, + }, + { + name: "StructI", + typ: PointerTo(TypeOf(StructI(want))), + val: ValueOf(func() any { + v := StructI(want) + return &v + }()), + impl: true, + }, + { + name: "StructIPtr", + typ: PointerTo(TypeOf(StructIPtr(want))), + val: ValueOf(func() any { + v := StructIPtr(want) + return &v + }()), + impl: true, + }, + { + name: "StructIPtr", + typ: TypeOf(StructIPtr(want)), + val: ValueOf(StructIPtr(want)), + impl: false, + }, + // { + // typ: TypeOf((*Iface)(nil)).Elem(), // FIXME(sbinet): fix method.ifn/tfn + // val: ValueOf(StructI(want)), + // impl: true, + // }, + } + + for i, table := range tests { + for j := 0; j < 2; j++ { + var fields []StructField + if j == 1 { + fields = append(fields, StructField{ + Name: "Dummy", + PkgPath: "", + Type: TypeOf(int(0)), + }) + } + fields = append(fields, StructField{ + Name: table.name, + Anonymous: true, + PkgPath: "", + Type: table.typ, + }) + + // We currently do not correctly implement methods + // for embedded fields other than the first. + // Therefore, for now, we expect those methods + // to not exist. See issues 15924 and 20824. + // When those issues are fixed, this test of panic + // should be removed. + if j == 1 && table.impl { + func() { + defer func() { + if err := recover(); err == nil { + t.Errorf("test-%d-%d did not panic", i, j) + } + }() + _ = StructOf(fields) + }() + continue + } + + rt := StructOf(fields) + rv := New(rt).Elem() + rv.Field(j).Set(table.val) + + if _, ok := rv.Interface().(Iface); ok != table.impl { + if table.impl { + t.Errorf("test-%d-%d: type=%v fails to implement Iface.\n", i, j, table.typ) + } else { + t.Errorf("test-%d-%d: type=%v should NOT implement Iface\n", i, j, table.typ) + } + continue + } + + if !table.impl { + continue + } + + v := rv.Interface().(Iface).Get() + if v != want { + t.Errorf("test-%d-%d: x.Get()=%v. want=%v\n", i, j, v, want) + } + + fct := rv.MethodByName("Get") + out := fct.Call(nil) + if !DeepEqual(out[0].Interface(), want) { + t.Errorf("test-%d-%d: x.Get()=%v. want=%v\n", i, j, out[0].Interface(), want) + } + } + } + + // Test an embedded nil pointer with pointer methods. + fields := []StructField{{ + Name: "StructIPtr", + Anonymous: true, + Type: PointerTo(TypeOf(StructIPtr(want))), + }} + rt := StructOf(fields) + rv := New(rt).Elem() + // This should panic since the pointer is nil. + shouldPanic("", func() { + rv.Interface().(IfaceSet).Set(want) + }) + + // Test an embedded nil pointer to a struct with pointer methods. + + fields = []StructField{{ + Name: "SettableStruct", + Anonymous: true, + Type: PointerTo(TypeOf(SettableStruct{})), + }} + rt = StructOf(fields) + rv = New(rt).Elem() + // This should panic since the pointer is nil. + shouldPanic("", func() { + rv.Interface().(IfaceSet).Set(want) + }) + + // The behavior is different if there is a second field, + // since now an interface value holds a pointer to the struct + // rather than just holding a copy of the struct. + fields = []StructField{ + { + Name: "SettableStruct", + Anonymous: true, + Type: PointerTo(TypeOf(SettableStruct{})), + }, + { + Name: "EmptyStruct", + Anonymous: true, + Type: StructOf(nil), + }, + } + // With the current implementation this is expected to panic. + // Ideally it should work and we should be able to see a panic + // if we call the Set method. + shouldPanic("", func() { + StructOf(fields) + }) + + // Embed a field that can be stored directly in an interface, + // with a second field. + fields = []StructField{ + { + Name: "SettablePointer", + Anonymous: true, + Type: TypeOf(SettablePointer{}), + }, + { + Name: "EmptyStruct", + Anonymous: true, + Type: StructOf(nil), + }, + } + // With the current implementation this is expected to panic. + // Ideally it should work and we should be able to call the + // Set and Get methods. + shouldPanic("", func() { + StructOf(fields) + }) +} + +func TestStructOfTooManyFields(t *testingT) { + // Bug Fix: #25402 - this should not panic + tt := StructOf([]StructField{ + {Name: "Time", Type: TypeOf(time.Time{}), Anonymous: true}, + }) + + if _, present := tt.MethodByName("After"); !present { + t.Errorf("Expected method `After` to be found") + } +} + +func TestStructOfDifferentPkgPath(t *testingT) { + fields := []StructField{ + { + Name: "f1", + PkgPath: "p1", + Type: TypeOf(int(0)), + }, + { + Name: "f2", + PkgPath: "p2", + Type: TypeOf(int(0)), + }, + } + shouldPanic("different PkgPath", func() { + StructOf(fields) + }) +} + +func TestStructOfTooLarge(t *testingT) { + t1 := TypeOf(byte(0)) + t2 := TypeOf(int16(0)) + t4 := TypeOf(int32(0)) + t0 := ArrayOf(0, t1) + + // 2^64-3 sized type (or 2^32-3 on 32-bit archs) + bigType := StructOf([]StructField{ + {Name: "F1", Type: ArrayOf(int(^uintptr(0)>>1), t1)}, + {Name: "F2", Type: ArrayOf(int(^uintptr(0)>>1-1), t1)}, + }) + + type test struct { + shouldPanic bool + fields []StructField + } + + tests := [...]test{ + { + shouldPanic: false, // 2^64-1, ok + fields: []StructField{ + {Name: "F1", Type: bigType}, + {Name: "F2", Type: ArrayOf(2, t1)}, + }, + }, + { + shouldPanic: true, // overflow in total size + fields: []StructField{ + {Name: "F1", Type: bigType}, + {Name: "F2", Type: ArrayOf(3, t1)}, + }, + }, + { + shouldPanic: true, // overflow while aligning F2 + fields: []StructField{ + {Name: "F1", Type: bigType}, + {Name: "F2", Type: t4}, + }, + }, + { + shouldPanic: true, // overflow while adding trailing byte for zero-sized fields + fields: []StructField{ + {Name: "F1", Type: bigType}, + {Name: "F2", Type: ArrayOf(2, t1)}, + {Name: "F3", Type: t0}, + }, + }, + { + shouldPanic: true, // overflow while aligning total size + fields: []StructField{ + {Name: "F1", Type: t2}, + {Name: "F2", Type: bigType}, + }, + }, + } + + for i, tt := range tests { + func() { + defer func() { + err := recover() + if !tt.shouldPanic { + if err != nil { + t.Errorf("test %d should not panic, got %s", i, err) + } + return + } + if err == nil { + t.Errorf("test %d expected to panic", i) + return + } + s := fmt.Sprintf("%s", err) + if s != "reflect.StructOf: struct size would exceed virtual address space" { + t.Errorf("test %d wrong panic message: %s", i, s) + return + } + }() + _ = StructOf(tt.fields) + }() + } +} + +type D1 struct { + d int +} +type D2 struct { + d int +} + +func TestStructOfAnonymous(t *testingT) { + var s any = struct{ D1 }{} + f := TypeOf(s).Field(0) + ds := StructOf([]StructField{f}) + st := TypeOf(s) + dt := New(ds).Elem() + if st != dt.Type() { + t.Errorf("StructOf returned %s, want %s", dt.Type(), st) + } + + // This should not panic. + _ = dt.Interface().(struct{ D1 }) +} + +func TestFuncOf(t *testingT) { + // check construction and use of type not in binary + type K string + type V float64 + + fn := func(args []Value) []Value { + if len(args) != 1 { + t.Errorf("args == %v, want exactly one arg", args) + } else if args[0].Type() != TypeOf(K("")) { + t.Errorf("args[0] is type %v, want %v", args[0].Type(), TypeOf(K(""))) + } else if args[0].String() != "gopher" { + t.Errorf("args[0] = %q, want %q", args[0].String(), "gopher") + } + return []Value{ValueOf(V(3.14))} + } + v := MakeFunc(FuncOf([]Type{TypeOf(K(""))}, []Type{TypeOf(V(0))}, false), fn) + + outs := v.Call([]Value{ValueOf(K("gopher"))}) + if len(outs) != 1 { + t.Fatalf("v.Call returned %v, want exactly one result", outs) + } else if outs[0].Type() != TypeOf(V(0)) { + t.Fatalf("c.Call[0] is type %v, want %v", outs[0].Type(), TypeOf(V(0))) + } + f := outs[0].Float() + if f != 3.14 { + t.Errorf("constructed func returned %f, want %f", f, 3.14) + } + + // check that types already in binary are found + type T1 int + testCases := []struct { + in, out []Type + variadic bool + want any + }{ + {in: []Type{TypeOf(T1(0))}, want: (func(T1))(nil)}, + {in: []Type{TypeOf(int(0))}, want: (func(int))(nil)}, + {in: []Type{SliceOf(TypeOf(int(0)))}, variadic: true, want: (func(...int))(nil)}, + {in: []Type{TypeOf(int(0))}, out: []Type{TypeOf(false)}, want: (func(int) bool)(nil)}, + {in: []Type{TypeOf(int(0))}, out: []Type{TypeOf(false), TypeOf("")}, want: (func(int) (bool, string))(nil)}, + } + for _, tt := range testCases { + checkSameType(t, FuncOf(tt.in, tt.out, tt.variadic), tt.want) + } + + // check that variadic requires last element be a slice. + FuncOf([]Type{TypeOf(1), TypeOf(""), SliceOf(TypeOf(false))}, nil, true) + shouldPanic("must be slice", func() { FuncOf([]Type{TypeOf(0), TypeOf(""), TypeOf(false)}, nil, true) }) + shouldPanic("must be slice", func() { FuncOf(nil, nil, true) }) + + //testcase for #54669 + var in []Type + for i := 0; i < 51; i++ { + in = append(in, TypeOf(1)) + } + FuncOf(in, nil, false) +} + +/* +func TestChanOf(t *testingT) { + // check construction and use of type not in binary + type T string + ct := ChanOf(BothDir, TypeOf(T(""))) + v := MakeChan(ct, 2) + runtime.GC() + v.Send(ValueOf(T("hello"))) + runtime.GC() + v.Send(ValueOf(T("world"))) + runtime.GC() + + sv1, _ := v.Recv() + sv2, _ := v.Recv() + s1 := sv1.String() + s2 := sv2.String() + if s1 != "hello" || s2 != "world" { + t.Errorf("constructed chan: have %q, %q, want %q, %q", s1, s2, "hello", "world") + } + + // check that type already in binary is found + type T1 int + checkSameType(t, ChanOf(BothDir, TypeOf(T1(1))), (chan T1)(nil)) + + // Check arrow token association in undefined chan types. + var left chan<- chan T + var right chan (<-chan T) + tLeft := ChanOf(SendDir, ChanOf(BothDir, TypeOf(T("")))) + tRight := ChanOf(BothDir, ChanOf(RecvDir, TypeOf(T("")))) + if tLeft != TypeOf(left) { + t.Errorf("chan<-chan: have %s, want %T", tLeft, left) + } + if tRight != TypeOf(right) { + t.Errorf("chan<-chan: have %s, want %T", tRight, right) + } +} +*/ + +func TestChanOfDir(t *testingT) { + // check construction and use of type not in binary + type T string + crt := ChanOf(RecvDir, TypeOf(T(""))) + cst := ChanOf(SendDir, TypeOf(T(""))) + + // check that type already in binary is found + type T1 int + checkSameType(t, ChanOf(RecvDir, TypeOf(T1(1))), (<-chan T1)(nil)) + checkSameType(t, ChanOf(SendDir, TypeOf(T1(1))), (chan<- T1)(nil)) + + // check String form of ChanDir + if crt.ChanDir().String() != "<-chan" { + t.Errorf("chan dir: have %q, want %q", crt.ChanDir().String(), "<-chan") + } + if cst.ChanDir().String() != "chan<-" { + t.Errorf("chan dir: have %q, want %q", cst.ChanDir().String(), "chan<-") + } +} + +/* +func TestChanOfGC(t *testingT) { + done := make(chan bool, 1) + go func() { + select { + case <-done: + case <-time.After(5 * time.Second): + panic("deadlock in TestChanOfGC") + } + }() + + defer func() { + done <- true + }() + + type T *uintptr + tt := TypeOf(T(nil)) + ct := ChanOf(BothDir, tt) + + // NOTE: The garbage collector handles allocated channels specially, + // so we have to save pointers to channels in x; the pointer code will + // use the gc info in the newly constructed chan type. + const n = 100 + var x []any + for i := 0; i < n; i++ { + v := MakeChan(ct, n) + for j := 0; j < n; j++ { + p := new(uintptr) + *p = uintptr(i*n + j) + v.Send(ValueOf(p).Convert(tt)) + } + pv := New(ct) + pv.Elem().Set(v) + x = append(x, pv.Interface()) + } + runtime.GC() + + for i, xi := range x { + v := ValueOf(xi).Elem() + for j := 0; j < n; j++ { + pv, _ := v.Recv() + k := pv.Elem().Interface() + if k != uintptr(i*n+j) { + t.Errorf("lost x[%d][%d] = %d, want %d", i, j, k, i*n+j) + } + } + } +} +*/ + +func shouldPanic(expect string, f func()) { + defer func() { + r := recover() + if r == nil { + panic("did not panic") + } + if expect != "" { + var s string + switch r := r.(type) { + case string: + s = r + case *ValueError: + s = r.Error() + default: + panic(fmt.Sprintf("panicked with unexpected type %T", r)) + } + if !strings.HasPrefix(s, "reflect") { + panic(`panic string does not start with "reflect": ` + s) + } + if !strings.Contains(s, expect) { + panic(`panic string does not contain "` + expect + `": ` + s) + } + } + }() + f() +} diff --git a/_demo/go/reflectmakefn/main.go b/_demo/go/reflectmakefn/main.go new file mode 100644 index 0000000000..bda56fb6c9 --- /dev/null +++ b/_demo/go/reflectmakefn/main.go @@ -0,0 +1,26 @@ +package main + +import "reflect" + +func demo(fn func(n int) int) func(n int) int { + return func(n int) int { + return fn(n + 100) + } +} + +func main() { + var base = 100 + fn := func(n int) int { + return n + base + } + f := reflect.MakeFunc(reflect.TypeOf(demo), func(args []reflect.Value) []reflect.Value { + fn := reflect.ValueOf(func(n int) int { + return args[0].Interface().(func(int) int)(n + 100) + }) + return []reflect.Value{fn} + }) + r := f.Call([]reflect.Value{reflect.ValueOf(fn)}) + if r[0].Call([]reflect.Value{reflect.ValueOf(100)})[0].Int() != 300 { + panic("call fn error") + } +} diff --git a/_demo/go/reflectmethod/main.go b/_demo/go/reflectmethod/main.go new file mode 100644 index 0000000000..dff4c5e666 --- /dev/null +++ b/_demo/go/reflectmethod/main.go @@ -0,0 +1,1145 @@ +package main + +import ( + "fmt" + "log" + . "reflect" + "runtime" + "strings" +) + +type testingT struct { +} + +func (t *testingT) Errorf(format string, args ...any) { + log.Panicf(format, args...) +} + +func (t *testingT) Fatal(args ...any) { + log.Panic(args...) +} + +func (t *testingT) Fatalf(format string, args ...any) { + log.Panicf(format, args...) +} + +func main() { + var t testingT + TestMethod(&t) + TestMethodValue(&t) + TestVariadicMethodValue(&t) + TestDirectIfaceMethod(&t) + TestMethod5(&t) + TestMethodSmall(&t) + TestMethodFloat(&t) +} + +func shouldPanic(expect string, f func()) { + defer func() { + r := recover() + if r == nil { + panic("did not panic") + } + if expect != "" { + var s string + switch r := r.(type) { + case string: + s = r + case *ValueError: + s = r.Error() + default: + panic(fmt.Sprintf("panicked with unexpected type %T", r)) + } + if !strings.HasPrefix(s, "reflect") { + panic(`panic string does not start with "reflect": ` + s) + } + if !strings.Contains(s, expect) { + panic(`panic string does not contain "` + expect + `": ` + s) + } + } + }() + f() +} + +type Point struct { + x, y int +} + +// This will be index 0. +func (p Point) AnotherMethod(scale int) int { + return -1 +} + +// This will be index 1. +func (p Point) Dist(scale int) int { + //println("Point.Dist", p.x, p.y, scale) + return p.x*p.x*scale + p.y*p.y*scale +} + +// This will be index 2. +func (p Point) GCMethod(k int) int { + runtime.GC() + return k + p.x +} + +// This will be index 3. +func (p Point) NoArgs() { + // Exercise no-argument/no-result paths. +} + +// This will be index 4. +func (p Point) TotalDist(points ...Point) int { + tot := 0 + for _, q := range points { + dx := q.x - p.x + dy := q.y - p.y + tot += dx*dx + dy*dy // Should call Sqrt, but it's just a test. + + } + return tot +} + +// This will be index 5. +func (p *Point) Int64Method(x int64) int64 { + return x +} + +// This will be index 6. +func (p *Point) Int32Method(x int32) int32 { + return x +} + +func TestMethod(t *testingT) { + // Non-curried method of type. + p := Point{3, 4} + i := TypeOf(p).Method(1).Func.Call([]Value{ValueOf(p), ValueOf(10)})[0].Int() + if i != 250 { + t.Errorf("Type Method returned %d; want 250", i) + } + + m, ok := TypeOf(p).MethodByName("Dist") + if !ok { + t.Fatalf("method by name failed") + } + i = m.Func.Call([]Value{ValueOf(p), ValueOf(11)})[0].Int() + if i != 275 { + t.Errorf("Type MethodByName returned %d; want 275", i) + } + + m, ok = TypeOf(p).MethodByName("NoArgs") + if !ok { + t.Fatalf("method by name failed") + } + n := len(m.Func.Call([]Value{ValueOf(p)})) + if n != 0 { + t.Errorf("NoArgs returned %d values; want 0", n) + } + + i = TypeOf(&p).Method(1).Func.Call([]Value{ValueOf(&p), ValueOf(12)})[0].Int() + if i != 300 { + t.Errorf("Pointer Type Method returned %d; want 300", i) + } + + m, ok = TypeOf(&p).MethodByName("Dist") + if !ok { + t.Fatalf("ptr method by name failed") + } + i = m.Func.Call([]Value{ValueOf(&p), ValueOf(13)})[0].Int() + if i != 325 { + t.Errorf("Pointer Type MethodByName returned %d; want 325", i) + } + + m, ok = TypeOf(&p).MethodByName("NoArgs") + if !ok { + t.Fatalf("method by name failed") + } + n = len(m.Func.Call([]Value{ValueOf(&p)})) + if n != 0 { + t.Errorf("NoArgs returned %d values; want 0", n) + } + + _, ok = TypeOf(&p).MethodByName("AA") + if ok { + t.Errorf(`MethodByName("AA") should have failed`) + } + + _, ok = TypeOf(&p).MethodByName("ZZ") + if ok { + t.Errorf(`MethodByName("ZZ") should have failed`) + } + + // Curried method of value. + tfunc := TypeOf((func(int) int)(nil)) + v := ValueOf(p).Method(1) + if tt := v.Type(); tt != tfunc { + t.Errorf("Value Method Type is %s; want %s", tt, tfunc) + } + i = v.Call([]Value{ValueOf(14)})[0].Int() + if i != 350 { + t.Errorf("Value Method returned %d; want 350", i) + } + v = ValueOf(p).MethodByName("Dist") + if tt := v.Type(); tt != tfunc { + t.Errorf("Value MethodByName Type is %s; want %s", tt, tfunc) + } + i = v.Call([]Value{ValueOf(15)})[0].Int() + if i != 375 { + t.Errorf("Value MethodByName returned %d; want 375", i) + } + v = ValueOf(p).MethodByName("NoArgs") + v.Call(nil) + + // Curried method of pointer. + v = ValueOf(&p).Method(1) + if tt := v.Type(); tt != tfunc { + t.Errorf("Pointer Value Method Type is %s; want %s", tt, tfunc) + } + i = v.Call([]Value{ValueOf(16)})[0].Int() + if i != 400 { + t.Errorf("Pointer Value Method returned %d; want 400", i) + } + v = ValueOf(&p).MethodByName("Dist") + if tt := v.Type(); tt != tfunc { + t.Errorf("Pointer Value MethodByName Type is %s; want %s", tt, tfunc) + } + i = v.Call([]Value{ValueOf(17)})[0].Int() + if i != 425 { + t.Errorf("Pointer Value MethodByName returned %d; want 425", i) + } + v = ValueOf(&p).MethodByName("NoArgs") + v.Call(nil) + + // Curried method of interface value. + // Have to wrap interface value in a struct to get at it. + // Passing it to ValueOf directly would + // access the underlying Point, not the interface. + var x interface { + Dist(int) int + } = p + pv := ValueOf(&x).Elem() + v = pv.Method(0) + if tt := v.Type(); tt != tfunc { + t.Errorf("Interface Method Type is %s; want %s", tt, tfunc) + } + i = v.Call([]Value{ValueOf(18)})[0].Int() + if i != 450 { + t.Errorf("Interface Method returned %d; want 450", i) + } + v = pv.MethodByName("Dist") + if tt := v.Type(); tt != tfunc { + t.Errorf("Interface MethodByName Type is %s; want %s", tt, tfunc) + } + i = v.Call([]Value{ValueOf(19)})[0].Int() + if i != 475 { + t.Errorf("Interface MethodByName returned %d; want 475", i) + } +} + +func TestMethodValue(t *testingT) { + p := Point{3, 4} + var i int64 + + // Check that method value have the same underlying code pointers. + if p1, p2 := ValueOf(Point{1, 1}).Method(1), ValueOf(Point{2, 2}).Method(1); p1.Pointer() != p2.Pointer() { + t.Errorf("methodValueCall mismatched: %v - %v", p1, p2) + } + + // Curried method of value. + tfunc := TypeOf((func(int) int)(nil)) + v := ValueOf(p).Method(1) + if tt := v.Type(); tt != tfunc { + t.Errorf("Value Method Type is %s; want %s", tt, tfunc) + } + i = ValueOf(v.Interface()).Call([]Value{ValueOf(10)})[0].Int() + if i != 250 { + t.Errorf("Value Method returned %d; want 250", i) + } + v = ValueOf(p).MethodByName("Dist") + if tt := v.Type(); tt != tfunc { + t.Errorf("Value MethodByName Type is %s; want %s", tt, tfunc) + } + i = ValueOf(v.Interface()).Call([]Value{ValueOf(11)})[0].Int() + if i != 275 { + t.Errorf("Value MethodByName returned %d; want 275", i) + } + v = ValueOf(p).MethodByName("NoArgs") + ValueOf(v.Interface()).Call(nil) + v.Interface().(func())() + + // Curried method of pointer. + v = ValueOf(&p).Method(1) + if tt := v.Type(); tt != tfunc { + t.Errorf("Pointer Value Method Type is %s; want %s", tt, tfunc) + } + i = ValueOf(v.Interface()).Call([]Value{ValueOf(12)})[0].Int() + if i != 300 { + t.Errorf("Pointer Value Method returned %d; want 300", i) + } + v = ValueOf(&p).MethodByName("Dist") + if tt := v.Type(); tt != tfunc { + t.Errorf("Pointer Value MethodByName Type is %s; want %s", tt, tfunc) + } + i = ValueOf(v.Interface()).Call([]Value{ValueOf(13)})[0].Int() + if i != 325 { + t.Errorf("Pointer Value MethodByName returned %d; want 325", i) + } + v = ValueOf(&p).MethodByName("NoArgs") + ValueOf(v.Interface()).Call(nil) + v.Interface().(func())() + + // Curried method of pointer to pointer. + pp := &p + v = ValueOf(&pp).Elem().Method(1) + if tt := v.Type(); tt != tfunc { + t.Errorf("Pointer Pointer Value Method Type is %s; want %s", tt, tfunc) + } + i = ValueOf(v.Interface()).Call([]Value{ValueOf(14)})[0].Int() + if i != 350 { + t.Errorf("Pointer Pointer Value Method returned %d; want 350", i) + } + v = ValueOf(&pp).Elem().MethodByName("Dist") + if tt := v.Type(); tt != tfunc { + t.Errorf("Pointer Pointer Value MethodByName Type is %s; want %s", tt, tfunc) + } + i = ValueOf(v.Interface()).Call([]Value{ValueOf(15)})[0].Int() + if i != 375 { + t.Errorf("Pointer Pointer Value MethodByName returned %d; want 375", i) + } + + // Curried method of interface value. + // Have to wrap interface value in a struct to get at it. + // Passing it to ValueOf directly would + // access the underlying Point, not the interface. + var s = struct { + X interface { + Dist(int) int + } + }{p} + pv := ValueOf(s).Field(0) + v = pv.Method(0) + if tt := v.Type(); tt != tfunc { + t.Errorf("Interface Method Type is %s; want %s", tt, tfunc) + } + i = ValueOf(v.Interface()).Call([]Value{ValueOf(16)})[0].Int() + if i != 400 { + t.Errorf("Interface Method returned %d; want 400", i) + } + v = pv.MethodByName("Dist") + if tt := v.Type(); tt != tfunc { + t.Errorf("Interface MethodByName Type is %s; want %s", tt, tfunc) + } + i = ValueOf(v.Interface()).Call([]Value{ValueOf(17)})[0].Int() + if i != 425 { + t.Errorf("Interface MethodByName returned %d; want 425", i) + } + + // For issue #33628: method args are not stored at the right offset + // on amd64p32. + m64 := ValueOf(&p).MethodByName("Int64Method").Interface().(func(int64) int64) + if x := m64(123); x != 123 { + t.Errorf("Int64Method returned %d; want 123", x) + } + m32 := ValueOf(&p).MethodByName("Int32Method").Interface().(func(int32) int32) + if x := m32(456); x != 456 { + t.Errorf("Int32Method returned %d; want 456", x) + } +} + +func TestVariadicMethodValue(t *testingT) { + p := Point{3, 4} + points := []Point{{20, 21}, {22, 23}, {24, 25}} + want := int64(p.TotalDist(points[0], points[1], points[2])) + + // Variadic method of type. + tfunc := TypeOf((func(Point, ...Point) int)(nil)) + if tt := TypeOf(p).Method(4).Type; tt != tfunc { + t.Errorf("Variadic Method Type from TypeOf is %s; want %s", tt, tfunc) + } + + // Curried method of value. + tfunc = TypeOf((func(...Point) int)(nil)) + v := ValueOf(p).Method(4) + if tt := v.Type(); tt != tfunc { + t.Errorf("Variadic Method Type is %s; want %s", tt, tfunc) + } + i := ValueOf(v.Interface()).Call([]Value{ValueOf(points[0]), ValueOf(points[1]), ValueOf(points[2])})[0].Int() + if i != want { + t.Errorf("Variadic Method returned %d; want %d", i, want) + } + i = ValueOf(v.Interface()).CallSlice([]Value{ValueOf(points)})[0].Int() + if i != want { + t.Errorf("Variadic Method CallSlice returned %d; want %d", i, want) + } + + f := v.Interface().(func(...Point) int) + i = int64(f(points[0], points[1], points[2])) + if i != want { + t.Errorf("Variadic Method Interface returned %d; want %d", i, want) + } + i = int64(f(points...)) + if i != want { + t.Errorf("Variadic Method Interface Slice returned %d; want %d", i, want) + } +} + +type DirectIfaceT struct { + p *int +} + +func (d DirectIfaceT) M() int { return *d.p } + +func TestDirectIfaceMethod(t *testingT) { + x := 42 + v := DirectIfaceT{&x} + typ := TypeOf(v) + m, ok := typ.MethodByName("M") + if !ok { + t.Fatalf("cannot find method M") + } + in := []Value{ValueOf(v)} + out := m.Func.Call(in) + if got := out[0].Int(); got != 42 { + t.Errorf("Call with value receiver got %d, want 42", got) + } + + pv := &v + typ = TypeOf(pv) + m, ok = typ.MethodByName("M") + if !ok { + t.Fatalf("cannot find method M") + } + in = []Value{ValueOf(pv)} + out = m.Func.Call(in) + if got := out[0].Int(); got != 42 { + t.Errorf("Call with pointer receiver got %d, want 42", got) + } +} + +// Reflect version of $GOROOT/test/method5.go + +// Concrete types implementing M method. +// Smaller than a word, word-sized, larger than a word. +// Value and pointer receivers. + +type Tinter interface { + M(int, byte) (byte, int) +} + +type Tsmallv byte + +func (v Tsmallv) M(x int, b byte) (byte, int) { return b, x + int(v) } + +type Tsmallp byte + +func (p *Tsmallp) M(x int, b byte) (byte, int) { return b, x + int(*p) } + +type Twordv uintptr + +func (v Twordv) M(x int, b byte) (byte, int) { return b, x + int(v) } + +type Twordp uintptr + +func (p *Twordp) M(x int, b byte) (byte, int) { return b, x + int(*p) } + +type Tbigv [2]uintptr + +func (v Tbigv) M(x int, b byte) (byte, int) { return b, x + int(v[0]) + int(v[1]) } + +type Tbigp [2]uintptr + +func (p *Tbigp) M(x int, b byte) (byte, int) { return b, x + int(p[0]) + int(p[1]) } + +type tinter interface { + m(int, byte) (byte, int) +} + +// Embedding via pointer. + +type Tm1 struct { + Tm2 +} + +type Tm2 struct { + *Tm3 +} + +type Tm3 struct { + *Tm4 +} + +type Tm4 struct { +} + +func (t4 Tm4) M(x int, b byte) (byte, int) { return b, x + 40 } + +func TestMethod5(t *testingT) { + CheckF := func(name string, f func(int, byte) (byte, int), inc int) { + b, x := f(1000, 99) + if b != 99 || x != 1000+inc { + t.Errorf("%s(1000, 99) = %v, %v, want 99, %v", name, b, x, 1000+inc) + } + } + + CheckV := func(name string, i Value, inc int) { + bx := i.Method(0).Call([]Value{ValueOf(1000), ValueOf(byte(99))}) + b := bx[0].Interface() + x := bx[1].Interface() + if b != byte(99) || x != 1000+inc { + t.Errorf("direct %s.M(1000, 99) = %v, %v, want 99, %v", name, b, x, 1000+inc) + } + + CheckF(name+".M", i.Method(0).Interface().(func(int, byte) (byte, int)), inc) + } + + var TinterType = TypeOf(new(Tinter)).Elem() + + CheckI := func(name string, i any, inc int) { + v := ValueOf(i) + CheckV(name, v, inc) + CheckV("(i="+name+")", v.Convert(TinterType), inc) + } + + sv := Tsmallv(1) + CheckI("sv", sv, 1) + CheckI("&sv", &sv, 1) + + sp := Tsmallp(2) + CheckI("&sp", &sp, 2) + + wv := Twordv(3) + CheckI("wv", wv, 3) + CheckI("&wv", &wv, 3) + + wp := Twordp(4) + CheckI("&wp", &wp, 4) + + bv := Tbigv([2]uintptr{5, 6}) + CheckI("bv", bv, 11) + CheckI("&bv", &bv, 11) + + bp := Tbigp([2]uintptr{7, 8}) + CheckI("&bp", &bp, 15) + + t4 := Tm4{} + t3 := Tm3{&t4} + t2 := Tm2{&t3} + t1 := Tm1{t2} + CheckI("t4", t4, 40) + CheckI("&t4", &t4, 40) + CheckI("t3", t3, 40) + CheckI("&t3", &t3, 40) + CheckI("t2", t2, 40) + CheckI("&t2", &t2, 40) + CheckI("t1", t1, 40) + CheckI("&t1", &t1, 40) + + var tnil Tinter + vnil := ValueOf(&tnil).Elem() + shouldPanic("Method", func() { vnil.Method(0) }) +} + +// Package-level type definitions for StructResult return type +type StructResult struct{ N byte } + +type TinterSmallStruct interface { + M(int, byte) (StructResult, int) +} + +type TsrSmallv byte + +func (v TsrSmallv) M(x int, b byte) (StructResult, int) { + return StructResult{b}, x + int(v) +} + +type TsrSmallp byte + +func (p *TsrSmallp) M(x int, b byte) (StructResult, int) { + return StructResult{b}, x + int(*p) +} + +type TsrWordv uintptr + +func (v TsrWordv) M(x int, b byte) (StructResult, int) { + return StructResult{b}, x + int(v) +} + +type TsrWordp uintptr + +func (p *TsrWordp) M(x int, b byte) (StructResult, int) { + return StructResult{b}, x + int(*p) +} + +type TsrBigv [2]uintptr + +func (v TsrBigv) M(x int, b byte) (StructResult, int) { + return StructResult{b}, x + int(v[0]) + int(v[1]) +} + +type TsrBigp [2]uintptr + +func (p *TsrBigp) M(x int, b byte) (StructResult, int) { + return StructResult{b}, x + int(p[0]) + int(p[1]) +} + +// Package-level type definitions for [1]byte return type +type TinterSmallArray interface { + M(int, byte) ([1]byte, int) +} + +type TarSmallv byte + +func (v TarSmallv) M(x int, b byte) ([1]byte, int) { + return [1]byte{b}, x + int(v) +} + +type TarSmallp byte + +func (p *TarSmallp) M(x int, b byte) ([1]byte, int) { + return [1]byte{b}, x + int(*p) +} + +type TarWordv uintptr + +func (v TarWordv) M(x int, b byte) ([1]byte, int) { + return [1]byte{b}, x + int(v) +} + +type TarWordp uintptr + +func (p *TarWordp) M(x int, b byte) ([1]byte, int) { + return [1]byte{b}, x + int(*p) +} + +type TarBigv [2]uintptr + +func (v TarBigv) M(x int, b byte) ([1]byte, int) { + return [1]byte{b}, x + int(v[0]) + int(v[1]) +} + +type TarBigp [2]uintptr + +func (p *TarBigp) M(x int, b byte) ([1]byte, int) { + return [1]byte{b}, x + int(p[0]) + int(p[1]) +} + +// Embedding via pointer for StructResult return type +type TsrEmb1 struct{ TsrEmb2 } +type TsrEmb2 struct{ *TsrEmb3 } +type TsrEmb3 struct{ *TsrEmb4 } +type TsrEmb4 struct{} + +func (t4 TsrEmb4) M(x int, b byte) (StructResult, int) { + return StructResult{b}, x + 40 +} + +// Embedding via pointer for [1]byte return type +type TarEmb1 struct{ TarEmb2 } +type TarEmb2 struct{ *TarEmb3 } +type TarEmb3 struct{ *TarEmb4 } +type TarEmb4 struct{} + +func (t4 TarEmb4) M(x int, b byte) ([1]byte, int) { + return [1]byte{b}, x + 40 +} + +// TestMethodSmall is similar to TestMethod5 but tests methods returning small aggregates +func TestMethodSmall(t *testingT) { + // Helper function: check return value of small struct + CheckStructF := func(name string, f func(int, byte) (StructResult, int), inc int) { + ret, x := f(1000, 99) + if ret.N != byte(99) || x != 1000+inc { + t.Errorf("%s(1000, 99) = {%v}, %v, want {99}, %v", name, ret.N, x, 1000+inc) + } + } + + // Helper function: check via reflection with small struct return + CheckStructV := func(name string, i Value, inc int) { + bx := i.Method(0).Call([]Value{ValueOf(1000), ValueOf(byte(99))}) + structVal := bx[0] + ret := structVal.Field(0).Interface() + x := bx[1].Interface() + if ret != byte(99) || x != 1000+inc { + t.Errorf("direct %s.M(1000, 99) = {%v}, %v, want {99}, %v", name, ret, x, 1000+inc) + } + CheckStructF(name+".M", i.Method(0).Interface().(func(int, byte) (StructResult, int)), inc) + } + + TinterStructType := TypeOf((*TinterSmallStruct)(nil)).Elem() + + // Helper function: check both direct and interface-converted calls for struct + CheckStructI := func(name string, i any, inc int) { + v := ValueOf(i) + CheckStructV(name, v, inc) + CheckStructV("(i="+name+")", v.Convert(TinterStructType), inc) + } + + // Helper function: check return value of small array + CheckArrayF := func(name string, f func(int, byte) ([1]byte, int), inc int) { + ret, x := f(1000, 99) + if ret[0] != byte(99) || x != 1000+inc { + t.Errorf("%s(1000, 99) = [%v], %v, want [99], %v", name, ret[0], x, 1000+inc) + } + } + + // Helper function: check via reflection with small array return + CheckArrayV := func(name string, i Value, inc int) { + bx := i.Method(0).Call([]Value{ValueOf(1000), ValueOf(byte(99))}) + arrVal := bx[0] + if arrVal.Len() < 1 { + t.Errorf("returned array length insufficient") + return + } + ret := arrVal.Index(0).Interface() + x := bx[1].Interface() + if ret != byte(99) || x != 1000+inc { + t.Errorf("direct %s.M(1000, 99) = [%v], %v, want [99], %v", name, ret, x, 1000+inc) + } + CheckArrayF(name+".M", i.Method(0).Interface().(func(int, byte) ([1]byte, int)), inc) + } + + TinterArrayType := TypeOf((*TinterSmallArray)(nil)).Elem() + + // Helper function: check both direct and interface-converted calls for array + CheckArrayI := func(name string, i any, inc int) { + v := ValueOf(i) + CheckArrayV(name, v, inc) + CheckArrayV("(i="+name+")", v.Convert(TinterArrayType), inc) + } + + // Test cases for StructResult return type + + // Small receiver types (byte) + ssv := TsrSmallv(1) + CheckStructI("ssv", ssv, 1) + CheckStructI("&ssv", &ssv, 1) + + ssp := TsrSmallp(2) + CheckStructI("&ssp", &ssp, 2) + + // Word-sized receiver types (uintptr) + wsv := TsrWordv(3) + CheckStructI("wsv", wsv, 3) + CheckStructI("&wsv", &wsv, 3) + + wsp := TsrWordp(4) + CheckStructI("&wsp", &wsp, 4) + + // Large receiver types ([2]uintptr) + bsv := TsrBigv([2]uintptr{5, 6}) + CheckStructI("bsv", bsv, 11) + CheckStructI("&bsv", &bsv, 11) + + bsp := TsrBigp([2]uintptr{7, 8}) + CheckStructI("&bsp", &bsp, 15) + + // Embedded structs (pointer embedding chain) + tsm4 := TsrEmb4{} + tsm3 := TsrEmb3{&tsm4} + tsm2 := TsrEmb2{&tsm3} + tsm1 := TsrEmb1{tsm2} + CheckStructI("tsm4", tsm4, 40) + CheckStructI("&tsm4", &tsm4, 40) + CheckStructI("tsm3", tsm3, 40) + CheckStructI("&tsm3", &tsm3, 40) + CheckStructI("tsm2", tsm2, 40) + CheckStructI("&tsm2", &tsm2, 40) + CheckStructI("tsm1", tsm1, 40) + CheckStructI("&tsm1", &tsm1, 40) + + // Test cases for [1]byte return type + + // Small receiver types (byte) + sav := TarSmallv(1) + CheckArrayI("sav", sav, 1) + CheckArrayI("&sav", &sav, 1) + + sap := TarSmallp(2) + CheckArrayI("&sap", &sap, 2) + + // Word-sized receiver types (uintptr) + wav := TarWordv(3) + CheckArrayI("wav", wav, 3) + CheckArrayI("&wav", &wav, 3) + + wap := TarWordp(4) + CheckArrayI("&wap", &wap, 4) + + // Large receiver types ([2]uintptr) + bav := TarBigv([2]uintptr{5, 6}) + CheckArrayI("bav", bav, 11) + CheckArrayI("&bav", &bav, 11) + + bap := TarBigp([2]uintptr{7, 8}) + CheckArrayI("&bap", &bap, 15) + + // Embedded structs (pointer embedding chain) + tam4 := TarEmb4{} + tam3 := TarEmb3{&tam4} + tam2 := TarEmb2{&tam3} + tam1 := TarEmb1{tam2} + CheckArrayI("tam4", tam4, 40) + CheckArrayI("&tam4", &tam4, 40) + CheckArrayI("tam3", tam3, 40) + CheckArrayI("&tam3", &tam3, 40) + CheckArrayI("tam2", tam2, 40) + CheckArrayI("&tam2", &tam2, 40) + CheckArrayI("tam1", tam1, 40) + CheckArrayI("&tam1", &tam1, 40) +} + +// Package-level type definitions for float32 return type +type TinterFloat32 interface { + M(int, byte) (float32, int) +} + +type T32Smallv byte + +func (v T32Smallv) M(x int, b byte) (float32, int) { + return float32(b), x + int(v) +} + +type T32Smallp byte + +func (p *T32Smallp) M(x int, b byte) (float32, int) { + return float32(b), x + int(*p) +} + +type T32Wordv uintptr + +func (v T32Wordv) M(x int, b byte) (float32, int) { + return float32(b), x + int(v) +} + +type T32Wordp uintptr + +func (p *T32Wordp) M(x int, b byte) (float32, int) { + return float32(b), x + int(*p) +} + +type T32Bigv [2]uintptr + +func (v T32Bigv) M(x int, b byte) (float32, int) { + return float32(b), x + int(v[0]) + int(v[1]) +} + +type T32Bigp [2]uintptr + +func (p *T32Bigp) M(x int, b byte) (float32, int) { + return float32(b), x + int(p[0]) + int(p[1]) +} + +// Embedding via pointer for float32 return type +type T32Emb1 struct{ T32Emb2 } +type T32Emb2 struct{ *T32Emb3 } +type T32Emb3 struct{ *T32Emb4 } +type T32Emb4 struct{} + +func (t4 T32Emb4) M(x int, b byte) (float32, int) { + return float32(b), x + 40 +} + +// Package-level type definitions for float64 return type +type TinterFloat64 interface { + M(int, byte) (float64, int) +} + +type T64Smallv byte + +func (v T64Smallv) M(x int, b byte) (float64, int) { + return float64(b), x + int(v) +} + +type T64Smallp byte + +func (p *T64Smallp) M(x int, b byte) (float64, int) { + return float64(b), x + int(*p) +} + +type T64Wordv uintptr + +func (v T64Wordv) M(x int, b byte) (float64, int) { + return float64(b), x + int(v) +} + +type T64Wordp uintptr + +func (p *T64Wordp) M(x int, b byte) (float64, int) { + return float64(b), x + int(*p) +} + +type T64Bigv [2]uintptr + +func (v T64Bigv) M(x int, b byte) (float64, int) { + return float64(b), x + int(v[0]) + int(v[1]) +} + +type T64Bigp [2]uintptr + +func (p *T64Bigp) M(x int, b byte) (float64, int) { + return float64(b), x + int(p[0]) + int(p[1]) +} + +// Embedding via pointer for float64 return type +type T64Emb1 struct{ T64Emb2 } +type T64Emb2 struct{ *T64Emb3 } +type T64Emb3 struct{ *T64Emb4 } +type T64Emb4 struct{} + +func (t4 T64Emb4) M(x int, b byte) (float64, int) { + return float64(b), x + 40 +} + +// Package-level type definitions for float32 struct return type +type Float32Struct struct{ N float32 } + +type TinterFloat32Struct interface { + M(int, byte) (Float32Struct, int) +} + +type T32sSmallv byte + +func (v T32sSmallv) M(x int, b byte) (Float32Struct, int) { + return Float32Struct{float32(b)}, x + int(v) +} + +type T32sSmallp byte + +func (p *T32sSmallp) M(x int, b byte) (Float32Struct, int) { + return Float32Struct{float32(b)}, x + int(*p) +} + +type T32sWordv uintptr + +func (v T32sWordv) M(x int, b byte) (Float32Struct, int) { + return Float32Struct{float32(b)}, x + int(v) +} + +type T32sWordp uintptr + +func (p *T32sWordp) M(x int, b byte) (Float32Struct, int) { + return Float32Struct{float32(b)}, x + int(*p) +} + +type T32sBigv [2]uintptr + +func (v T32sBigv) M(x int, b byte) (Float32Struct, int) { + return Float32Struct{float32(b)}, x + int(v[0]) + int(v[1]) +} + +type T32sBigp [2]uintptr + +func (p *T32sBigp) M(x int, b byte) (Float32Struct, int) { + return Float32Struct{float32(b)}, x + int(p[0]) + int(p[1]) +} + +// Embedding via pointer for float32 struct return type +type T32sEmb1 struct{ T32sEmb2 } +type T32sEmb2 struct{ *T32sEmb3 } +type T32sEmb3 struct{ *T32sEmb4 } +type T32sEmb4 struct{} + +func (t4 T32sEmb4) M(x int, b byte) (Float32Struct, int) { + return Float32Struct{float32(b)}, x + 40 +} + +// TestMethodFloat tests methods returning float32, float64, and float32 struct +func TestMethodFloat(t *testingT) { + // Helper function: check return value of float32 + CheckFloat32F := func(name string, f func(int, byte) (float32, int), inc int) { + ret, x := f(1000, 99) + if ret != float32(99) || x != 1000+inc { + t.Errorf("%s(1000, 99) = %v, %v, want 99, %v", name, ret, x, 1000+inc) + } + } + + CheckFloat32V := func(name string, i Value, inc int) { + bx := i.Method(0).Call([]Value{ValueOf(1000), ValueOf(byte(99))}) + ret := bx[0].Interface() + x := bx[1].Interface() + if ret != float32(99) || x != 1000+inc { + t.Errorf("direct %s.M(1000, 99) = %v, %v, want 99, %v", name, ret, x, 1000+inc) + } + CheckFloat32F(name+".M", i.Method(0).Interface().(func(int, byte) (float32, int)), inc) + } + + TinterFloat32Type := TypeOf((*TinterFloat32)(nil)).Elem() + + CheckFloat32I := func(name string, i any, inc int) { + v := ValueOf(i) + CheckFloat32V(name, v, inc) + CheckFloat32V("(i="+name+")", v.Convert(TinterFloat32Type), inc) + } + + // Helper function: check return value of float64 + CheckFloat64F := func(name string, f func(int, byte) (float64, int), inc int) { + ret, x := f(1000, 99) + if ret != float64(99) || x != 1000+inc { + t.Errorf("%s(1000, 99) = %v, %v, want 99, %v", name, ret, x, 1000+inc) + } + } + + CheckFloat64V := func(name string, i Value, inc int) { + bx := i.Method(0).Call([]Value{ValueOf(1000), ValueOf(byte(99))}) + ret := bx[0].Interface() + x := bx[1].Interface() + if ret != float64(99) || x != 1000+inc { + t.Errorf("direct %s.M(1000, 99) = %v, %v, want 99, %v", name, ret, x, 1000+inc) + } + CheckFloat64F(name+".M", i.Method(0).Interface().(func(int, byte) (float64, int)), inc) + } + + TinterFloat64Type := TypeOf((*TinterFloat64)(nil)).Elem() + + CheckFloat64I := func(name string, i any, inc int) { + v := ValueOf(i) + CheckFloat64V(name, v, inc) + CheckFloat64V("(i="+name+")", v.Convert(TinterFloat64Type), inc) + } + + // Helper function: check return value of float32 struct + CheckFloat32StructF := func(name string, f func(int, byte) (Float32Struct, int), inc int) { + ret, x := f(1000, 99) + if ret.N != float32(99) || x != 1000+inc { + t.Errorf("%s(1000, 99) = {%v}, %v, want {99}, %v", name, ret.N, x, 1000+inc) + } + } + + CheckFloat32StructV := func(name string, i Value, inc int) { + bx := i.Method(0).Call([]Value{ValueOf(1000), ValueOf(byte(99))}) + structVal := bx[0] + ret := structVal.Field(0).Interface() + x := bx[1].Interface() + if ret != float32(99) || x != 1000+inc { + t.Errorf("direct %s.M(1000, 99) = {%v}, %v, want {99}, %v", name, ret, x, 1000+inc) + } + CheckFloat32StructF(name+".M", i.Method(0).Interface().(func(int, byte) (Float32Struct, int)), inc) + } + + TinterFloat32StructType := TypeOf((*TinterFloat32Struct)(nil)).Elem() + + CheckFloat32StructI := func(name string, i any, inc int) { + v := ValueOf(i) + CheckFloat32StructV(name, v, inc) + CheckFloat32StructV("(i="+name+")", v.Convert(TinterFloat32StructType), inc) + } + + // Test cases for float32 return type + + // Small receiver types (byte) + s32v := T32Smallv(1) + CheckFloat32I("s32v", s32v, 1) + CheckFloat32I("&s32v", &s32v, 1) + + s32p := T32Smallp(2) + CheckFloat32I("&s32p", &s32p, 2) + + // Word-sized receiver types (uintptr) + w32v := T32Wordv(3) + CheckFloat32I("w32v", w32v, 3) + CheckFloat32I("&w32v", &w32v, 3) + + w32p := T32Wordp(4) + CheckFloat32I("&w32p", &w32p, 4) + + // Large receiver types ([2]uintptr) + b32v := T32Bigv([2]uintptr{5, 6}) + CheckFloat32I("b32v", b32v, 11) + CheckFloat32I("&b32v", &b32v, 11) + + b32p := T32Bigp([2]uintptr{7, 8}) + CheckFloat32I("&b32p", &b32p, 15) + + // Embedded structs (pointer embedding chain) for float32 + t32m4 := T32Emb4{} + t32m3 := T32Emb3{&t32m4} + t32m2 := T32Emb2{&t32m3} + t32m1 := T32Emb1{t32m2} + CheckFloat32I("t32m4", t32m4, 40) + CheckFloat32I("&t32m4", &t32m4, 40) + CheckFloat32I("t32m3", t32m3, 40) + CheckFloat32I("&t32m3", &t32m3, 40) + CheckFloat32I("t32m2", t32m2, 40) + CheckFloat32I("&t32m2", &t32m2, 40) + CheckFloat32I("t32m1", t32m1, 40) + CheckFloat32I("&t32m1", &t32m1, 40) + + // Test cases for float64 return type + + // Small receiver types (byte) + s64v := T64Smallv(1) + CheckFloat64I("s64v", s64v, 1) + CheckFloat64I("&s64v", &s64v, 1) + + s64p := T64Smallp(2) + CheckFloat64I("&s64p", &s64p, 2) + + // Word-sized receiver types (uintptr) + w64v := T64Wordv(3) + CheckFloat64I("w64v", w64v, 3) + CheckFloat64I("&w64v", &w64v, 3) + + w64p := T64Wordp(4) + CheckFloat64I("&w64p", &w64p, 4) + + // Large receiver types ([2]uintptr) + b64v := T64Bigv([2]uintptr{5, 6}) + CheckFloat64I("b64v", b64v, 11) + CheckFloat64I("&b64v", &b64v, 11) + + b64p := T64Bigp([2]uintptr{7, 8}) + CheckFloat64I("&b64p", &b64p, 15) + + // Embedded structs (pointer embedding chain) for float64 + t64m4 := T64Emb4{} + t64m3 := T64Emb3{&t64m4} + t64m2 := T64Emb2{&t64m3} + t64m1 := T64Emb1{t64m2} + CheckFloat64I("t64m4", t64m4, 40) + CheckFloat64I("&t64m4", &t64m4, 40) + CheckFloat64I("t64m3", t64m3, 40) + CheckFloat64I("&t64m3", &t64m3, 40) + CheckFloat64I("t64m2", t64m2, 40) + CheckFloat64I("&t64m2", &t64m2, 40) + CheckFloat64I("t64m1", t64m1, 40) + CheckFloat64I("&t64m1", &t64m1, 40) + + // Test cases for float32 struct return type + + // Small receiver types (byte) + s32sv := T32sSmallv(1) + CheckFloat32StructI("s32sv", s32sv, 1) + CheckFloat32StructI("&s32sv", &s32sv, 1) + + s32sp := T32sSmallp(2) + CheckFloat32StructI("&s32sp", &s32sp, 2) + + // Word-sized receiver types (uintptr) + w32sv := T32sWordv(3) + CheckFloat32StructI("w32sv", w32sv, 3) + CheckFloat32StructI("&w32sv", &w32sv, 3) + + w32sp := T32sWordp(4) + CheckFloat32StructI("&w32sp", &w32sp, 4) + + // Large receiver types ([2]uintptr) + b32sv := T32sBigv([2]uintptr{5, 6}) + CheckFloat32StructI("b32sv", b32sv, 11) + CheckFloat32StructI("&b32sv", &b32sv, 11) + + b32sp := T32sBigp([2]uintptr{7, 8}) + CheckFloat32StructI("&b32sp", &b32sp, 15) + + // Embedded structs (pointer embedding chain) for float32 struct + t32sm4 := T32sEmb4{} + t32sm3 := T32sEmb3{&t32sm4} + t32sm2 := T32sEmb2{&t32sm3} + t32sm1 := T32sEmb1{t32sm2} + CheckFloat32StructI("t32sm4", t32sm4, 40) + CheckFloat32StructI("&t32sm4", &t32sm4, 40) + CheckFloat32StructI("t32sm3", t32sm3, 40) + CheckFloat32StructI("&t32sm3", &t32sm3, 40) + CheckFloat32StructI("t32sm2", t32sm2, 40) + CheckFloat32StructI("&t32sm2", &t32sm2, 40) + CheckFloat32StructI("t32sm1", t32sm1, 40) + CheckFloat32StructI("&t32sm1", &t32sm1, 40) +} diff --git a/_demo/go/reflectname-1412/main.go b/_demo/go/reflectname-1412/main.go new file mode 100644 index 0000000000..e3e67b9641 --- /dev/null +++ b/_demo/go/reflectname-1412/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "fmt" + "reflect" +) + +func main() { + value := 42 + rv := reflect.ValueOf(value) + rt := reflect.TypeOf(value) + + fmt.Printf("Value: %v\n", rv.Interface()) + fmt.Printf("Kind: %v\n", rt.Kind()) + fmt.Printf("Name: %v\n", rt.Name()) + + if rt.Kind() != reflect.Int { + panic(fmt.Sprintf("Expected kind Int, got %v", rt.Kind())) + } + if rt.Name() != "int" { + panic(fmt.Sprintf("Expected name int, got %v", rt.Name())) + } + + fmt.Println("✓ Reflect test passed!") +} diff --git a/_demo/go/reflectnamedfn/main.go b/_demo/go/reflectnamedfn/main.go new file mode 100644 index 0000000000..83a056379b --- /dev/null +++ b/_demo/go/reflectnamedfn/main.go @@ -0,0 +1,84 @@ +package main + +import ( + "reflect" +) + +func demo(n int, s string) (bool, int) { + return true, n + len(s) +} + +//llgo:type C +type CFunc func(n int, s string) (bool, int) + +type T func(n int, s string) (bool, int) + +func (t T) Demo() int { + return 100 +} + +func (t T) Call(s string) (bool, int) { + return t(100, s) +} + +func main() { + v1 := reflect.ValueOf(demo) + typ := reflect.TypeOf((*T)(nil)).Elem() + if typ.Kind() != reflect.Func { + panic("kind error: " + typ.Kind().String()) + } + if typ.NumIn() != 2 { + panic("bad num in") + } + if typ.NumOut() != 2 { + panic("bad num out") + } + if typ.IsVariadic() { + panic("not variadic") + } + if typ.NumMethod() != 2 { + panic("error methods") + } + v2 := reflect.New(typ).Elem() + if v2.Type() != typ { + panic("bad type") + } + v2.Set(v1) + check(v2, "named") + + r := v2.Method(1).Call(nil) + if r[0].Int() != 100 { + panic("error call") + } + r = v2.MethodByName("Call").Call([]reflect.Value{reflect.ValueOf("hello")}) + if r[0].Bool() != true { + panic("error r[0]") + } + if r[1].Int() != 100+5 { + panic("error r[1]") + } + + ctyp := reflect.TypeOf((*CFunc)(nil)).Elem() + if ctyp.Kind() != reflect.Func { + panic("kind error: " + ctyp.Kind().String()) + } + v3 := reflect.New(ctyp).Elem() + if v3.Type() != ctyp { + panic("bad c named type") + } + v3.Set(v1) + check(v3, "c named") +} + +func check(v reflect.Value, s string) { + if v.Kind() != reflect.Func { + panic("error") + } + r := v.Call([]reflect.Value{reflect.ValueOf(100), reflect.ValueOf("hello")}) + if r[0].Bool() != true { + panic("error r[0]: " + s) + } + if r[1].Int() != 100+5 { + panic("error r[1]: " + s) + } +} diff --git a/_demo/go/reflectnew/main.go b/_demo/go/reflectnew/main.go new file mode 100644 index 0000000000..ccb9027fee --- /dev/null +++ b/_demo/go/reflectnew/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "reflect" + "unsafe" +) + +func demo(n int, s string) (bool, int) { + return true, n + len(s) +} + +func main() { + v1 := reflect.ValueOf(demo) + check(v1, "demo") + + fn := func(n int, s string) (bool, int) { + return true, n + len(s) + } + v2 := reflect.ValueOf(fn) + check(v2, "fn") + + nv1 := reflect.New(v1.Type()).Elem() + nv1.Set(v1) + check(nv1, "reflect.New demo") + + nv2 := reflect.New(v2.Type()).Elem() + nv2.Set(v2) + check(nv2, "reflect.New closure") + + _demo := demo + nv3 := reflect.NewAt(v1.Type(), unsafe.Pointer(&_demo)).Elem() + check(nv3, "reflect.NewAt demo") + + nv4 := reflect.NewAt(v2.Type(), unsafe.Pointer(&fn)).Elem() + check(nv4, "reflect.NewAt closure") +} + +func check(v reflect.Value, s string) { + r := v.Call([]reflect.Value{reflect.ValueOf(100), reflect.ValueOf("hello")}) + if r[0].Bool() != true { + panic("error r[0]: " + s) + } + if r[1].Int() != 100+5 { + panic("error r[1]: " + s) + } +} diff --git a/_demo/go/reflectpkgpath/main.go b/_demo/go/reflectpkgpath/main.go new file mode 100644 index 0000000000..a16e842da8 --- /dev/null +++ b/_demo/go/reflectpkgpath/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "reflect" + "unsafe" +) + +func main() { + demo1() + demo2() +} + +func demo1() { + type T unsafe.Pointer + t := reflect.TypeOf(unsafe.Pointer(nil)) + t1 := reflect.TypeOf(T(nil)) + if t.Name() != "Pointer" { + panic("error: " + t.Name()) + } + if t.PkgPath() != "unsafe" { + panic("error: " + t.PkgPath()) + } + if t1.Name() != "T" { + panic("error: " + t1.Name()) + } + if t1.PkgPath() == "unsafe" { + panic("error: bad pkgpath") + } +} + +type Point struct { + X int + Y int +} + +func (pt *Point) Set(x, y int) { + pt.X, pt.Y = x, y +} + +type My interface { + Demo() +} + +func demo2() { + typ1 := reflect.TypeOf((*My)(nil)).Elem() + typ2 := reflect.TypeOf((*Point)(nil)).Elem() + if typ1.Name() != "My" { + panic("error typ1 name") + } + if typ2.Name() != "Point" { + panic("error typ2 name") + } + if typ1.PkgPath() == "" { + panic("error typ1 pkgpath") + } + if typ2.PkgPath() == "" { + panic("error typ2 pkgpath") + } + if typ1.PkgPath() != typ2.PkgPath() { + panic("error pkgpath") + } + if typ1.NumMethod() != 1 { + panic("error typ1 num method") + } + if typ2.NumMethod() != 0 { + panic("error typ2 num method") + } + if reflect.PointerTo(typ2).NumMethod() != 1 { + panic("error *typ2 num method") + } +} diff --git a/_demo/go/reflectpointerto/main.go b/_demo/go/reflectpointerto/main.go new file mode 100644 index 0000000000..15ce3a7fcc --- /dev/null +++ b/_demo/go/reflectpointerto/main.go @@ -0,0 +1,72 @@ +package main + +import ( + "log" + "reflect" +) + +func main() { + PointerTo() + AddrOnPointerField() + PointerToDynamic() + NamedPointer() +} + +func PointerTo() { + got := reflect.PointerTo(reflect.TypeOf((*int)(nil))) + want := reflect.TypeOf((**int)(nil)) + if got != want { + log.Panicf("PointerTo(*int) = %v, want %v\n", got, want) + } +} + +func AddrOnPointerField() { + type S struct{ N *int } + v := reflect.ValueOf(&S{}).Elem().Field(0).Addr().Type() + want := reflect.TypeOf((**int)(nil)) + if v != want { + log.Panicf("Addr().Type() = %v, want %v\n", v, want) + } +} + +type T struct{} + +func PointerToDynamic() { + t := reflect.TypeOf(T{}) + st := reflect.SliceOf(t) + s := st.String() + pst := reflect.PointerTo(st) + if pst.String() != "*"+s { + panic(pst.String()) + } + ppst := reflect.PointerTo(pst) + if ppst.String() != "**"+s { + panic(ppst.String()) + } + pppst := reflect.PointerTo(ppst) + if pppst.String() != "***"+s { + panic(pppst.String()) + } + ppppst := reflect.PointerTo(pppst) + if ppppst.String() != "****"+s { + panic(ppppst.String()) + } +} + +type Ptr *int + +func NamedPointer() { + t := reflect.TypeOf(Ptr(nil)) + s := t.String() + if s[0] == '*' { + panic(s) + } + pt := reflect.PointerTo(t) + if pt.String() != "*"+s { + panic(pt.String()) + } + ppt := reflect.PointerTo(pt) + if ppt.String() != "**"+s { + panic(ppt.String()) + } +} diff --git a/_demo/go/reflectslice/main.go b/_demo/go/reflectslice/main.go new file mode 100644 index 0000000000..c31573bc7b --- /dev/null +++ b/_demo/go/reflectslice/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "reflect" +) + +type rtype struct { + flag int +} +type uncommonType struct { + offset int +} +type method struct { + Name string +} + +func main() { + demo(2) +} + +func demo(count int) { + tt := reflect.New(reflect.StructOf([]reflect.StructField{ + {Name: "S", Type: reflect.TypeOf(rtype{})}, + {Name: "U", Type: reflect.TypeOf(uncommonType{})}, + {Name: "M", Type: reflect.ArrayOf(count, reflect.TypeOf(method{}))}, + })) + iface := tt.Elem().Field(2).Slice(0, count).Interface() + _, ok := iface.([]method) + if !ok { + panic("error") + } +} diff --git a/_demo/go/reflectsliceat/main.go b/_demo/go/reflectsliceat/main.go new file mode 100644 index 0000000000..248c173dea --- /dev/null +++ b/_demo/go/reflectsliceat/main.go @@ -0,0 +1,7 @@ +//go:build !go1.23 +// +build !go1.23 + +package main + +func main() { +} diff --git a/_demo/go/reflectsliceat/main_go123.go b/_demo/go/reflectsliceat/main_go123.go new file mode 100644 index 0000000000..ca634d7fb5 --- /dev/null +++ b/_demo/go/reflectsliceat/main_go123.go @@ -0,0 +1,34 @@ +//go:build go1.23 +// +build go1.23 + +package main + +import ( + "fmt" + "reflect" + "unsafe" +) + +func main() { + // Existing array or memory region + var arr [5]int = [5]int{10, 20, 30, 40, 50} + + // Use SliceAt to create a slice in the middle of the array (zero allocation) + // Starting from arr[1] with length 3 + sliceVal := reflect.SliceAt( + reflect.TypeOf(0), // Element type: int + unsafe.Pointer(&arr[1]), // Base address + 3, // Length + ) + + slice := sliceVal.Interface().([]int) + if r := fmt.Sprint(slice); r != "[20 30 40]" { + panic("error: " + r) + } + + // Modifying the slice affects the original array + slice[0] = 999 + if r := fmt.Sprint(arr); r != "[10 999 30 40 50]" { + panic("error: " + r) + } +} diff --git a/_demo/go/reflectstructof/main.go b/_demo/go/reflectstructof/main.go new file mode 100644 index 0000000000..7e9dc0d322 --- /dev/null +++ b/_demo/go/reflectstructof/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "reflect" +) + +func add(a int, b int) int { + return a + b +} + +func sub(a int, b int) int { + return a - b +} + +func main() { + typ := reflect.StructOf([]reflect.StructField{ + { + Name: "Add", + PkgPath: "", + Type: reflect.TypeOf(add), + }, + { + Name: "Sub", + PkgPath: "", + Type: reflect.TypeOf(sub), + }, + }) + st := reflect.New(typ).Elem() + st.Field(0).Set(reflect.ValueOf(add)) + st.Field(1).Set(reflect.ValueOf(sub)) + r := st.Field(0).Call([]reflect.Value{reflect.ValueOf(1), reflect.ValueOf(2)}) + if len(r) != 1 || r[0].Interface() != 3 { + panic("st.Add(1,2) error") + } + r = st.Field(1).Call([]reflect.Value{reflect.ValueOf(1), reflect.ValueOf(2)}) + if len(r) != 1 || r[0].Interface() != -1 { + panic("st.Sub(1,2) error") + } +} diff --git a/_demo/go/reflectvisiblefields/main.go b/_demo/go/reflectvisiblefields/main.go new file mode 100644 index 0000000000..3b80453ae2 --- /dev/null +++ b/_demo/go/reflectvisiblefields/main.go @@ -0,0 +1,61 @@ +package main + +import ( + "fmt" + "reflect" +) + +// Define a struct with embedded anonymous fields and various visibility +type Inner struct { + InnerExported int // exported field in embedded struct + innerUnexported string // unexported field in embedded struct +} + +type Outer struct { + OuterField string + Inner // embedded (anonymous) struct + AnotherExported float64 + anotherUnexported bool +} + +func main() { + t := reflect.TypeOf(Outer{}) + + // Get all visible fields + fields := reflect.VisibleFields(t) + + fmt.Printf("Struct: %s\n\n", t.Name()) + fmt.Printf("Total visible fields: %d\n\n", len(fields)) + + for i, f := range fields { + fmt.Printf("[%d] Name: %-18s Index: %-4v Anonymous: %-5v Type: %-10v PkgPath: %q\n", + i, + f.Name, + f.Index, + f.Anonymous, + f.Type, + f.PkgPath, + ) + } + + fmt.Println("\n--- Access field values via FieldByIndex ---") + + o := Outer{ + OuterField: "hello", + Inner: Inner{InnerExported: 42, innerUnexported: "hidden"}, + AnotherExported: 3.14, + anotherUnexported: true, + } + + v := reflect.ValueOf(o) + + // Access each field using its Index + for _, f := range fields { + fieldVal := v.FieldByIndex(f.Index) + if fieldVal.CanInterface() { + fmt.Printf("%-18s = %v\n", f.Name, fieldVal.Interface()) + } else { + fmt.Printf("%-18s = \n", f.Name, fieldVal) + } + } +} diff --git a/_demo/go/return-1605/main.go b/_demo/go/return-1605/main.go new file mode 100644 index 0000000000..f978121cd4 --- /dev/null +++ b/_demo/go/return-1605/main.go @@ -0,0 +1,35 @@ +package main + +// Regression test for https://github.com/xgo-dev/llgo/issues/1608 + +type T struct { + data []int +} + +func a() []int { + var t = T{data: []int{1, 2}} + a := t.data + t.data = []int{1, 2, 3} + return a +} + +func b() ([]int, bool) { + var t = T{data: []int{1, 2}} + a := t.data + t.data = []int{1, 2, 3} + return a, true +} + +func main() { + resultA := a() + if len(resultA) != 2 || resultA[0] != 1 || resultA[1] != 2 { + panic("a(): expect [1,2] but got different") + } + + resultB, _ := b() + if len(resultB) != 2 || resultB[0] != 1 || resultB[1] != 2 { + panic("b(): expect [1,2] but got different") + } + + println("ok") +} diff --git a/_demo/go/runtime/main.go b/_demo/go/runtime/main.go new file mode 100644 index 0000000000..923a0f2430 --- /dev/null +++ b/_demo/go/runtime/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "runtime" +) + +func main() { + println(runtime.GOROOT()) +} diff --git a/_demo/go/statefn/main.go b/_demo/go/statefn/main.go new file mode 100644 index 0000000000..d62a6e0638 --- /dev/null +++ b/_demo/go/statefn/main.go @@ -0,0 +1,37 @@ +package main + +type stateFn func(*counter) stateFn + +type counter struct { + value int + max int + state stateFn +} + +func main() { + c := &counter{max: 5, state: startState} + + for c.state != nil { + c.state = c.state(c) + } +} + +func startState(c *counter) stateFn { + println("start") + return countState +} + +func countState(c *counter) stateFn { + c.value++ + println("count:", c.value) + + if c.value >= c.max { + return endState + } + return countState +} + +func endState(c *counter) stateFn { + println("end") + return nil +} diff --git a/_demo/sync/sync.go b/_demo/go/sync/sync.go similarity index 100% rename from _demo/sync/sync.go rename to _demo/go/sync/sync.go diff --git a/_demo/go/syscall/main.go b/_demo/go/syscall/main.go new file mode 100644 index 0000000000..7047c63a49 --- /dev/null +++ b/_demo/go/syscall/main.go @@ -0,0 +1,47 @@ +package main + +import ( + "syscall" + "unsafe" + + "github.com/goplus/lib/c" +) + +func printErr(prefix string, err error) { + if err == nil { + return + } + if errno, ok := err.(syscall.Errno); ok { + c.Printf(c.Str("%s: errno=%d\n"), c.AllocaCStr(prefix), errno) + return + } + c.Printf(c.Str("%s: error\n"), c.AllocaCStr(prefix)) +} + +func main() { + pid := syscall.Getpid() + c.Printf(c.Str("pid=%d\n"), pid) + + if wd, err := syscall.Getwd(); err != nil { + printErr("getwd", err) + } else { + c.Printf(c.Str("cwd=%s\n"), c.AllocaCStr(wd)) + } + + fd, err := syscall.Open("/etc/hosts", 0, 0) + if err != nil { + printErr("open /etc/hosts", err) + return + } + + var buf [128]byte + if n, err := syscall.Read(fd, buf[:]); err != nil { + printErr("read /etc/hosts", err) + } else { + c.Printf(c.Str("read=%d\n"), n) + if n > 0 { + c.Printf(c.Str("head: %.*s\n"), n, (*c.Char)(unsafe.Pointer(&buf[0]))) + } + } + _ = syscall.Close(fd) +} diff --git a/_demo/go/syscallraw/main.go b/_demo/go/syscallraw/main.go new file mode 100644 index 0000000000..79bcd2bae6 --- /dev/null +++ b/_demo/go/syscallraw/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "syscall" + "unsafe" +) + +func main() { + msg := []byte("Hello from Syscall!\n") + r1, r2, err := syscall.Syscall( + syscall.SYS_WRITE, + 1, + uintptr(unsafe.Pointer(&msg[0])), + uintptr(len(msg)), + ) + if r1 != 20 || r2 != 0 || err != 0 { + panic("syscall error") + } +} diff --git a/_demo/sysexec/exec.go b/_demo/go/sysexec/exec.go similarity index 100% rename from _demo/sysexec/exec.go rename to _demo/go/sysexec/exec.go diff --git a/_demo/go/sysopen-1654/main.go b/_demo/go/sysopen-1654/main.go new file mode 100644 index 0000000000..1cd255361f --- /dev/null +++ b/_demo/go/sysopen-1654/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "syscall" +) + +// Regression test for syscall.Open failure path. +// On existing file + O_EXCL, open must return fd=-1 and err=EEXIST. +func main() { + path := filepath.Join(os.TempDir(), fmt.Sprintf("sysopen-1654-%d.tmp", os.Getpid())) + if err := os.WriteFile(path, []byte("x"), 0o600); err != nil { + panic(fmt.Sprintf("prepare temp file failed: %v", err)) + } + defer os.Remove(path) + + fd, err := syscall.Open(path, syscall.O_CREAT|syscall.O_EXCL|syscall.O_RDWR, 0o600) + if err == nil { + panic(fmt.Sprintf("unexpected nil error: fd=%d uintptr=%#x on %s/%s", fd, uintptr(fd), runtime.GOOS, runtime.GOARCH)) + } + if fd != -1 { + panic(fmt.Sprintf("unexpected fd on failure: fd=%d uintptr=%#x err=%v on %s/%s", fd, uintptr(fd), err, runtime.GOOS, runtime.GOARCH)) + } + if !errors.Is(err, syscall.EEXIST) { + panic(fmt.Sprintf("unexpected error: got=%v want=%v", err, syscall.EEXIST)) + } + + fmt.Println("ok") +} diff --git a/_demo/go/texttemplate/main.go b/_demo/go/texttemplate/main.go new file mode 100644 index 0000000000..13cc218dc7 --- /dev/null +++ b/_demo/go/texttemplate/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "log" + "os" + "strings" + "text/template" +) + +func main() { + ExampleTemplate_block() +} + +func ExampleTemplate_block() { + const ( + master = `Names:{{block "list" .}}{{"\n"}}{{range .}}{{println "-" .}}{{end}}{{end}}` + overlay = `{{define "list"}} {{join . ", "}}{{end}} ` + ) + var ( + funcs = template.FuncMap{"join": strings.Join} + guardians = []string{"Gamora", "Groot", "Nebula", "Rocket", "Star-Lord"} + ) + masterTmpl, err := template.New("master").Funcs(funcs).Parse(master) + if err != nil { + log.Fatal(err) + } + overlayTmpl, err := template.Must(masterTmpl.Clone()).Parse(overlay) + if err != nil { + log.Fatal(err) + } + if err := masterTmpl.Execute(os.Stdout, guardians); err != nil { + log.Fatal(err) + } + if err := overlayTmpl.Execute(os.Stdout, guardians); err != nil { + log.Fatal(err) + } + // Output: + // Names: + // - Gamora + // - Groot + // - Nebula + // - Rocket + // - Star-Lord + // Names: Gamora, Groot, Nebula, Rocket, Star-Lord +} diff --git a/_demo/timedur/timedur.go b/_demo/go/timedur/timedur.go similarity index 100% rename from _demo/timedur/timedur.go rename to _demo/go/timedur/timedur.go diff --git a/_demo/go/timer/main.go b/_demo/go/timer/main.go new file mode 100644 index 0000000000..0a5e4af82a --- /dev/null +++ b/_demo/go/timer/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "time" + + "github.com/goplus/lib/c" +) + +// Small demo showing timer fire, stop, and reset behavior. +func main() { + c.Printf(c.Str("start: %s\n"), time.Now().Format(time.StampMilli)) + + // Timer that fires after 300ms. + t := time.NewTimer(300 * time.Millisecond) + + go func() { + <-t.C + c.Printf(c.Str("timer fired: %s\n"), time.Now().Format(time.StampMilli)) + }() + + // Stop and reset to fire later. + if t.Stop() { + c.Printf(c.Str("timer stopped before first fire\n")) + } + t.Reset(600 * time.Millisecond) + + // Schedule a function via AfterFunc. + done := make(chan struct{}) + time.AfterFunc(150*time.Millisecond, func() { + c.Printf(c.Str("afterfunc: %s\n"), time.Now().Format(time.StampMilli)) + close(done) + }) + + <-done + // The timer may have fired in the goroutine; wait for it once. + time.Sleep(50 * time.Millisecond) + select { + case <-t.C: + default: + } + c.Printf(c.Str("done: %s\n"), time.Now().Format(time.StampMilli)) +} diff --git a/_pydemo/callpy/callpy.go b/_demo/py/callpy/callpy.go similarity index 100% rename from _pydemo/callpy/callpy.go rename to _demo/py/callpy/callpy.go diff --git a/_demo/py/go.mod b/_demo/py/go.mod new file mode 100644 index 0000000000..177eabce91 --- /dev/null +++ b/_demo/py/go.mod @@ -0,0 +1,5 @@ +module github.com/xgo-dev/llgo/_demo/py + +go 1.20 + +require github.com/goplus/lib v0.2.0 diff --git a/_demo/go.sum b/_demo/py/go.sum similarity index 100% rename from _demo/go.sum rename to _demo/py/go.sum diff --git a/_pydemo/matrix/matrix.go b/_demo/py/matrix/matrix.go similarity index 100% rename from _pydemo/matrix/matrix.go rename to _demo/py/matrix/matrix.go diff --git a/_pydemo/max/max.go b/_demo/py/max/max.go similarity index 100% rename from _pydemo/max/max.go rename to _demo/py/max/max.go diff --git a/_pydemo/pi/pi.go b/_demo/py/pi/pi.go similarity index 100% rename from _pydemo/pi/pi.go rename to _demo/py/pi/pi.go diff --git a/_pydemo/print/print.go b/_demo/py/print/print.go similarity index 100% rename from _pydemo/print/print.go rename to _demo/py/print/print.go diff --git a/_pydemo/statistics/statistics.go b/_demo/py/statistics/statistics.go similarity index 100% rename from _pydemo/statistics/statistics.go rename to _demo/py/statistics/statistics.go diff --git a/_pydemo/tensor/tensor.go b/_demo/py/tensor/tensor.go similarity index 100% rename from _pydemo/tensor/tensor.go rename to _demo/py/tensor/tensor.go diff --git a/_demo/targetsbuild/build.sh b/_demo/targetsbuild/build.sh deleted file mode 100644 index 3f6bd4fe5c..0000000000 --- a/_demo/targetsbuild/build.sh +++ /dev/null @@ -1,180 +0,0 @@ -#!/bin/bash - -# Function to display usage information -show_usage() { - cat << EOF -Usage: $(basename "$0") [OPTIONS] [TARGET_FILE] - -Build targets for llgo across multiple platforms. - -OPTIONS: - -h, --help Show this help message and exit - -ARGUMENTS: - TARGET_FILE Optional. A text file containing target names, one per line. - Lines starting with # are treated as comments and ignored. - Empty lines are also ignored. - -BEHAVIOR: - Without TARGET_FILE: - - Automatically discovers all targets from ../../targets/*.json files - - Extracts target names from JSON filenames - - With TARGET_FILE: - - Reads target names from the specified file - - Supports comments (lines starting with #) - - Ignores empty lines and whitespace - -IGNORED TARGETS: - The following targets are automatically ignored and not built: - atmega1280, atmega2560, atmega328p, atmega32u4, attiny85, - fe310, k210, riscv32, riscv64, rp2040 - -RESULT CATEGORIES: - ✅ Successful: Build completed successfully - 🔕 Ignored: Target is in the ignore list - ⚠️ Warned: Build failed with configuration warnings - ❌ Failed: Build failed with errors - -EXIT CODES: - 0 All builds successful, ignored, or warned only - 1 One or more builds failed with errors - -EXAMPLES: - $(basename "$0") # Build all targets from JSON files - $(basename "$0") my-targets.txt # Build targets from file - $(basename "$0") --help # Show this help - -TARGET FILE FORMAT: - # This is a comment - esp32 - cortex-m4 - - # Another comment - riscv64 -EOF -} - -# Check for help flag -if [[ "$1" == "-h" || "$1" == "--help" ]]; then - show_usage - exit 0 -fi - -# Check for invalid number of arguments -if [ $# -gt 1 ]; then - echo "Error: Too many arguments." - echo "Use '$(basename "$0") --help' for usage information." - exit 1 -fi - -# Initialize arrays to store results -successful_targets=() -ignored_targets=() -warned_targets=() -failed_targets=() -targets_to_build=() - -# Define ignore list -ignore_list=( - "atmega1280" - "atmega2560" - "atmega328p" - "atmega32u4" - "attiny85" - "fe310" - "k210" - "riscv32" - "riscv64" - "rp2040" -) - -# Build the targets list based on input method -if [ $# -eq 1 ]; then - # Read targets from file - target_file="$1" - if [ ! -f "$target_file" ]; then - echo "Error: Target file '$target_file' not found." - echo "Use '$(basename "$0") --help' for usage information." - exit 1 - fi - - while IFS= read -r target || [[ -n "$target" ]]; do - # Skip empty lines and comments - if [[ -z "$target" || "$target" =~ ^[[:space:]]*# ]]; then - continue - fi - - # Trim whitespace - target=$(echo "$target" | xargs) - targets_to_build+=("$target") - done < "$target_file" -else - # Use targets from *.json files - for target_file in ../../targets/*.json; do - # Extract target name from filename (remove path and .json extension) - target=$(basename "$target_file" .json) - targets_to_build+=("$target") - done -fi - -# Process each target -for target in "${targets_to_build[@]}"; do - # Check if target is in ignore list - if [[ " ${ignore_list[@]} " =~ " ${target} " ]]; then - echo 🔕 $target "(ignored)" - ignored_targets+=("$target") - continue - fi - - output=$(../../llgo.sh build -target $target -o hello.out . 2>&1) - if [ $? -eq 0 ]; then - echo ✅ $target `file hello.out` - successful_targets+=("$target") - else - # Check if output contains warning messages - if echo "$output" | grep -q "does not have a valid LLVM target triple\|does not have a valid CPU configuration"; then - echo ⚠️ $target - echo "$output" - warned_targets+=("$target") - else - echo ❌ $target - echo "$output" - failed_targets+=("$target") - fi - fi -done - -echo "" -echo "----------------------------------------" - -# Output successful targets -echo "Successful targets (${#successful_targets[@]} total):" -for target in "${successful_targets[@]}"; do - echo "$target" -done - -echo "" -echo "Ignored targets (${#ignored_targets[@]} total):" -for target in "${ignored_targets[@]}"; do - echo "$target" -done - -echo "" -echo "Warned targets (${#warned_targets[@]} total):" -for target in "${warned_targets[@]}"; do - echo "$target" -done - -echo "" -echo "Failed targets (${#failed_targets[@]} total):" -for target in "${failed_targets[@]}"; do - echo "$target" -done - -# Exit with error code if there are any failed targets -if [ ${#failed_targets[@]} -gt 0 ]; then - echo "" - echo "Build failed with ${#failed_targets[@]} failed targets." - exit 1 -fi diff --git a/_demo/targetsbuild/empty.go b/_demo/targetsbuild/empty.go deleted file mode 100644 index 71be2ff7b1..0000000000 --- a/_demo/targetsbuild/empty.go +++ /dev/null @@ -1,6 +0,0 @@ -package main - -import _ "github.com/goplus/llgo/_demo/targetsbuild/C" - -func main() { -} diff --git a/_lldb/README.md b/_lldb/README.md deleted file mode 100644 index 9b9e1315e4..0000000000 --- a/_lldb/README.md +++ /dev/null @@ -1,115 +0,0 @@ -## LLGo Plugin of LLDB - -### Build with debug info - -```shell -LLGO_DEBUG_SYMBOLS=1 llgo build -o cl/_testdata/debug/out ./cl/_testdata/debug -``` - -### Debug with lldb - -```shell -_lldb/runlldb.sh ./cl/_testdata/debug/out -``` - -or - -```shell -/opt/homebrew/bin/lldb -O "command script import _lldb/llgo_plugin.py" ./cl/_testdata/debug/out -# github.com/goplus/llgo/cl/_testdata/debug -Breakpoint 1: no locations (pending). -Breakpoint set in dummy target, will get copied into future targets. -(lldb) command script import _lldb/llgo_plugin.py -(lldb) target create "./cl/_testdata/debug/out" -Current executable set to '/Users/lijie/source/goplus/llgo/cl/_testdata/debug/out' (arm64). -(lldb) r -Process 21992 launched: '/Users/lijie/source/goplus/llgo/cl/_testdata/debug/out' (arm64) -globalInt: 301 -s: 0x100123e40 -0x100123be0 -5 8 -called function with struct -1 2 3 4 5 6 7 8 9 10 +1.100000e+01 +1.200000e+01 true (+1.300000e+01+1.400000e+01i) (+1.500000e+01+1.600000e+01i) [3/3]0x1001129a0 [3/3]0x100112920 hello 0x1001149b0 0x100123ab0 0x100123d10 0x1001149e0 (0x100116810,0x1001149d0) 0x10011bf00 0x10010fa80 (0x100116840,0x100112940) 0x10001b4a4 -9 -1 (0x1001167e0,0x100112900) -called function with types -0x100123e40 -0x1000343d0 -Process 21992 stopped -* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 - frame #0: 0x000000010001b3b4 out`main at in.go:225:12 - 222 // s.i8: '\x01' - 223 // s.i16: 2 - 224 s.i8 = 0x12 --> 225 println(s.i8) - 226 // Expected: - 227 // all variables: globalInt globalStruct globalStructPtr s i err - 228 // s.i8: '\x12' -(lldb) v -var i int = -var s github.com/goplus/llgo/cl/_testdata/debug.StructWithAllTypeFields = { - i8 = '\x12', - i16 = 2, - i32 = 3, - i64 = 4, - i = 5, - u8 = '\x06', - u16 = 7, - u32 = 8, - u64 = 9, - u = 10, - f32 = 11, - f64 = 12, - b = true, - c64 = {real = 13, imag = 14}, - c128 = {real = 15, imag = 16}, - slice = []int{21, 22, 23}, - arr = [3]int{24, 25, 26}, - arr2 = [3]github.com/goplus/llgo/cl/_testdata/debug.E{{i = 27}, {i = 28}, {i = 29}}, - s = "hello", - e = {i = 30}, - pf = 0x0000000100123d10, - pi = 0x00000001001149e0, - intr = {type = 0x0000000100116810, data = 0x00000001001149d0}, - m = {count = 4296130304}, - c = {}, - err = {type = 0x0000000100116840, data = 0x0000000100112940}, - fn = {f = 0x000000010001b4a4, data = 0x00000001001149c0}, - pad1 = 100, - pad2 = 200 -} -var globalStructPtr *github.com/goplus/llgo/cl/_testdata/debug.StructWithAllTypeFields = -var globalStruct github.com/goplus/llgo/cl/_testdata/debug.StructWithAllTypeFields = { - i8 = '\x01', - i16 = 2, - i32 = 3, - i64 = 4, - i = 5, - u8 = '\x06', - u16 = 7, - u32 = 8, - u64 = 9, - u = 10, - f32 = 11, - f64 = 12, - b = true, - c64 = {real = 13, imag = 14}, - c128 = {real = 15, imag = 16}, - slice = []int{21, 22, 23}, - arr = [3]int{24, 25, 26}, - arr2 = [3]github.com/goplus/llgo/cl/_testdata/debug.E{{i = 27}, {i = 28}, {i = 29}}, - s = "hello", - e = {i = 30}, - pf = 0x0000000100123d10, - pi = 0x00000001001149e0, - intr = {type = 0x0000000100116810, data = 0x00000001001149d0}, - m = {count = 4296130304}, - c = {}, - err = {type = 0x0000000100116840, data = 0x0000000100112940}, - fn = {f = 0x000000010001b4a4, data = 0x00000001001149c0}, - pad1 = 100, - pad2 = 200 -} -var globalInt int = 301 -var err error = {type = 0x0000000100112900, data = 0x000000000000001a} -``` diff --git a/_lldb/lldbtest/main.go b/_lldb/lldbtest/main.go deleted file mode 100644 index ffc188b52c..0000000000 --- a/_lldb/lldbtest/main.go +++ /dev/null @@ -1,569 +0,0 @@ -package main - -import "errors" - -type Base struct { - name string -} - -type E struct { - // Base - i int -} -type StructWithAllTypeFields struct { - i8 int8 - i16 int16 - i32 int32 - i64 int64 - i int - u8 uint8 - u16 uint16 - u32 uint32 - u64 uint64 - u uint - f32 float32 - f64 float64 - b bool - c64 complex64 - c128 complex128 - slice []int - arr [3]int - arr2 [3]E - s string - e E - pf *StructWithAllTypeFields // resursive - pi *int - intr Interface - m map[string]uint64 - c chan int - err error - fn func(string) (int, error) - pad1 int - pad2 int -} - -type Interface interface { - Foo(a []int, b string) int -} - -type Struct struct{} - -func (s *Struct) Foo(a []int, b string) int { - return 1 -} - -func FuncWithAllTypeStructParam(s StructWithAllTypeFields) { - println(&s) - // Expected: - // all variables: s - // s.i8: '\x01' - // s.i16: 2 - // s.i32: 3 - // s.i64: 4 - // s.i: 5 - // s.u8: '\x06' - // s.u16: 7 - // s.u32: 8 - // s.u64: 9 - // s.u: 10 - // s.f32: 11 - // s.f64: 12 - // s.b: true - // s.c64: complex64{real = 13, imag = 14} - // s.c128: complex128{real = 15, imag = 16} - // s.slice: []int{21, 22, 23} - // s.arr: [3]int{24, 25, 26} - // s.arr2: [3]lldbtest.E{{i = 27}, {i = 28}, {i = 29}} - // s.s: "hello" - // s.e: lldbtest.E{i = 30} - // s.pad1: 100 - // s.pad2: 200 - s.i8 = '\b' - // Expected: - // s.i8: '\b' - // s.i16: 2 - println(len(s.s), s.i8) -} - -// Params is a function with all types of parameters. -func FuncWithAllTypeParams( - i8 int8, - i16 int16, - i32 int32, - i64 int64, - i int, - u8 uint8, - u16 uint16, - u32 uint32, - u64 uint64, - u uint, - f32 float32, - f64 float64, - b bool, - c64 complex64, - c128 complex128, - slice []int, - arr [3]int, - arr2 [3]E, - s string, - e E, - f StructWithAllTypeFields, - pf *StructWithAllTypeFields, - pi *int, - intr Interface, - m map[string]uint64, - c chan int, - err error, - fn func(string) (int, error), -) (int, error) { - // Expected: - // all variables: i8 i16 i32 i64 i u8 u16 u32 u64 u f32 f64 b c64 c128 slice arr arr2 s e f pf pi intr m c err fn - // i32: 3 - // i64: 4 - // i: 5 - // u32: 8 - // u64: 9 - // u: 10 - // f32: 11 - // f64: 12 - // slice: []int{21, 22, 23} - // arr: [3]int{24, 25, 26} - // arr2: [3]lldbtest.E{{i = 27}, {i = 28}, {i = 29}} - // slice[0]: 21 - // slice[1]: 22 - // slice[2]: 23 - // arr[0]: 24 - // arr[1]: 25 - // arr[2]: 26 - // arr2[0].i: 27 - // arr2[1].i: 28 - // arr2[2].i: 29 - // e: lldbtest.E{i = 30} - - // Expected(skip): - // i8: '\b' - // i16: 2 - // u8: '\x06' - // u16: 7 - // b: true - println( - i8, i16, i32, i64, i, u8, u16, u32, u64, u, - f32, f64, b, - c64, c128, - slice, arr[0:], - s, - &e, - &f, pf, pi, intr, m, - c, - err, - fn, - ) - i8 = 9 - i16 = 10 - i32 = 11 - i64 = 12 - i = 13 - u8 = 14 - u16 = 15 - u32 = 16 - u64 = 17 - u = 18 - f32 = 19 - f64 = 20 - b = false - c64 = 21 + 22i - c128 = 23 + 24i - slice = []int{31, 32, 33} - arr = [3]int{34, 35, 36} - arr2 = [3]E{{i: 37}, {i: 38}, {i: 39}} - s = "world" - e = E{i: 40} - - println(i8, i16, i32, i64, i, u8, u16, u32, u64, u, - f32, f64, b, - c64, c128, - slice, arr[0:], &arr2, - s, - &e, - &f, pf, pi, intr, m, - c, - err, - fn, - ) - // Expected: - // i8: '\t' - // i16: 10 - // i32: 11 - // i64: 12 - // i: 13 - // u8: '\x0e' - // u16: 15 - // u32: 16 - // u64: 17 - // u: 18 - // f32: 19 - // f64: 20 - // b: false - // c64: complex64{real = 21, imag = 22} - // c128: complex128{real = 23, imag = 24} - // slice: []int{31, 32, 33} - // arr2: [3]lldbtest.E{{i = 37}, {i = 38}, {i = 39}} - // s: "world" - // e: lldbtest.E{i = 40} - - // Expected(skip): - // arr: [3]int{34, 35, 36} - return 1, errors.New("some error") -} - -type TinyStruct struct { - I int -} - -type SmallStruct struct { - I int - J int -} - -type MidStruct struct { - I int - J int - K int -} - -type BigStruct struct { - I int - J int - K int - L int - M int - N int - O int - P int - Q int - R int -} - -func FuncStructParams(t TinyStruct, s SmallStruct, m MidStruct, b BigStruct) { - // println(&t, &s, &m, &b) - // Expected: - // all variables: t s m b - // t.I: 1 - // s.I: 2 - // s.J: 3 - // m.I: 4 - // m.J: 5 - // m.K: 6 - // b.I: 7 - // b.J: 8 - // b.K: 9 - // b.L: 10 - // b.M: 11 - // b.N: 12 - // b.O: 13 - // b.P: 14 - // b.Q: 15 - // b.R: 16 - println(t.I, s.I, s.J, m.I, m.J, m.K, b.I, b.J, b.K, b.L, b.M, b.N, b.O, b.P, b.Q, b.R) - t.I = 10 - s.I = 20 - s.J = 21 - m.I = 40 - m.J = 41 - m.K = 42 - b.I = 70 - b.J = 71 - b.K = 72 - b.L = 73 - b.M = 74 - b.N = 75 - b.O = 76 - b.P = 77 - b.Q = 78 - b.R = 79 - // Expected: - // all variables: t s m b - // t.I: 10 - // s.I: 20 - // s.J: 21 - // m.I: 40 - // m.J: 41 - // m.K: 42 - // b.I: 70 - // b.J: 71 - // b.K: 72 - // b.L: 73 - // b.M: 74 - // b.N: 75 - // b.O: 76 - // b.P: 77 - // b.Q: 78 - // b.R: 79 - println("done") -} - -func FuncStructPtrParams(t *TinyStruct, s *SmallStruct, m *MidStruct, b *BigStruct) { - // Expected: - // all variables: t s m b - // t.I: 1 - // s.I: 2 - // s.J: 3 - // m.I: 4 - // m.J: 5 - // m.K: 6 - // b.I: 7 - // b.J: 8 - // b.K: 9 - // b.L: 10 - // b.M: 11 - // b.N: 12 - // b.O: 13 - // b.P: 14 - // b.Q: 15 - // b.R: 16 - println(t, s, m, b) - t.I = 10 - s.I = 20 - s.J = 21 - m.I = 40 - m.J = 41 - m.K = 42 - b.I = 70 - b.J = 71 - b.K = 72 - b.L = 73 - b.M = 74 - b.N = 75 - b.O = 76 - b.P = 77 - b.Q = 78 - b.R = 79 - // Expected: - // all variables: t s m b - // t.I: 10 - // s.I: 20 - // s.J: 21 - // m.I: 40 - // m.J: 41 - // m.K: 42 - // b.I: 70 - // b.J: 71 - // b.K: 72 - // b.L: 73 - // b.M: 74 - // b.N: 75 - // b.O: 76 - // b.P: 77 - // b.Q: 78 - // b.R: 79 - println(t.I, s.I, s.J, m.I, m.J, m.K, b.I, b.J, b.K, b.L, b.M, b.N, b.O, b.P, b.Q, b.R) - println("done") -} - -func ScopeIf(branch int) { - a := 1 - // Expected: - // all variables: a branch - // a: 1 - if branch == 1 { - b := 2 - c := 3 - // Expected: - // all variables: a b c branch - // a: 1 - // b: 2 - // c: 3 - // branch: 1 - println(a, b, c) - } else { - c := 3 - d := 4 - // Expected: - // all variables: a c d branch - // a: 1 - // c: 3 - // d: 4 - // branch: 0 - println(a, c, d) - } - // Expected: - // all variables: a branch - // a: 1 - println("a:", a) -} - -func ScopeFor() { - a := 1 - for i := 0; i < 10; i++ { - switch i { - case 0: - println("i is 0") - // Expected: - // all variables: i a - // i: 0 - // a: 1 - println("i:", i) - case 1: - println("i is 1") - // Expected: - // all variables: i a - // i: 1 - // a: 1 - println("i:", i) - default: - println("i is", i) - } - } - println("a:", a) -} - -func ScopeSwitch(i int) { - a := 0 - switch i { - case 1: - b := 1 - println("i is 1") - // Expected: - // all variables: i a b - // i: 1 - // a: 0 - // b: 1 - println("i:", i, "a:", a, "b:", b) - case 2: - c := 2 - println("i is 2") - // Expected: - // all variables: i a c - // i: 2 - // a: 0 - // c: 2 - println("i:", i, "a:", a, "c:", c) - default: - d := 3 - println("i is", i) - // Expected: - // all variables: i a d - // i: 3 - // a: 0 - // d: 3 - println("i:", i, "a:", a, "d:", d) - } - // Expected: - // all variables: a i - // a: 0 - println("a:", a) -} - -func main() { - FuncStructParams(TinyStruct{I: 1}, SmallStruct{I: 2, J: 3}, MidStruct{I: 4, J: 5, K: 6}, BigStruct{I: 7, J: 8, K: 9, L: 10, M: 11, N: 12, O: 13, P: 14, Q: 15, R: 16}) - FuncStructPtrParams(&TinyStruct{I: 1}, &SmallStruct{I: 2, J: 3}, &MidStruct{I: 4, J: 5, K: 6}, &BigStruct{I: 7, J: 8, K: 9, L: 10, M: 11, N: 12, O: 13, P: 14, Q: 15, R: 16}) - i := 100 - s := StructWithAllTypeFields{ - i8: 1, - i16: 2, - i32: 3, - i64: 4, - i: 5, - u8: 6, - u16: 7, - u32: 8, - u64: 9, - u: 10, - f32: 11, - f64: 12, - b: true, - c64: 13 + 14i, - c128: 15 + 16i, - slice: []int{21, 22, 23}, - arr: [3]int{24, 25, 26}, - arr2: [3]E{{i: 27}, {i: 28}, {i: 29}}, - s: "hello", - e: E{i: 30}, - pf: &StructWithAllTypeFields{i16: 100}, - pi: &i, - intr: &Struct{}, - m: map[string]uint64{"a": 31, "b": 32}, - c: make(chan int), - err: errors.New("Test error"), - fn: func(s string) (int, error) { - println("fn:", s) - i = 201 - return 1, errors.New("fn error") - }, - pad1: 100, - pad2: 200, - } - // Expected: - // all variables: s i err - // s.i8: '\x01' - // s.i16: 2 - // s.i32: 3 - // s.i64: 4 - // s.i: 5 - // s.u8: '\x06' - // s.u16: 7 - // s.u32: 8 - // s.u64: 9 - // s.u: 10 - // s.f32: 11 - // s.f64: 12 - // s.b: true - // s.c64: complex64{real = 13, imag = 14} - // s.c128: complex128{real = 15, imag = 16} - // s.slice: []int{21, 22, 23} - // s.arr: [3]int{24, 25, 26} - // s.arr2: [3]lldbtest.E{{i = 27}, {i = 28}, {i = 29}} - // s.s: "hello" - // s.e: lldbtest.E{i = 30} - // s.pf.i16: 100 - // *(s.pf).i16: 100 - // *(s.pi): 100 - globalStructPtr = &s - globalStruct = s - println("globalInt:", globalInt) - // Expected(skip): - // all variables: globalInt globalStruct globalStructPtr s i err - println("s:", &s) - FuncWithAllTypeStructParam(s) - println("called function with struct") - i, err := FuncWithAllTypeParams( - s.i8, s.i16, s.i32, s.i64, s.i, s.u8, s.u16, s.u32, s.u64, s.u, - s.f32, s.f64, s.b, - s.c64, s.c128, - s.slice, s.arr, s.arr2, - s.s, - s.e, s, - s.pf, s.pi, - s.intr, - s.m, - s.c, - s.err, - s.fn, - ) - println(i, err) - ScopeIf(1) - ScopeIf(0) - ScopeFor() - ScopeSwitch(1) - ScopeSwitch(2) - ScopeSwitch(3) - println(globalStructPtr) - println(&globalStruct) - s.i8 = 0x12 - println(s.i8) - // Expected: - // all variables: s i err - // s.i8: '\x12' - - // Expected(skip): - // globalStruct.i8: '\x01' - println((*globalStructPtr).i8) - println("done") - println("") - println(&s, &globalStruct, globalStructPtr.i16, globalStructPtr) - globalStructPtr = nil -} - -var globalInt int = 301 -var globalStruct StructWithAllTypeFields -var globalStructPtr *StructWithAllTypeFields diff --git a/_lldb/llgo_plugin.py b/_lldb/llgo_plugin.py deleted file mode 100644 index 4643fe76b2..0000000000 --- a/_lldb/llgo_plugin.py +++ /dev/null @@ -1,297 +0,0 @@ -# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring - -from typing import List, Optional, Dict, Any, Tuple -import re -import lldb - - -def log(*args: Any, **kwargs: Any) -> None: - print(*args, **kwargs, flush=True) - - -def __lldb_init_module(debugger: lldb.SBDebugger, _: Dict[str, Any]) -> None: - debugger.HandleCommand( - 'command script add -f llgo_plugin.print_go_expression p') - debugger.HandleCommand( - 'command script add -f llgo_plugin.print_all_variables v') - - -def is_llgo_compiler(_target: lldb.SBTarget) -> bool: - return True - - -def get_indexed_value(value: lldb.SBValue, index: int) -> Optional[lldb.SBValue]: - if not value or not value.IsValid(): - return None - - type_name = value.GetType().GetName() - - if type_name.startswith('[]'): # Slice - data_ptr = value.GetChildMemberWithName('data') - element_type = data_ptr.GetType().GetPointeeType() - element_size = element_type.GetByteSize() - ptr_value = int(data_ptr.GetValue(), 16) - element_address = ptr_value + index * element_size - target = value.GetTarget() - return target.CreateValueFromAddress( - f"element_{index}", lldb.SBAddress(element_address, target), element_type) - elif value.GetType().IsArrayType(): # Array - return value.GetChildAtIndex(index) - else: - return None - - -def evaluate_expression(frame: lldb.SBFrame, expression: str) -> Optional[lldb.SBValue]: - parts = re.findall(r'\*|\w+|\(|\)|\[.*?\]|\.', expression) - - def evaluate_part(i: int) -> Tuple[Optional[lldb.SBValue], int]: - nonlocal parts - value: Optional[lldb.SBValue] = None - while i < len(parts): - part = parts[i] - - if part == '*': - sub_value, i = evaluate_part(i + 1) - if sub_value and sub_value.IsValid(): - value = sub_value.Dereference() - else: - return None, i - elif part == '(': - depth = 1 - j = i + 1 - while j < len(parts) and depth > 0: - if parts[j] == '(': - depth += 1 - elif parts[j] == ')': - depth -= 1 - j += 1 - value, i = evaluate_part(i + 1) - i = j - 1 - elif part == ')': - return value, i + 1 - elif part == '.': - if value is None: - value = frame.FindVariable(parts[i+1]) - else: - value = value.GetChildMemberWithName(parts[i+1]) - i += 2 - elif part.startswith('['): - index = int(part[1:-1]) - value = get_indexed_value(value, index) - i += 1 - else: - if value is None: - value = frame.FindVariable(part) - else: - value = value.GetChildMemberWithName(part) - i += 1 - - if not value or not value.IsValid(): - return None, i - - return value, i - - value, _ = evaluate_part(0) - return value - - -def print_go_expression(debugger: lldb.SBDebugger, command: str, result: lldb.SBCommandReturnObject, _internal_dict: Dict[str, Any]) -> None: - frame = debugger.GetSelectedTarget().GetProcess( - ).GetSelectedThread().GetSelectedFrame() - value = evaluate_expression(frame, command) - if value and value.IsValid(): - result.AppendMessage(format_value(value, debugger)) - else: - result.AppendMessage( - f"Error: Unable to evaluate expression '{command}'") - - -def print_all_variables(debugger: lldb.SBDebugger, _command: str, result: lldb.SBCommandReturnObject, _internal_dict: Dict[str, Any]) -> None: - target = debugger.GetSelectedTarget() - if not is_llgo_compiler(target): - result.AppendMessage("Not a LLGo compiled binary.") - return - - frame = debugger.GetSelectedTarget().GetProcess( - ).GetSelectedThread().GetSelectedFrame() - variables = frame.GetVariables(True, True, True, True) - - output: List[str] = [] - for var in variables: - type_name = map_type_name(var.GetType().GetName()) - formatted = format_value(var, debugger, include_type=False, indent=0) - output.append(f"var {var.GetName()} {type_name} = {formatted}") - - result.AppendMessage("\n".join(output)) - - -def is_pointer(frame: lldb.SBFrame, var_name: str) -> bool: - var = frame.FindVariable(var_name) - return var.IsValid() and var.GetType().IsPointerType() - - -def format_value(var: lldb.SBValue, debugger: lldb.SBDebugger, include_type: bool = True, indent: int = 0) -> str: - if not var.IsValid(): - return "" - - var_type = var.GetType() - type_class = var_type.GetTypeClass() - type_name = map_type_name(var_type.GetName()) - - # Handle typedef types - original_type_name = type_name - while var_type.IsTypedefType(): - var_type = var_type.GetTypedefedType() - type_name = map_type_name(var_type.GetName()) - type_class = var_type.GetTypeClass() - - if var_type.IsPointerType(): - return format_pointer(var, debugger, indent, original_type_name) - - if type_name.startswith('[]'): # Slice - return format_slice(var, debugger, indent) - elif var_type.IsArrayType(): - return format_array(var, debugger, indent) - elif type_name == 'string': # String - return format_string(var) - elif type_class in [lldb.eTypeClassStruct, lldb.eTypeClassClass]: - return format_struct(var, debugger, include_type, indent, original_type_name) - else: - value = var.GetValue() - summary = var.GetSummary() - if value is not None: - return f"{value}" if include_type else str(value) - elif summary is not None: - return f"{summary}" if include_type else summary - else: - return "" - - -def format_slice(var: lldb.SBValue, debugger: lldb.SBDebugger, indent: int) -> str: - length = var.GetChildMemberWithName('len').GetValue() - if length is None: - return "" - length = int(length) - data_ptr = var.GetChildMemberWithName('data') - elements: List[str] = [] - - ptr_value = int(data_ptr.GetValue(), 16) - element_type = data_ptr.GetType().GetPointeeType() - element_size = element_type.GetByteSize() - - target = debugger.GetSelectedTarget() - indent_str = ' ' * indent - next_indent_str = ' ' * (indent + 1) - - for i in range(length): - element_address = ptr_value + i * element_size - element = target.CreateValueFromAddress( - f"element_{i}", lldb.SBAddress(element_address, target), element_type) - value = format_value( - element, debugger, include_type=False, indent=indent+1) - elements.append(value) - - type_name = var.GetType().GetName() - - if len(elements) > 5: # 如果元素数量大于5,则进行折行显示 - result = f"{type_name}{{\n{next_indent_str}" + \ - f",\n{next_indent_str}".join(elements) + f"\n{indent_str}}}" - else: - result = f"{type_name}{{{', '.join(elements)}}}" - - return result - - -def format_array(var: lldb.SBValue, debugger: lldb.SBDebugger, indent: int) -> str: - elements: List[str] = [] - indent_str = ' ' * indent - next_indent_str = ' ' * (indent + 1) - - for i in range(var.GetNumChildren()): - value = format_value(var.GetChildAtIndex( - i), debugger, include_type=False, indent=indent+1) - elements.append(value) - - array_size = var.GetNumChildren() - element_type = map_type_name(var.GetType().GetArrayElementType().GetName()) - type_name = f"[{array_size}]{element_type}" - - if len(elements) > 5: # wrap line if too many elements - return f"{type_name}{{\n{next_indent_str}" + f",\n{next_indent_str}".join(elements) + f"\n{indent_str}}}" - else: - return f"{type_name}{{{', '.join(elements)}}}" - - -def format_string(var: lldb.SBValue) -> str: - summary = var.GetSummary() - if summary is not None: - return summary # Keep the quotes - else: - data = var.GetChildMemberWithName('data').GetValue() - length = var.GetChildMemberWithName('len').GetValue() - if data and length: - length = int(length) - error = lldb.SBError() - return '"%s"' % var.process.ReadCStringFromMemory(int(data, 16), length + 1, error) - return "" - - -def format_struct(var: lldb.SBValue, debugger: lldb.SBDebugger, include_type: bool = True, indent: int = 0, type_name: str = "") -> str: - children: List[str] = [] - indent_str = ' ' * indent - next_indent_str = ' ' * (indent + 1) - - for i in range(var.GetNumChildren()): - child = var.GetChildAtIndex(i) - child_name = child.GetName() - child_value = format_value( - child, debugger, include_type=False, indent=indent+1) - children.append(f"{child_name} = {child_value}") - - if len(children) > 5: # 如果字段数量大于5,则进行折行显示 - struct_content = "{\n" + ",\n".join( - [f"{next_indent_str}{child}" for child in children]) + f"\n{indent_str}}}" - else: - struct_content = f"{{{', '.join(children)}}}" - - if include_type: - return f"{type_name}{struct_content}" - else: - return struct_content - - -def format_pointer(var: lldb.SBValue, _debugger: lldb.SBDebugger, _indent: int, _type_name: str) -> str: - if not var.IsValid() or var.GetValueAsUnsigned() == 0: - return "" - return var.GetValue() # Return the address as a string - - -def map_type_name(type_name: str) -> str: - # Handle pointer types - if type_name.endswith('*'): - base_type = type_name[:-1].strip() - mapped_base_type = map_type_name(base_type) - return f"*{mapped_base_type}" - - # Map other types - type_mapping: Dict[str, str] = { - 'long': 'int', - 'void': 'unsafe.Pointer', - 'char': 'byte', - 'short': 'int16', - 'int': 'int32', - 'long long': 'int64', - 'unsigned char': 'uint8', - 'unsigned short': 'uint16', - 'unsigned int': 'uint32', - 'unsigned long': 'uint', - 'unsigned long long': 'uint64', - 'float': 'float32', - 'double': 'float64', - } - - for c_type, go_type in type_mapping.items(): - if type_name.startswith(c_type): - return type_name.replace(c_type, go_type, 1) - - return type_name diff --git a/_lldb/runlldb.sh b/_lldb/runlldb.sh deleted file mode 100755 index 0e6e57df36..0000000000 --- a/_lldb/runlldb.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -set -e - -# Source common functions and variables -# shellcheck source=./_lldb/common.sh -source "$(dirname "$0")/common.sh" - -executable="$1" - -# Get the directory of the current script -script_dir="$(dirname "$0")" - -# Run LLDB with the LLGO plugin -"$LLDB_PATH" -O "command script import ${script_dir}/llgo_plugin.py" "$executable" diff --git a/_lldb/runtest.sh b/_lldb/runtest.sh deleted file mode 100755 index 6d6834c2a3..0000000000 --- a/_lldb/runtest.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash - -set -e - -# Source common functions and variables -# shellcheck source=./_lldb/common.sh -# shellcheck disable=SC1091 -source "$(dirname "$0")/common.sh" || exit 1 - -# Parse command-line arguments -package_path="$DEFAULT_PACKAGE_PATH" -verbose=False -interactive=False -plugin_path=None - -while [[ $# -gt 0 ]]; do - case $1 in - -v|--verbose) - verbose=True - shift - ;; - -i|--interactive) - interactive=True - shift - ;; - -p|--plugin) - plugin_path="\"$2\"" - shift 2 - ;; - *) - package_path="$1" - shift - ;; - esac -done - -# Build the project -build_project "$package_path" || exit 1 - -# Set up the result file path -result_file="/tmp/lldb_exit_code" - -# Prepare LLDB commands -lldb_commands=( - "command script import ../llgo_plugin.py" - "command script import ../test.py" - "script test.run_tests_with_result('./debug.out', ['main.go'], $verbose, $interactive, $plugin_path, '$result_file')" - "quit" -) - -# Run LLDB with prepared commands -lldb_command_string="" -for cmd in "${lldb_commands[@]}"; do - lldb_command_string+=" -o \"$cmd\"" -done - -cd "$package_path" -# Run LLDB with the test script -eval "$LLDB_PATH $lldb_command_string" - -# Read the exit code from the result file -if [ -f "$result_file" ]; then - exit_code=$(cat "$result_file") - rm "$result_file" - exit "$exit_code" -else - echo "Error: Could not find exit code file" - exit 1 -fi diff --git a/_lldb/test.py b/_lldb/test.py deleted file mode 100644 index 306914cafc..0000000000 --- a/_lldb/test.py +++ /dev/null @@ -1,402 +0,0 @@ -# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring - -import os -import sys -import argparse -import signal -from dataclasses import dataclass, field -from typing import List, Optional, Set, Dict, Any -import lldb -import llgo_plugin -from llgo_plugin import log - - -class LLDBTestException(Exception): - pass - - -@dataclass -class Test: - source_file: str - line_number: int - variable: str - expected_value: str - - -@dataclass -class TestResult: - test: Test - status: str - actual: Optional[str] = None - message: Optional[str] = None - missing: Optional[Set[str]] = None - extra: Optional[Set[str]] = None - - -@dataclass -class TestCase: - source_file: str - start_line: int - end_line: int - tests: List[Test] - - -@dataclass -class CaseResult: - test_case: TestCase - function: str - results: List[TestResult] - - -@dataclass -class TestResults: - total: int = 0 - passed: int = 0 - failed: int = 0 - case_results: List[CaseResult] = field(default_factory=list) - - -class LLDBDebugger: - def __init__(self, executable_path: str, plugin_path: Optional[str] = None) -> None: - self.executable_path: str = executable_path - self.plugin_path: Optional[str] = plugin_path - self.debugger: lldb.SBDebugger = lldb.SBDebugger.Create() - self.debugger.SetAsync(False) - self.target: Optional[lldb.SBTarget] = None - self.process: Optional[lldb.SBProcess] = None - self.type_mapping: Dict[str, str] = { - 'long': 'int', - 'unsigned long': 'uint', - } - - def setup(self) -> None: - if self.plugin_path: - self.debugger.HandleCommand( - f'command script import "{self.plugin_path}"') - self.target = self.debugger.CreateTarget(self.executable_path) - if not self.target: - raise LLDBTestException( - f"Failed to create target for {self.executable_path}") - - self.debugger.HandleCommand( - 'command script add -f llgo_plugin.print_go_expression p') - self.debugger.HandleCommand( - 'command script add -f llgo_plugin.print_all_variables v') - - def set_breakpoint(self, file_spec: str, line_number: int) -> lldb.SBBreakpoint: - bp = self.target.BreakpointCreateByLocation(file_spec, line_number) - if not bp.IsValid(): - raise LLDBTestException( - f"Failed to set breakpoint at {file_spec}: {line_number}") - return bp - - def run_to_breakpoint(self) -> None: - if not self.process: - self.process = self.target.LaunchSimple(None, None, os.getcwd()) - else: - self.process.Continue() - if self.process.GetState() != lldb.eStateStopped: - raise LLDBTestException("Process didn't stop at breakpoint") - - def get_variable_value(self, var_expression: str) -> Optional[str]: - frame = self.process.GetSelectedThread().GetFrameAtIndex(0) - value = llgo_plugin.evaluate_expression(frame, var_expression) - if value and value.IsValid(): - return llgo_plugin.format_value(value, self.debugger) - return None - - def get_all_variable_names(self) -> Set[str]: - frame = self.process.GetSelectedThread().GetFrameAtIndex(0) - return set(var.GetName() for var in frame.GetVariables(True, True, True, True)) - - def get_current_function_name(self) -> str: - frame = self.process.GetSelectedThread().GetFrameAtIndex(0) - return frame.GetFunctionName() - - def cleanup(self) -> None: - if self.process and self.process.IsValid(): - self.process.Kill() - lldb.SBDebugger.Destroy(self.debugger) - - def run_console(self) -> bool: - log("\nEntering LLDB interactive mode.") - log("Type 'quit' to exit and continue with the next test case.") - log("Use Ctrl+D to exit and continue, or Ctrl+C to abort all tests.") - - old_stdin, old_stdout, old_stderr = sys.stdin, sys.stdout, sys.stderr - sys.stdin, sys.stdout, sys.stderr = sys.__stdin__, sys.__stdout__, sys.__stderr__ - - self.debugger.SetAsync(True) - self.debugger.HandleCommand("settings set auto-confirm true") - self.debugger.HandleCommand("command script import lldb") - - interpreter = self.debugger.GetCommandInterpreter() - continue_tests = True - - def keyboard_interrupt_handler(_sig: Any, _frame: Any) -> None: - nonlocal continue_tests - log("\nTest execution aborted by user.") - continue_tests = False - raise KeyboardInterrupt - - original_handler = signal.signal( - signal.SIGINT, keyboard_interrupt_handler) - - try: - while continue_tests: - log("\n(lldb) ", end="") - try: - command = input().strip() - except EOFError: - log("\nExiting LLDB interactive mode. Continuing with next test case.") - break - except KeyboardInterrupt: - break - - if command.lower() == 'quit': - log("\nExiting LLDB interactive mode. Continuing with next test case.") - break - - result = lldb.SBCommandReturnObject() - interpreter.HandleCommand(command, result) - log(result.GetOutput().rstrip() if result.Succeeded() - else result.GetError().rstrip()) - - finally: - signal.signal(signal.SIGINT, original_handler) - sys.stdin, sys.stdout, sys.stderr = old_stdin, old_stdout, old_stderr - - return continue_tests - - -def parse_expected_values(source_files: List[str]) -> List[TestCase]: - test_cases: List[TestCase] = [] - for source_file in source_files: - with open(source_file, 'r', encoding='utf-8') as f: - content = f.readlines() - i = 0 - while i < len(content): - line = content[i].strip() - if line.startswith('// Expected:'): - start_line = i + 1 - tests: List[Test] = [] - i += 1 - while i < len(content): - line = content[i].strip() - if not line.startswith('//'): - break - parts = line.lstrip('//').strip().split(':', 1) - if len(parts) == 2: - var, value = map(str.strip, parts) - tests.append(Test(source_file, i + 1, var, value)) - i += 1 - end_line = i - test_cases.append( - TestCase(source_file, start_line, end_line, tests)) - else: - i += 1 - return test_cases - - -def execute_tests(executable_path: str, test_cases: List[TestCase], verbose: bool, interactive: bool, plugin_path: Optional[str]) -> TestResults: - results = TestResults() - - for test_case in test_cases: - debugger = LLDBDebugger(executable_path, plugin_path) - try: - if verbose: - log( - f"\nSetting breakpoint at {test_case.source_file}:{test_case.end_line}") - debugger.setup() - debugger.set_breakpoint(test_case.source_file, test_case.end_line) - debugger.run_to_breakpoint() - - all_variable_names = debugger.get_all_variable_names() - - case_result = execute_test_case( - debugger, test_case, all_variable_names) - - results.total += len(case_result.results) - results.passed += sum(1 for r in case_result.results if r.status == 'pass') - results.failed += sum(1 for r in case_result.results if r.status != 'pass') - results.case_results.append(case_result) - - case = case_result.test_case - loc = f"{case.source_file}:{case.start_line}-{case.end_line}" - if verbose or interactive or any(r.status != 'pass' for r in case_result.results): - log(f"\nTest case: {loc} in function '{case_result.function}'") - for result in case_result.results: - print_test_result(result, verbose=verbose) - - if interactive and any(r.status != 'pass' for r in case_result.results): - log("\nTest case failed. Entering LLDB interactive mode.") - continue_tests = debugger.run_console() - if not continue_tests: - log("Aborting all tests.") - break - - finally: - debugger.cleanup() - - return results - - -def run_tests(executable_path: str, source_files: List[str], verbose: bool, interactive: bool, plugin_path: Optional[str]) -> int: - test_cases = parse_expected_values(source_files) - if verbose: - log(f"Running tests for {', '.join(source_files)} with {executable_path}") - log(f"Found {len(test_cases)} test cases") - - results = execute_tests(executable_path, test_cases, - verbose, interactive, plugin_path) - print_test_results(results) - - # Return 0 if all tests passed, 1 otherwise - return 0 if results.failed == 0 else 1 - - -def execute_test_case(debugger: LLDBDebugger, test_case: TestCase, all_variable_names: Set[str]) -> CaseResult: - results: List[TestResult] = [] - - for test in test_case.tests: - if test.variable == "all variables": - result = execute_all_variables_test(test, all_variable_names) - else: - result = execute_single_variable_test(debugger, test) - results.append(result) - - return CaseResult(test_case, debugger.get_current_function_name(), results) - - -def execute_all_variables_test(test: Test, all_variable_names: Set[str]) -> TestResult: - expected_vars = set(test.expected_value.split()) - if expected_vars == all_variable_names: - return TestResult( - test=test, - status='pass', - actual=all_variable_names - ) - else: - return TestResult( - test=test, - status='fail', - actual=all_variable_names, - missing=expected_vars - all_variable_names, - extra=all_variable_names - expected_vars - ) - - -def execute_single_variable_test(debugger: LLDBDebugger, test: Test) -> TestResult: - actual_value = debugger.get_variable_value(test.variable) - if actual_value is None: - return TestResult( - test=test, - status='error', - message=f'Unable to fetch value for {test.variable}' - ) - - actual_value = actual_value.strip() - expected_value = test.expected_value.strip() - - if actual_value == expected_value: - return TestResult( - test=test, - status='pass', - actual=actual_value - ) - else: - return TestResult( - test=test, - status='fail', - actual=actual_value - ) - - -def print_test_results(results: TestResults) -> None: - log("\nTest results:") - log(f" Total tests: {results.total}") - log(f" Passed tests: {results.passed}") - log(f" Failed tests: {results.failed}") - if results.total == results.passed: - log("All tests passed!") - else: - log("Some tests failed") - - -def print_test_result(result: TestResult, verbose: bool) -> None: - status_symbol = "✓" if result.status == 'pass' else "✗" - status_text = "Pass" if result.status == 'pass' else "Fail" - test = result.test - - if result.status == 'pass': - if verbose: - log(f"{status_symbol} Line {test.line_number}, {test.variable}: {status_text}") - if test.variable == 'all variables': - log(f" Variables: {', '.join(sorted(result.actual))}") - else: # fail or error - log(f"{status_symbol} Line {test.line_number}, {test.variable}: {status_text}") - if test.variable == 'all variables': - if result.missing: - log(f" Missing variables: {', '.join(sorted(result.missing))}") - if result.extra: - log(f" Extra variables: {', '.join(sorted(result.extra))}") - log(f" Expected: {', '.join(sorted(test.expected_value.split()))}") - log(f" Actual: {', '.join(sorted(result.actual))}") - elif result.status == 'error': - log(f" Error: {result.message}") - else: - log(f" Expected: {test.expected_value}") - log(f" Actual: {result.actual}") - - -def run_tests_with_result(executable_path: str, source_files: List[str], verbose: bool, interactive: bool, plugin_path: Optional[str], result_path: str) -> int: - try: - exit_code = run_tests(executable_path, source_files, - verbose, interactive, plugin_path) - except Exception as e: - log(f"An error occurred during test execution: {str(e)}") - exit_code = 2 # Use a different exit code for unexpected errors - - try: - with open(result_path, 'w', encoding='utf-8') as f: - f.write(str(exit_code)) - except IOError as e: - log(f"Error writing result to file {result_path}: {str(e)}") - # If we can't write to the file, we should still return the exit code - - return exit_code - - -def main() -> None: - log(sys.argv) - parser = argparse.ArgumentParser( - description="LLDB 18 Debug Script with DWARF 5 Support") - parser.add_argument("executable", help="Path to the executable") - parser.add_argument("sources", nargs='+', help="Paths to the source files") - parser.add_argument("-v", "--verbose", action="store_true", - help="Enable verbose output") - parser.add_argument("-i", "--interactive", action="store_true", - help="Enable interactive mode on test failure") - parser.add_argument("--plugin", help="Path to the LLDB plugin") - parser.add_argument("--result-path", help="Path to write the result") - args = parser.parse_args() - - plugin_path = args.plugin or os.path.join(os.path.dirname( - os.path.realpath(__file__)), "go_lldb_plugin.py") - - try: - if args.result_path: - exit_code = run_tests_with_result(args.executable, args.sources, - args.verbose, args.interactive, plugin_path, args.result_path) - else: - exit_code = run_tests(args.executable, args.sources, - args.verbose, args.interactive, plugin_path) - except Exception as e: - log(f"An unexpected error occurred: {str(e)}") - exit_code = 2 # Use a different exit code for unexpected errors - - sys.exit(exit_code) - - -if __name__ == "__main__": - main() diff --git a/_pydemo/go.mod b/_pydemo/go.mod deleted file mode 100644 index c117f8d970..0000000000 --- a/_pydemo/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/goplus/llgo/_pydemo - -go 1.20 - -require github.com/goplus/lib v0.2.0 diff --git a/_pydemo/go.sum b/_pydemo/go.sum deleted file mode 100644 index 512980a575..0000000000 --- a/_pydemo/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/goplus/lib v0.2.0 h1:AjqkN1XK5H23wZMMlpaUYAMCDAdSBQ2NMFrLtSh7W4g= -github.com/goplus/lib v0.2.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0= diff --git a/_xtool/go.mod b/_xtool/go.mod index 03c80912e9..b37d2e761d 100644 --- a/_xtool/go.mod +++ b/_xtool/go.mod @@ -1,4 +1,4 @@ -module github.com/goplus/llgo/_xtool +module github.com/xgo-dev/llgo/_xtool go 1.20 diff --git a/_xtool/pydump/pydump.go b/_xtool/pydump/pydump.go index b0532a8756..d1cbccba47 100644 --- a/_xtool/pydump/pydump.go +++ b/_xtool/pydump/pydump.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/benchmark/baseline/README.md b/benchmark/baseline/README.md new file mode 100644 index 0000000000..77c5163aab --- /dev/null +++ b/benchmark/baseline/README.md @@ -0,0 +1,86 @@ +# LLGo baseline benchmarks + +This suite is the lightweight performance gate for ordinary LLGo changes. It +uses fixed workloads and short calibrated benchmarks on Linux and macOS so it +can run on every `main` push and pull request. Branch-only series can be run +explicitly with `workflow_dispatch`, avoiding duplicate push and pull-request +jobs for the same commit. The two native jobs record normalized artifacts; a +trusted `workflow_run` publisher validates and merges both platforms into one +commit, branch, or pull-request series. + +The program workloads reuse: + +- `benchmark/binary_size/cprintf`: only `lib/c.Printf` (default and `-lto=full`); +- `benchmark/binary_size/println`: only the built-in `println` (default and `-lto=full`); +- `benchmark/binary_size/fmtprintf`: `fmt.Printf` (default and `-lto=full`). + +For each workload, the collector performs an unmeasured warm build, then records +median build time, median process time, file size, executable-code bytes, +allocated non-executable data, and zero-filled data. On ELF, read-only constants +are included in the data bucket; on Mach-O, `__TEXT` constants are included in +the text bucket. The Go benchmark stream records five samples of selected +compiler helpers and LLGo-generated core-language operations: direct/interface +calls, defer, goroutine creation, channels, `getg`, and global access. + +For pull requests, each platform job checks out the recorded base and current +commits into the same source path, then runs both suites sequentially on the same +runner. The pull request comment compares that pair, avoiding differences from +runner machines and embedded source paths. Dependency setup is shared, and Go's +build cache can be reused by unchanged packages; main pushes still run the suite +only once. Very small changes can still be scheduler, frequency, or thermal +noise and should be confirmed by repeated workflow runs. If a workflow does not +provide a paired result, the publisher falls back to the latest matching `main` +data. + +The trusted publisher commits the current result history and generated site to +the `pages` branch of the configured data repository. Every LLGo repository +defaults to `/llgo-benchmark-data`: + +```text +llgo/baseline/series/main/main +llgo/baseline/series/branch/ +llgo/baseline/series/pull/ +``` + +The publisher never executes code from the measured revision and pull request +jobs never receive the benchmark repository token. Pull requests receive one +updated summary comment linking to their long-term trend page. If no matching +`main` history exists yet, the pull-request report is still published and +marks every metric as `new`. + +Local collection: + +```sh +GOMAXPROCS=2 go build -o .benchmark/llgo ./cmd/llgo +go run ./benchmark/baseline \ + -llgo .benchmark/llgo \ + -out .benchmark/results +``` + +Write the selected Go benchmark output to `.benchmark/results/go.txt`: + +```sh +results=.benchmark/results/go.txt +GOMAXPROCS=1 go test \ + -run '^$' \ + -bench '^(BenchmarkMergeCompilerFlags|BenchmarkMergeLinkerFlags|BenchmarkLookupPCRandom)$' \ + -benchtime=250ms -count=5 -cpu=1 \ + ./internal/clang ./internal/build/funcinfo | tee "$results" +GOMAXPROCS=1 .benchmark/llgo test \ + -run '^$' \ + -bench '^(BenchmarkRuntimeGetG|BenchmarkGlobal(Read|Write)|Benchmark(DirectCall|InterfaceCall|Defer|ChannelBuffered|ChannelHandoff))$' \ + -benchtime=250ms -count=5 \ + ./test/llgoext | tee -a "$results" +GOMAXPROCS=1 .benchmark/llgo test \ + -run '^$' -bench '^BenchmarkGoroutine$' -benchtime=100x -count=5 \ + ./test/llgoext | tee -a "$results" +``` + +Then validate and export the complete artifact in standard Go benchmark format: + +```sh +go run ./benchmark/baseline \ + -mode export \ + -out .benchmark/results \ + -benchmark-output .benchmark/results/benchmark.txt +``` diff --git a/benchmark/baseline/main.go b/benchmark/baseline/main.go new file mode 100644 index 0000000000..94cbf4f01a --- /dev/null +++ b/benchmark/baseline/main.go @@ -0,0 +1,511 @@ +/* + * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "bufio" + "bytes" + "context" + "debug/elf" + "debug/macho" + "encoding/json" + "errors" + "flag" + "fmt" + "io" + "math" + "os" + "os/exec" + "path/filepath" + "regexp" + "runtime" + "slices" + "strconv" + "strings" + "time" +) + +type metric struct { + Name string `json:"name"` + Unit string `json:"unit"` + Value float64 `json:"value"` + Range string `json:"range,omitempty"` + Extra string `json:"extra,omitempty"` +} + +type workload struct { + name string + source string + output string + flags []string +} + +var workloads = []workload{ + {name: "cprintf", source: "benchmark/binary_size/cprintf/main.go", output: "Hello, world\n"}, + {name: "cprintf-lto", source: "benchmark/binary_size/cprintf/main.go", output: "Hello, world\n", flags: []string{"-lto=full"}}, + {name: "println", source: "benchmark/binary_size/println/main.go", output: "Hello, world\n"}, + {name: "println-lto", source: "benchmark/binary_size/println/main.go", output: "Hello, world\n", flags: []string{"-lto=full"}}, + {name: "fmtprintf", source: "benchmark/binary_size/fmtprintf/main.go", output: "Hello, world\n"}, + {name: "fmtprintf-lto", source: "benchmark/binary_size/fmtprintf/main.go", output: "Hello, world\n", flags: []string{"-lto=full"}}, +} + +var expectedGoBenchmarks = []string{ + "BenchmarkChannelBuffered", + "BenchmarkChannelHandoff", + "BenchmarkDefer", + "BenchmarkDirectCall", + "BenchmarkGlobalRead", + "BenchmarkGlobalWrite", + "BenchmarkGoroutine", + "BenchmarkInterfaceCall", + "BenchmarkLookupPCRandom", + "BenchmarkMergeCompilerFlags", + "BenchmarkMergeLinkerFlags", + "BenchmarkRuntimeGetG", +} + +const goBenchmarkSamples = 5 + +type footprint struct { + file uint64 + text uint64 + data uint64 + bss uint64 +} + +var inspectExecutable = executableFootprint + +func main() { + if err := runCLI(context.Background(), os.Args[1:]); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func runCLI(ctx context.Context, args []string) error { + flags := flag.NewFlagSet("llgo-baseline", flag.ContinueOnError) + flags.SetOutput(io.Discard) + mode := flags.String("mode", "collect", "collect, validate, or export") + root := flags.String("root", ".", "LLGo repository root") + llgo := flags.String("llgo", "llgo", "LLGo command") + out := flags.String("out", filepath.Join("benchmark", "baseline", "out"), "result directory") + buildRuns := flags.Int("build-runs", 3, "build repetitions per workload") + runRuns := flags.Int("run-runs", 7, "process repetitions per workload") + benchmarkOutput := flags.String( + "benchmark-output", + "", + "standard Go benchmark output for export mode", + ) + if err := flags.Parse(args); err != nil { + return err + } + + switch *mode { + case "collect": + return collect(ctx, *root, *llgo, *out, *buildRuns, *runRuns) + case "validate": + return validateArtifact(*out) + case "export": + return exportBenchmarks(*out, *benchmarkOutput) + default: + return fmt.Errorf("unknown mode %q", *mode) + } +} + +func exportBenchmarks(dir, output string) error { + if output == "" { + return errors.New("export mode requires benchmark-output") + } + if err := validateArtifact(dir); err != nil { + return err + } + sizes, err := readMetrics(filepath.Join(dir, "size.json")) + if err != nil { + return err + } + timings, err := readMetrics(filepath.Join(dir, "time.json")) + if err != nil { + return err + } + core, err := os.ReadFile(filepath.Join(dir, "go.txt")) + if err != nil { + return err + } + byName := make(map[string]float64, len(sizes)+len(timings)) + for _, value := range append(sizes, timings...) { + byName[value.Name] = value.Value + } + + var data strings.Builder + fmt.Fprintf(&data, "goos: %s\ngoarch: %s\n", runtime.GOOS, runtime.GOARCH) + data.WriteString("pkg: github.com/xgo-dev/llgo/benchmark/baseline\n") + for _, unit := range []string{ + "file-bytes", + "text-bytes", + "data-bytes", + "bss-bytes", + } { + fmt.Fprintf(&data, "Unit %s better=lower assume=exact\n", unit) + } + data.WriteString("Unit build-ns better=lower\n") + data.WriteString("Unit run-ns better=lower\n") + for _, item := range workloads { + fmt.Fprintf( + &data, + "BenchmarkProgram/%s 1 %s file-bytes %s text-bytes %s data-bytes %s bss-bytes %s build-ns %s run-ns\n", + item.name, + formatMetric(byName["binary/"+item.name+"/file"]), + formatMetric(byName["binary/"+item.name+"/text"]), + formatMetric(byName["binary/"+item.name+"/data"]), + formatMetric(byName["binary/"+item.name+"/bss"]), + formatMetric(byName["compile/"+item.name]), + formatMetric(byName["run/"+item.name]), + ) + } + if len(core) > 0 && core[len(core)-1] != '\n' { + core = append(core, '\n') + } + data.Write(core) + return os.WriteFile(output, []byte(data.String()), 0o644) +} + +func readMetrics(path string) ([]metric, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var values []metric + if err := json.Unmarshal(data, &values); err != nil { + return nil, fmt.Errorf("%s: %w", path, err) + } + return values, nil +} + +func formatMetric(value float64) string { + return strconv.FormatFloat(value, 'f', -1, 64) +} + +func collect(ctx context.Context, root, llgo, out string, buildRuns, runRuns int) error { + if buildRuns <= 0 || runRuns <= 0 { + return errors.New("build and run repetitions must be positive") + } + root, err := filepath.Abs(root) + if err != nil { + return err + } + out, err = filepath.Abs(out) + if err != nil { + return err + } + binDir := filepath.Join(out, "bin") + if err := os.RemoveAll(out); err != nil { + return err + } + if err := os.MkdirAll(binDir, 0o755); err != nil { + return err + } + + env := append(os.Environ(), + "GOMAXPROCS=2", + "LLGO_ROOT="+root, + "LLGO_FULL_RPATH=true", + ) + var sizes, timings []metric + for _, item := range workloads { + binary := filepath.Join(binDir, item.name) + buildArgs := append([]string{"build"}, item.flags...) + buildArgs = append(buildArgs, "-o", binary, filepath.Join(root, item.source)) + // Keep first-use toolchain and filesystem caches out of the measured + // median so the first revision is not systematically disadvantaged. + if err := run(ctx, env, io.Discard, llgo, buildArgs...); err != nil { + return fmt.Errorf("warm build %s: %w", item.name, err) + } + buildDurations := make([]time.Duration, 0, buildRuns) + for range buildRuns { + start := time.Now() + if err := run(ctx, env, io.Discard, llgo, buildArgs...); err != nil { + return fmt.Errorf("build %s: %w", item.name, err) + } + buildDurations = append(buildDurations, time.Since(start)) + } + timings = append(timings, durationMetric("compile/"+item.name, buildDurations)) + + size, err := inspectExecutable(binary) + if err != nil { + return fmt.Errorf("inspect %s: %w", item.name, err) + } + sizes = append(sizes, + byteMetric("binary/"+item.name+"/file", size.file), + byteMetric("binary/"+item.name+"/text", size.text), + byteMetric("binary/"+item.name+"/data", size.data), + byteMetric("binary/"+item.name+"/bss", size.bss), + ) + + var output bytes.Buffer + if err := run(ctx, env, &output, binary); err != nil { + return fmt.Errorf("execute %s: %w", item.name, err) + } + if got := strings.ReplaceAll(output.String(), "\r\n", "\n"); got != item.output { + return fmt.Errorf("execute %s: output %q, want %q", item.name, got, item.output) + } + runDurations := make([]time.Duration, 0, runRuns) + for range runRuns { + start := time.Now() + if err := run(ctx, env, io.Discard, binary); err != nil { + return fmt.Errorf("execute %s: %w", item.name, err) + } + runDurations = append(runDurations, time.Since(start)) + } + timings = append(timings, durationMetric("run/"+item.name, runDurations)) + } + + if err := writeMetrics(filepath.Join(out, "size.json"), sizes); err != nil { + return err + } + return writeMetrics(filepath.Join(out, "time.json"), timings) +} + +func run(ctx context.Context, env []string, output io.Writer, name string, args ...string) error { + cmd := exec.CommandContext(ctx, name, args...) + cmd.Env = env + cmd.Stdout = output + cmd.Stderr = output + if err := cmd.Run(); err != nil { + return fmt.Errorf("%s %s: %w", name, strings.Join(args, " "), err) + } + return nil +} + +func durationMetric(name string, values []time.Duration) metric { + ordered := slices.Clone(values) + slices.Sort(ordered) + middle := len(ordered) / 2 + median := float64(ordered[middle].Nanoseconds()) + if len(ordered)%2 == 0 { + median = (float64(ordered[middle-1].Nanoseconds()) + median) / 2 + } + return metric{ + Name: name, + Unit: "ns", + Value: median, + Range: strconv.FormatInt(ordered[0].Nanoseconds(), 10) + ".." + + strconv.FormatInt(ordered[len(ordered)-1].Nanoseconds(), 10), + Extra: fmt.Sprintf("median of %d consecutive runs", len(ordered)), + } +} + +func byteMetric(name string, value uint64) metric { + return metric{Name: name, Unit: "bytes", Value: float64(value)} +} + +func writeMetrics(path string, values []metric) error { + data, err := json.MarshalIndent(values, "", " ") + if err != nil { + return err + } + data = append(data, '\n') + return os.WriteFile(path, data, 0o644) +} + +func executableFootprint(path string) (footprint, error) { + info, err := os.Stat(path) + if err != nil { + return footprint{}, err + } + out := footprint{file: uint64(info.Size())} + + if f, err := elf.Open(path); err == nil { + defer f.Close() + addELFSections(&out, f.Sections) + return out, nil + } + + if f, err := macho.Open(path); err == nil { + defer f.Close() + addMachOSections(&out, f.Sections) + return out, nil + } + + return footprint{}, fmt.Errorf("unsupported executable format: %s", path) +} + +func addELFSections(out *footprint, sections []*elf.Section) { + for _, section := range sections { + if section.Flags&elf.SHF_ALLOC == 0 { + continue + } + switch { + case section.Type == elf.SHT_NOBITS: + out.bss += section.Size + case section.Flags&elf.SHF_EXECINSTR != 0: + out.text += section.Size + default: + out.data += section.Size + } + } +} + +func addMachOSections(out *footprint, sections []*macho.Section) { + for _, section := range sections { + switch { + case section.Seg == "__TEXT": + out.text += section.Size + case strings.HasPrefix(section.Seg, "__DATA") && + (section.Name == "__bss" || section.Name == "__common" || strings.HasSuffix(section.Name, "_bss")): + out.bss += section.Size + case strings.HasPrefix(section.Seg, "__DATA"): + out.data += section.Size + } + } +} + +func validateArtifact(dir string) error { + sizeNames := make(map[string]string, len(workloads)*4) + timeNames := make(map[string]string, len(workloads)*2) + for _, item := range workloads { + for _, part := range []string{"file", "text", "data", "bss"} { + sizeNames["binary/"+item.name+"/"+part] = "bytes" + } + timeNames["compile/"+item.name] = "ns" + timeNames["run/"+item.name] = "ns" + } + if err := validateMetrics(filepath.Join(dir, "size.json"), sizeNames); err != nil { + return err + } + if err := validateMetrics(filepath.Join(dir, "time.json"), timeNames); err != nil { + return err + } + f, err := os.Open(filepath.Join(dir, "go.txt")) + if err != nil { + return err + } + defer f.Close() + return validateGoBenchmarks(f) +} + +var ( + metricRange = regexp.MustCompile(`^[0-9]+\.\.[0-9]+$`) + metricExtra = regexp.MustCompile(`^[A-Za-z0-9 .,_:/()+-]+$`) + cpuSuffix = regexp.MustCompile(`-[0-9]+$`) +) + +func validateMetrics(path string, expected map[string]string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + + decoder := json.NewDecoder(f) + decoder.DisallowUnknownFields() + var values []metric + if err := decoder.Decode(&values); err != nil { + return fmt.Errorf("%s: %w", path, err) + } + if len(values) != len(expected) { + return fmt.Errorf("%s: got %d metrics, want %d", path, len(values), len(expected)) + } + seen := make(map[string]bool, len(values)) + for _, value := range values { + unit, ok := expected[value.Name] + if !ok { + return fmt.Errorf("%s: unexpected metric %q", path, value.Name) + } + if seen[value.Name] { + return fmt.Errorf("%s: duplicate metric %q", path, value.Name) + } + seen[value.Name] = true + if value.Unit != unit { + return fmt.Errorf("%s: metric %q has unit %q, want %q", path, value.Name, value.Unit, unit) + } + if math.IsNaN(value.Value) || math.IsInf(value.Value, 0) || value.Value < 0 { + return fmt.Errorf("%s: metric %q has invalid value %v", path, value.Name, value.Value) + } + if value.Range != "" && !metricRange.MatchString(value.Range) { + return fmt.Errorf("%s: metric %q has invalid range %q", path, value.Name, value.Range) + } + if value.Extra != "" && !metricExtra.MatchString(value.Extra) { + return fmt.Errorf("%s: metric %q has invalid extra text %q", path, value.Name, value.Extra) + } + } + return nil +} + +func validateGoBenchmarks(r io.Reader) error { + expected := make(map[string]int, len(expectedGoBenchmarks)) + for _, name := range expectedGoBenchmarks { + expected[name] = 0 + } + scanner := bufio.NewScanner(r) + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if len(fields) == 0 || !strings.HasPrefix(fields[0], "Benchmark") { + continue + } + name := cpuSuffix.ReplaceAllString(fields[0], "") + samples, ok := expected[name] + if !ok { + return fmt.Errorf("unexpected Go benchmark %q", name) + } + if samples >= goBenchmarkSamples { + return fmt.Errorf("too many samples for Go benchmark %q", name) + } + if len(fields) < 4 || (len(fields)-2)%2 != 0 { + return fmt.Errorf("malformed Go benchmark line %q", scanner.Text()) + } + if _, err := strconv.ParseUint(fields[1], 10, 64); err != nil { + return fmt.Errorf("benchmark %q has invalid iteration count: %w", name, err) + } + hasTime := false + for i := 2; i < len(fields); i += 2 { + value, err := strconv.ParseFloat(fields[i], 64) + if err != nil || math.IsNaN(value) || math.IsInf(value, 0) || value < 0 { + return fmt.Errorf("benchmark %q has invalid value %q", name, fields[i]) + } + switch fields[i+1] { + case "ns/op": + hasTime = true + case "B/op", "allocs/op": + default: + return fmt.Errorf("benchmark %q has unexpected unit %q", name, fields[i+1]) + } + } + if !hasTime { + return fmt.Errorf("benchmark %q has no ns/op result", name) + } + expected[name] = samples + 1 + } + if err := scanner.Err(); err != nil { + return err + } + var missing []string + for name, samples := range expected { + if samples == 0 { + missing = append(missing, name) + } + } + slices.Sort(missing) + if len(missing) != 0 { + return fmt.Errorf("missing Go benchmarks: %s", strings.Join(missing, ", ")) + } + for name, samples := range expected { + if samples != goBenchmarkSamples { + return fmt.Errorf("Go benchmark %q has %d samples, want %d", name, samples, goBenchmarkSamples) + } + } + return nil +} diff --git a/benchmark/baseline/main_test.go b/benchmark/baseline/main_test.go new file mode 100644 index 0000000000..7f060a0881 --- /dev/null +++ b/benchmark/baseline/main_test.go @@ -0,0 +1,540 @@ +/* + * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "bytes" + "context" + "debug/elf" + "debug/macho" + "encoding/json" + "errors" + "fmt" + "io" + "math" + "os" + "path/filepath" + "slices" + "strings" + "testing" + "time" +) + +func TestDurationMetric(t *testing.T) { + values := []time.Duration{9, 3, 6} + got := durationMetric("compile/test", values) + if got.Name != "compile/test" || got.Unit != "ns" || got.Value != 6 { + t.Fatalf("durationMetric = %+v", got) + } + if got.Range != "3..9" || got.Extra != "median of 3 consecutive runs" { + t.Fatalf("duration metadata = %+v", got) + } + if !slices.Equal(values, []time.Duration{9, 3, 6}) { + t.Fatalf("durationMetric mutated input: %v", values) + } + + even := durationMetric("compile/even", []time.Duration{8, 2}) + if even.Value != 5 || even.Range != "2..8" { + t.Fatalf("even durationMetric = %+v", even) + } +} + +func TestWriteAndValidateMetrics(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "metrics.json") + values := []metric{ + {Name: "one", Unit: "bytes", Value: 1}, + {Name: "two", Unit: "bytes", Value: 2, Range: "1..3", Extra: "median of 3 consecutive runs"}, + } + if err := writeMetrics(path, values); err != nil { + t.Fatal(err) + } + if err := validateMetrics(path, map[string]string{"one": "bytes", "two": "bytes"}); err != nil { + t.Fatal(err) + } + + var decoded []metric + data, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatal(err) + } + if len(decoded) != 2 || decoded[1].Range != "1..3" { + t.Fatalf("decoded metrics = %+v", decoded) + } +} + +func TestExportBenchmarks(t *testing.T) { + dir := t.TempDir() + writeValidArtifact(t, dir) + output := filepath.Join(dir, "benchmark.txt") + if err := exportBenchmarks(dir, output); err != nil { + t.Fatal(err) + } + data, err := os.ReadFile(output) + if err != nil { + t.Fatal(err) + } + text := string(data) + for _, want := range []string{ + "pkg: github.com/xgo-dev/llgo/benchmark/baseline", + "Unit file-bytes better=lower assume=exact", + "Unit build-ns better=lower", + "BenchmarkProgram/cprintf 1 1 file-bytes 1 text-bytes 1 data-bytes 1 bss-bytes 1 build-ns 1 run-ns", + "BenchmarkProgram/cprintf-lto 1 1 file-bytes 1 text-bytes 1 data-bytes 1 bss-bytes 1 build-ns 1 run-ns", + "BenchmarkRuntimeGetG-1 100 12.5 ns/op", + } { + if !strings.Contains(text, want) { + t.Fatalf("export does not contain %q:\n%s", want, text) + } + } +} + +func TestExportBenchmarksRejectsInvalidInput(t *testing.T) { + if err := exportBenchmarks(t.TempDir(), ""); err == nil || + !strings.Contains(err.Error(), "benchmark-output") { + t.Fatalf("exportBenchmarks error = %v", err) + } +} + +func TestWriteMetricsRejectsDirectory(t *testing.T) { + if err := writeMetrics(t.TempDir(), []metric{{Name: "one", Unit: "bytes", Value: 1}}); err == nil { + t.Fatal("writeMetrics unexpectedly accepted a directory") + } + path := filepath.Join(t.TempDir(), "nan.json") + if err := writeMetrics(path, []metric{{Name: "one", Unit: "bytes", Value: math.NaN()}}); err == nil { + t.Fatal("writeMetrics unexpectedly accepted NaN") + } +} + +func TestValidateMetricsRejectsInvalidData(t *testing.T) { + tests := []struct { + name string + data string + want string + }{ + {"unknown field", `[{"name":"one","unit":"bytes","value":1,"bad":1}]`, "unknown field"}, + {"wrong count", `[]`, "got 0 metrics"}, + {"unexpected name", `[{"name":"two","unit":"bytes","value":1}]`, `unexpected metric "two"`}, + {"wrong unit", `[{"name":"one","unit":"ns","value":1}]`, `unit "ns"`}, + {"negative", `[{"name":"one","unit":"bytes","value":-1}]`, "invalid value"}, + {"bad range", `[{"name":"one","unit":"bytes","value":1,"range":"bad"}]`, "invalid range"}, + {"bad extra", `[{"name":"one","unit":"bytes","value":1,"extra":"`); err != nil { + t.Fatalf("Execute: %v", err) + } + got := out.String() + if strings.Contains(got, "`, + )) + data := struct { + Attr template.HTMLAttr + URL template.URL + CSS template.CSS + JS template.JS + HTML template.HTML + Srcset template.Srcset + JSStr template.JSStr + }{ + Attr: template.HTMLAttr(` title="T"`), + URL: template.URL(`https://example.com/?a=1&b=2`), + CSS: template.CSS(`color:red`), + JS: template.JS(`1+2`), + HTML: template.HTML(`ok`), + Srcset: template.Srcset(`a.png 1x, b.png 2x`), + JSStr: template.JSStr(`\x41`), + } + + b.Reset() + if err := tmpl.Execute(&b, data); err != nil { + t.Fatalf("Execute(safe types): %v", err) + } + got := b.String() + if !strings.Contains(got, `ok`) { + t.Fatalf("safe HTML missing: %q", got) + } + if !strings.Contains(got, `style="color:red"`) { + t.Fatalf("safe CSS missing: %q", got) + } + if !strings.Contains(got, `onclick='x=1+2'`) { + t.Fatalf("safe JS missing: %q", got) + } + if !strings.Contains(got, `srcset="a.png 1x, b.png 2x"`) { + t.Fatalf("safe Srcset missing: %q", got) + } + if !strings.Contains(got, `let s='\x41'`) { + t.Fatalf("safe JSStr missing: %q", got) + } +} + +func TestTemplateConstructionAndParsingAPIs(t *testing.T) { + root := template.New("root") + if root.Name() != "root" { + t.Fatalf("Name = %q, want root", root.Name()) + } + + // Delims + Parse + New + Templates + delimed := root.Delims("[[", "]]") + if _, err := delimed.Parse(`[[define "base"]]base:[[template "child" .]][[end]]`); err != nil { + t.Fatalf("Parse with Delims: %v", err) + } + child := root.New("child") + if _, err := child.Parse(`[[define "child"]][[.]][[end]]`); err != nil { + t.Fatalf("New/Parse child: %v", err) + } + if len(root.Templates()) < 2 { + t.Fatalf("Templates len = %d, want >= 2", len(root.Templates())) + } + + var out bytes.Buffer + if err := root.ExecuteTemplate(&out, "base", "ok"); err != nil { + t.Fatalf("ExecuteTemplate(base): %v", err) + } + if out.String() != "base:ok" { + t.Fatalf("base output = %q", out.String()) + } + + // AddParseTree on a fresh template set. + treeRoot := template.New("tree-root") + trees, err := parse.Parse("added", `{{define "added"}}ADDED{{end}}`, "{{", "}}", nil) + if err != nil { + t.Fatalf("parse.Parse: %v", err) + } + if _, err := treeRoot.AddParseTree("added", trees["added"]); err != nil { + t.Fatalf("AddParseTree: %v", err) + } + out.Reset() + if err := treeRoot.ExecuteTemplate(&out, "added", nil); err != nil { + t.Fatalf("ExecuteTemplate(added): %v", err) + } + if out.String() != "ADDED" { + t.Fatalf("added output = %q", out.String()) + } + + // ParseFiles / ParseGlob / ParseFS (package funcs + methods) + dir := t.TempDir() + basePath := filepath.Join(dir, "a.tmpl") + itemPath := filepath.Join(dir, "b.tmpl") + if err := os.WriteFile(basePath, []byte(`{{define "A"}}A:{{template "B" .}}{{end}}`), 0o644); err != nil { + t.Fatalf("WriteFile(a): %v", err) + } + if err := os.WriteFile(itemPath, []byte(`{{define "B"}}{{.}}{{end}}`), 0o644); err != nil { + t.Fatalf("WriteFile(b): %v", err) + } + + t1 := template.Must(template.ParseFiles(basePath, itemPath)) + out.Reset() + if err := t1.ExecuteTemplate(&out, "A", "x"); err != nil { + t.Fatalf("ParseFiles ExecuteTemplate: %v", err) + } + if out.String() != "A:x" { + t.Fatalf("ParseFiles output = %q", out.String()) + } + + t2 := template.Must(template.ParseGlob(filepath.Join(dir, "*.tmpl"))) + out.Reset() + if err := t2.ExecuteTemplate(&out, "A", "y"); err != nil { + t.Fatalf("ParseGlob ExecuteTemplate: %v", err) + } + if out.String() != "A:y" { + t.Fatalf("ParseGlob output = %q", out.String()) + } + + t3 := template.Must(template.ParseFS(os.DirFS(dir), "*.tmpl")) + out.Reset() + if err := t3.ExecuteTemplate(&out, "A", "z"); err != nil { + t.Fatalf("ParseFS ExecuteTemplate: %v", err) + } + if out.String() != "A:z" { + t.Fatalf("ParseFS output = %q", out.String()) + } + + t4 := template.Must(template.New("method-files").ParseFiles(basePath, itemPath)) + out.Reset() + if err := t4.ExecuteTemplate(&out, "A", "m"); err != nil { + t.Fatalf("Template.ParseFiles ExecuteTemplate: %v", err) + } + if out.String() != "A:m" { + t.Fatalf("Template.ParseFiles output = %q", out.String()) + } + + t5 := template.Must(template.New("method-glob").ParseGlob(filepath.Join(dir, "*.tmpl"))) + out.Reset() + if err := t5.ExecuteTemplate(&out, "A", "n"); err != nil { + t.Fatalf("Template.ParseGlob ExecuteTemplate: %v", err) + } + if out.String() != "A:n" { + t.Fatalf("Template.ParseGlob output = %q", out.String()) + } + + t6 := template.Must(template.New("method-fs").ParseFS(os.DirFS(dir), "*.tmpl")) + out.Reset() + if err := t6.ExecuteTemplate(&out, "A", "p"); err != nil { + t.Fatalf("Template.ParseFS ExecuteTemplate: %v", err) + } + if out.String() != "A:p" { + t.Fatalf("Template.ParseFS output = %q", out.String()) + } +} + +func TestErrorTypeFormatting(t *testing.T) { + errVal := &template.Error{ + ErrorCode: template.ErrOutputContext, + Name: "bad", + Line: 12, + Description: "cannot compute output context", + } + msg := errVal.Error() + if msg == "" { + t.Fatal("Error().empty") + } + if errVal.ErrorCode != template.ErrOutputContext { + t.Fatalf("ErrorCode = %v", errVal.ErrorCode) + } + if code := template.ErrorCode(template.OK); code != template.OK { + t.Fatalf("ErrorCode conversion failed: %v", code) + } +} diff --git a/test/std/image/color/color_test.go b/test/std/image/color/color_test.go new file mode 100644 index 0000000000..3bd4ea3ec0 --- /dev/null +++ b/test/std/image/color/color_test.go @@ -0,0 +1,112 @@ +package color_test + +import ( + "image/color" + "testing" +) + +func TestConversionFunctions(t *testing.T) { + r, g, b := color.CMYKToRGB(10, 20, 30, 40) + c, m, y, k := color.RGBToCMYK(r, g, b) + if c > 255 || m > 255 || y > 255 || k > 255 { + t.Fatalf("RGBToCMYK out of range: %d %d %d %d", c, m, y, k) + } + + y0, cb, cr := color.RGBToYCbCr(100, 120, 140) + r2, g2, b2 := color.YCbCrToRGB(y0, cb, cr) + if r2 > 255 || g2 > 255 || b2 > 255 { + t.Fatalf("YCbCrToRGB out of range: %d %d %d", r2, g2, b2) + } +} + +func TestColorTypesRGBA(t *testing.T) { + colors := []color.Color{ + color.Alpha{A: 0x7f}, + color.Alpha16{A: 0x7fff}, + color.CMYK{C: 1, M: 2, Y: 3, K: 4}, + color.Gray{Y: 0x22}, + color.Gray16{Y: 0x2222}, + color.NRGBA{R: 1, G: 2, B: 3, A: 4}, + color.NRGBA64{R: 1, G: 2, B: 3, A: 4}, + color.NYCbCrA{YCbCr: color.YCbCr{Y: 10, Cb: 20, Cr: 30}, A: 40}, + color.RGBA{R: 5, G: 6, B: 7, A: 8}, + color.RGBA64{R: 5, G: 6, B: 7, A: 8}, + color.YCbCr{Y: 10, Cb: 20, Cr: 30}, + } + for i, c := range colors { + r, g, b, a := c.RGBA() + _ = r + _ = g + _ = b + if a > 0xffff { + t.Fatalf("color[%d] alpha out of range: %d", i, a) + } + } +} + +func TestModelsAndPalette(t *testing.T) { + src := color.NRGBA{R: 10, G: 20, B: 30, A: 200} + models := []color.Model{ + color.RGBAModel, + color.RGBA64Model, + color.NRGBAModel, + color.NRGBA64Model, + color.AlphaModel, + color.Alpha16Model, + color.GrayModel, + color.Gray16Model, + color.CMYKModel, + color.YCbCrModel, + color.NYCbCrAModel, + } + for i, m := range models { + if got := m.Convert(src); got == nil { + t.Fatalf("model[%d] Convert returned nil", i) + } + } + + mf := color.ModelFunc(func(c color.Color) color.Color { + return color.GrayModel.Convert(c) + }) + if _, ok := mf.Convert(src).(color.Gray); !ok { + t.Fatal("ModelFunc conversion did not produce Gray") + } + + p := color.Palette{color.Black, color.White} + if idx := p.Index(color.RGBA{R: 255, G: 255, B: 255, A: 255}); idx != 1 { + t.Fatalf("Palette.Index = %d, want 1", idx) + } + if got := p.Convert(color.RGBA{R: 1, G: 1, B: 1, A: 255}); got == nil { + t.Fatal("Palette.Convert returned nil") + } +} + +func TestPublicSymbols(t *testing.T) { + _ = color.Black + _ = color.White + _ = color.Transparent + _ = color.Opaque + + _ = color.CMYKToRGB + _ = color.RGBToCMYK + _ = color.RGBToYCbCr + _ = color.YCbCrToRGB + _ = color.ModelFunc + + _ = color.Alpha{}.RGBA + _ = color.Alpha16{}.RGBA + _ = color.CMYK{}.RGBA + _ = color.Gray{}.RGBA + _ = color.Gray16{}.RGBA + _ = color.NRGBA{}.RGBA + _ = color.NRGBA64{}.RGBA + _ = color.NYCbCrA{}.RGBA + _ = color.RGBA{}.RGBA + _ = color.RGBA64{}.RGBA + _ = color.YCbCr{}.RGBA + + var _ color.Color = color.RGBA{} + var _ color.Model = color.RGBAModel + var _ color.Model = color.ModelFunc(func(c color.Color) color.Color { return c }) + var _ color.Palette = color.Palette{color.Black} +} diff --git a/test/std/image/color/palette/palette_test.go b/test/std/image/color/palette/palette_test.go new file mode 100644 index 0000000000..bf4b3e8a80 --- /dev/null +++ b/test/std/image/color/palette/palette_test.go @@ -0,0 +1,23 @@ +package palette_test + +import ( + "image/color" + "image/color/palette" + "testing" +) + +func TestStandardPalettes(t *testing.T) { + if len(palette.Plan9) == 0 { + t.Fatal("palette.Plan9 must not be empty") + } + if len(palette.WebSafe) == 0 { + t.Fatal("palette.WebSafe must not be empty") + } + + if _, ok := palette.Plan9[0].(color.Color); !ok { + t.Fatal("palette.Plan9[0] does not implement color.Color") + } + if _, ok := palette.WebSafe[0].(color.Color); !ok { + t.Fatal("palette.WebSafe[0] does not implement color.Color") + } +} diff --git a/test/std/image/draw/draw_test.go b/test/std/image/draw/draw_test.go new file mode 100644 index 0000000000..24424ab09c --- /dev/null +++ b/test/std/image/draw/draw_test.go @@ -0,0 +1,69 @@ +package draw_test + +import ( + "image" + "image/color" + "image/draw" + "testing" +) + +type dummyQuantizer struct{} + +func (dummyQuantizer) Quantize(p color.Palette, _ image.Image) color.Palette { + return p +} + +func TestDrawAndDrawMask(t *testing.T) { + dst := image.NewRGBA(image.Rect(0, 0, 2, 1)) + src := image.NewUniform(color.RGBA{R: 255, A: 255}) + draw.Draw(dst, dst.Bounds(), src, image.Point{}, draw.Src) + + if got := dst.RGBAAt(0, 0); got.R != 255 || got.A != 255 { + t.Fatalf("Draw result = %#v", got) + } + + mask := image.NewAlpha(image.Rect(0, 0, 2, 1)) + mask.SetAlpha(0, 0, color.Alpha{A: 255}) + mask.SetAlpha(1, 0, color.Alpha{A: 0}) + blue := image.NewUniform(color.RGBA{B: 255, A: 255}) + draw.DrawMask(dst, dst.Bounds(), blue, image.Point{}, mask, image.Point{}, draw.Over) + + left := dst.RGBAAt(0, 0) + right := dst.RGBAAt(1, 0) + if left.B == 0 { + t.Fatalf("masked pixel not updated: %#v", left) + } + if right.B != 0 { + t.Fatalf("unmasked pixel should remain unchanged: %#v", right) + } +} + +func TestOpDrawAndFloydSteinberg(t *testing.T) { + dst := image.NewRGBA(image.Rect(0, 0, 1, 1)) + src := image.NewUniform(color.RGBA{G: 200, A: 255}) + draw.Over.Draw(dst, dst.Bounds(), src, image.Point{}) + if got := dst.RGBAAt(0, 0); got.G == 0 { + t.Fatalf("Op.Draw result = %#v", got) + } + + pal := image.NewPaletted(image.Rect(0, 0, 1, 1), color.Palette{color.Black, color.White}) + draw.FloydSteinberg.Draw(pal, pal.Bounds(), image.NewUniform(color.White), image.Point{}) + if idx := pal.ColorIndexAt(0, 0); idx != 1 { + t.Fatalf("FloydSteinberg result index = %d, want 1", idx) + } +} + +func TestPublicAPISymbols(t *testing.T) { + _ = draw.Draw + _ = draw.DrawMask + _ = draw.FloydSteinberg + _ = draw.Over + _ = draw.Src + if draw.Op(0) != draw.Over { + t.Fatalf("Op(0) should be Over, got %v", draw.Op(0)) + } + + var _ draw.Image = image.NewRGBA(image.Rect(0, 0, 1, 1)) + var _ draw.RGBA64Image = image.NewRGBA64(image.Rect(0, 0, 1, 1)) + var _ draw.Quantizer = dummyQuantizer{} +} diff --git a/test/std/image/gif/gif_test.go b/test/std/image/gif/gif_test.go new file mode 100644 index 0000000000..ecc490c142 --- /dev/null +++ b/test/std/image/gif/gif_test.go @@ -0,0 +1,76 @@ +package gif_test + +import ( + "bytes" + "image" + "image/color" + "image/color/palette" + "image/gif" + "testing" +) + +func TestEncodeDecode(t *testing.T) { + img := image.NewPaletted(image.Rect(0, 0, 2, 2), palette.Plan9) + img.SetColorIndex(0, 0, 1) + img.SetColorIndex(1, 1, 2) + + var buf bytes.Buffer + if err := gif.Encode(&buf, img, &gif.Options{NumColors: 16}); err != nil { + t.Fatalf("Encode: %v", err) + } + + cfg, err := gif.DecodeConfig(bytes.NewReader(buf.Bytes())) + if err != nil { + t.Fatalf("DecodeConfig: %v", err) + } + if cfg.Width != 2 || cfg.Height != 2 { + t.Fatalf("DecodeConfig size = %dx%d, want 2x2", cfg.Width, cfg.Height) + } + + decoded, err := gif.Decode(bytes.NewReader(buf.Bytes())) + if err != nil { + t.Fatalf("Decode: %v", err) + } + if decoded.Bounds().Dx() != 2 || decoded.Bounds().Dy() != 2 { + t.Fatalf("decoded size = %dx%d, want 2x2", decoded.Bounds().Dx(), decoded.Bounds().Dy()) + } +} + +func TestEncodeAllDecodeAll(t *testing.T) { + frame := image.NewPaletted(image.Rect(0, 0, 1, 1), color.Palette{color.Black, color.White}) + frame.SetColorIndex(0, 0, 1) + anim := &gif.GIF{ + Image: []*image.Paletted{frame}, + Delay: []int{5}, + LoopCount: 1, + Disposal: []byte{gif.DisposalNone}, + } + + var buf bytes.Buffer + if err := gif.EncodeAll(&buf, anim); err != nil { + t.Fatalf("EncodeAll: %v", err) + } + + decoded, err := gif.DecodeAll(bytes.NewReader(buf.Bytes())) + if err != nil { + t.Fatalf("DecodeAll: %v", err) + } + if len(decoded.Image) != 1 { + t.Fatalf("len(Image) = %d, want 1", len(decoded.Image)) + } +} + +func TestPublicAPISymbols(t *testing.T) { + _ = gif.DisposalNone + _ = gif.DisposalBackground + _ = gif.DisposalPrevious + + _ = gif.Decode + _ = gif.DecodeConfig + _ = gif.Encode + _ = gif.EncodeAll + _ = gif.DecodeAll + + _ = gif.Options{} + _ = gif.GIF{} +} diff --git a/test/std/image/image_test.go b/test/std/image/image_test.go new file mode 100644 index 0000000000..f493536808 --- /dev/null +++ b/test/std/image/image_test.go @@ -0,0 +1,640 @@ +package image_test + +import ( + "bytes" + "errors" + "image" + "image/color" + "image/png" + "io" + "testing" +) + +func TestDecodeDecodeConfigAndGeometry(t *testing.T) { + img := image.NewRGBA(image.Rect(0, 0, 2, 2)) + img.Set(1, 1, color.RGBA{R: 200, G: 100, B: 50, A: 255}) + + var buf bytes.Buffer + if err := png.Encode(&buf, img); err != nil { + t.Fatalf("png.Encode: %v", err) + } + + cfg, format, err := image.DecodeConfig(bytes.NewReader(buf.Bytes())) + if err != nil { + t.Fatalf("DecodeConfig: %v", err) + } + if format != "png" { + t.Fatalf("DecodeConfig format = %q, want png", format) + } + if cfg.Width != 2 || cfg.Height != 2 { + t.Fatalf("DecodeConfig size = %dx%d, want 2x2", cfg.Width, cfg.Height) + } + + decoded, format, err := image.Decode(bytes.NewReader(buf.Bytes())) + if err != nil { + t.Fatalf("Decode: %v", err) + } + if format != "png" { + t.Fatalf("Decode format = %q, want png", format) + } + if decoded.Bounds().Dx() != 2 || decoded.Bounds().Dy() != 2 { + t.Fatalf("Decode bounds = %v, want (0,0)-(2,2)", decoded.Bounds()) + } + + p := image.Pt(3, 4).Add(image.Pt(1, -2)).Sub(image.Pt(2, 1)).Mul(2).Div(2) + if !p.Eq(image.Pt(2, 1)) { + t.Fatalf("point mismatch: got %v", p) + } + if p.String() != "(2,1)" { + t.Fatalf("Point.String() = %q", p.String()) + } + + r := image.Rect(0, 0, 4, 4) + if !p.In(r) { + t.Fatalf("point %v should be in %v", p, r) + } + if !r.Overlaps(image.Rect(3, 3, 6, 6)) { + t.Fatalf("expected overlap") + } + if got := r.Intersect(image.Rect(2, 2, 3, 3)); got != (image.Rect(2, 2, 3, 3)) { + t.Fatalf("Intersect = %v", got) + } + if r.Union(image.Rect(5, 5, 6, 6)) != (image.Rect(0, 0, 6, 6)) { + t.Fatalf("Union mismatch") + } + + if image.ErrFormat == nil { + t.Fatal("ErrFormat should not be nil") + } +} + +func TestRegisterFormat(t *testing.T) { + name := "llgo_dummy_fmt" + magic := "LLGOFMT" + decode := func(r interface{}) (image.Image, error) { + _ = r + return image.NewRGBA(image.Rect(0, 0, 1, 1)), nil + } + decodeConfig := func(r interface{}) (image.Config, error) { + _ = r + return image.Config{ColorModel: color.RGBAModel, Width: 1, Height: 1}, nil + } + + image.RegisterFormat( + name, + magic, + func(r io.Reader) (image.Image, error) { + _ = r + return decode(nil) + }, + func(r io.Reader) (image.Config, error) { + _ = r + return decodeConfig(nil) + }, + ) +} + +func TestPublicAPISymbolCoverage(t *testing.T) { + _ = image.Black + _ = image.White + _ = image.Transparent + _ = image.Opaque + _ = image.ErrFormat + _ = image.ZP + _ = image.ZR + + _ = image.Decode + _ = image.DecodeConfig + _ = image.RegisterFormat + _ = image.Pt + _ = image.Rect + + _ = image.NewAlpha + _ = image.NewAlpha16 + _ = image.NewCMYK + _ = image.NewGray + _ = image.NewGray16 + _ = image.NewNRGBA + _ = image.NewNRGBA64 + _ = image.NewNYCbCrA + _ = image.NewPaletted + _ = image.NewRGBA + _ = image.NewRGBA64 + _ = image.NewUniform + _ = image.NewYCbCr + + _ = image.Config{} + _ = image.Point{} + _ = image.Rectangle{} + if image.YCbCrSubsampleRatio(0) != 0 { + t.Fatalf("YCbCrSubsampleRatio conversion mismatch: got %v", image.YCbCrSubsampleRatio(0)) + } + _ = image.Alpha{} + _ = image.Alpha16{} + _ = image.CMYK{} + _ = image.Gray{} + _ = image.Gray16{} + _ = image.NRGBA{} + _ = image.NRGBA64{} + _ = image.NYCbCrA{} + _ = image.Paletted{} + _ = image.RGBA{} + _ = image.RGBA64{} + _ = image.Uniform{} + _ = image.YCbCr{} + + _ = image.YCbCrSubsampleRatio444 + _ = image.YCbCrSubsampleRatio422 + _ = image.YCbCrSubsampleRatio420 + _ = image.YCbCrSubsampleRatio440 + _ = image.YCbCrSubsampleRatio411 + _ = image.YCbCrSubsampleRatio410 + + var _ image.Image = (*image.RGBA)(nil) + var _ image.PalettedImage = (*image.Paletted)(nil) + var _ image.RGBA64Image = (*image.RGBA)(nil) + + zeroRatio := image.YCbCrSubsampleRatio(0) + if zeroRatio != 0 { + t.Fatalf("YCbCrSubsampleRatio conversion mismatch: got %v, want 0", zeroRatio) + } + if image.Image(nil) != nil { + t.Fatal("image.Image(nil) should be nil") + } + if image.PalettedImage(nil) != nil { + t.Fatal("image.PalettedImage(nil) should be nil") + } + if image.RGBA64Image(nil) != nil { + t.Fatal("image.RGBA64Image(nil) should be nil") + } +} + +func TestConcreteMethodCoverage(t *testing.T) { + r := image.Rect(0, 0, 2, 2) + p := image.Pt(1, 1) + if image.YCbCrSubsampleRatio420.String() == "" { + t.Fatal("YCbCrSubsampleRatio.String returned empty string") + } + + if got := p.Add(image.Pt(1, 1)); got != (image.Pt(2, 2)) { + t.Fatalf("Point.Add = %v, want (2,2)", got) + } + if got := p.Div(1); got != p { + t.Fatalf("Point.Div = %v, want %v", got, p) + } + if !p.Eq(image.Pt(1, 1)) { + t.Fatal("Point.Eq returned false") + } + if !p.In(r) { + t.Fatal("Point.In returned false") + } + if got := p.Mod(r); got != p { + t.Fatalf("Point.Mod = %v, want %v", got, p) + } + if got := p.Mul(2); got != (image.Pt(2, 2)) { + t.Fatalf("Point.Mul = %v, want (2,2)", got) + } + if got := p.String(); got != "(1,1)" { + t.Fatalf("Point.String = %q, want %q", got, "(1,1)") + } + if got := p.Sub(image.Pt(1, 1)); got != (image.Pt(0, 0)) { + t.Fatalf("Point.Sub = %v, want (0,0)", got) + } + + if got := r.Add(image.Pt(1, 1)); got != (image.Rect(1, 1, 3, 3)) { + t.Fatalf("Rectangle.Add = %v", got) + } + if c := r.At(0, 0); c == nil { + t.Fatal("Rectangle.At returned nil color") + } + if got := r.Bounds(); got != r { + t.Fatalf("Rectangle.Bounds = %v, want %v", got, r) + } + if got := r.Canon(); got != r { + t.Fatalf("Rectangle.Canon = %v, want %v", got, r) + } + if m := r.ColorModel(); m == nil { + t.Fatal("Rectangle.ColorModel returned nil") + } + if got := r.Dx(); got != 2 { + t.Fatalf("Rectangle.Dx = %d, want 2", got) + } + if got := r.Dy(); got != 2 { + t.Fatalf("Rectangle.Dy = %d, want 2", got) + } + if r.Empty() { + t.Fatal("Rectangle.Empty returned true") + } + if !r.Eq(r) { + t.Fatal("Rectangle.Eq returned false") + } + if !r.In(image.Rect(-1, -1, 3, 3)) { + t.Fatal("Rectangle.In returned false") + } + if got := r.Inset(1); got != (image.Rect(1, 1, 1, 1)) { + t.Fatalf("Rectangle.Inset = %v", got) + } + if got := r.Intersect(image.Rect(1, 1, 3, 3)); got != (image.Rect(1, 1, 2, 2)) { + t.Fatalf("Rectangle.Intersect = %v", got) + } + if !r.Overlaps(image.Rect(1, 1, 3, 3)) { + t.Fatal("Rectangle.Overlaps returned false") + } + if got := r.RGBA64At(0, 0); got.A == 0 { + t.Fatalf("Rectangle.RGBA64At alpha = %d, want non-zero", got.A) + } + if got := r.Size(); got != (image.Pt(2, 2)) { + t.Fatalf("Rectangle.Size = %v, want (2,2)", got) + } + if got := r.String(); got == "" { + t.Fatal("Rectangle.String returned empty") + } + if got := r.Sub(image.Pt(1, 1)); got != (image.Rect(-1, -1, 1, 1)) { + t.Fatalf("Rectangle.Sub = %v", got) + } + if got := r.Union(image.Rect(1, 1, 3, 3)); got != (image.Rect(0, 0, 3, 3)) { + t.Fatalf("Rectangle.Union = %v", got) + } + + a := image.NewAlpha(r) + a.Set(0, 0, color.Alpha{A: 7}) + a.SetAlpha(0, 0, color.Alpha{A: 8}) + a.SetRGBA64(0, 0, color.RGBA64{A: 0xFFFF}) + if got := a.AlphaAt(0, 0).A; got != 0xFF { + t.Fatalf("Alpha.AlphaAt = %d, want 255", got) + } + if c := a.At(0, 0); c == nil { + t.Fatal("Alpha.At returned nil") + } + if got := a.Bounds(); got != r { + t.Fatalf("Alpha.Bounds = %v, want %v", got, r) + } + if m := a.ColorModel(); m == nil { + t.Fatal("Alpha.ColorModel returned nil") + } + if a.Opaque() { + t.Fatal("Alpha.Opaque should be false with unset pixels") + } + if got := a.PixOffset(0, 0); got != 0 { + t.Fatalf("Alpha.PixOffset = %d, want 0", got) + } + if got := a.RGBA64At(0, 0).A; got != 0xFFFF { + t.Fatalf("Alpha.RGBA64At alpha = %d, want 65535", got) + } + if got := a.SubImage(image.Rect(0, 0, 1, 1)).Bounds(); got != (image.Rect(0, 0, 1, 1)) { + t.Fatalf("Alpha.SubImage bounds = %v", got) + } + + a16 := image.NewAlpha16(r) + a16.Set(0, 0, color.Alpha16{A: 7}) + a16.SetAlpha16(0, 0, color.Alpha16{A: 9}) + a16.SetRGBA64(0, 0, color.RGBA64{A: 0xFFFF}) + if got := a16.Alpha16At(0, 0).A; got != 0xFFFF { + t.Fatalf("Alpha16.Alpha16At = %d, want 65535", got) + } + if c := a16.At(0, 0); c == nil { + t.Fatal("Alpha16.At returned nil") + } + if got := a16.Bounds(); got != r { + t.Fatalf("Alpha16.Bounds = %v, want %v", got, r) + } + if m := a16.ColorModel(); m == nil { + t.Fatal("Alpha16.ColorModel returned nil") + } + if a16.Opaque() { + t.Fatal("Alpha16.Opaque should be false with unset pixels") + } + if got := a16.PixOffset(0, 0); got != 0 { + t.Fatalf("Alpha16.PixOffset = %d, want 0", got) + } + if got := a16.RGBA64At(0, 0).A; got != 0xFFFF { + t.Fatalf("Alpha16.RGBA64At alpha = %d, want 65535", got) + } + if got := a16.SubImage(image.Rect(0, 0, 1, 1)).Bounds(); got != (image.Rect(0, 0, 1, 1)) { + t.Fatalf("Alpha16.SubImage bounds = %v", got) + } + + cm := image.NewCMYK(r) + cm.Set(0, 0, color.CMYK{C: 1, M: 2, Y: 3, K: 4}) + cm.SetCMYK(0, 0, color.CMYK{C: 5, M: 6, Y: 7, K: 8}) + cm.SetRGBA64(0, 0, color.RGBA64{R: 0xFFFF, A: 0xFFFF}) + if c := cm.At(0, 0); c == nil { + t.Fatal("CMYK.At returned nil") + } + if got := cm.Bounds(); got != r { + t.Fatalf("CMYK.Bounds = %v, want %v", got, r) + } + if got := cm.CMYKAt(0, 0); got.C|got.M|got.Y|got.K == 0 { + t.Fatalf("CMYK.CMYKAt = %#v, want non-zero", got) + } + if m := cm.ColorModel(); m == nil { + t.Fatal("CMYK.ColorModel returned nil") + } + if !cm.Opaque() { + t.Fatal("CMYK.Opaque should be true") + } + if got := cm.PixOffset(0, 0); got != 0 { + t.Fatalf("CMYK.PixOffset = %d, want 0", got) + } + if got := cm.RGBA64At(0, 0); got.A == 0 { + t.Fatalf("CMYK.RGBA64At alpha = %d, want non-zero", got.A) + } + if got := cm.SubImage(image.Rect(0, 0, 1, 1)).Bounds(); got != (image.Rect(0, 0, 1, 1)) { + t.Fatalf("CMYK.SubImage bounds = %v", got) + } + + g := image.NewGray(r) + g.Set(0, 0, color.Gray{Y: 11}) + g.SetGray(0, 0, color.Gray{Y: 12}) + g.SetRGBA64(0, 0, color.RGBA64{A: 0xFFFF}) + g.SetGray(0, 0, color.Gray{Y: 13}) + if c := g.At(0, 0); c == nil { + t.Fatal("Gray.At returned nil") + } + if got := g.Bounds(); got != r { + t.Fatalf("Gray.Bounds = %v, want %v", got, r) + } + if m := g.ColorModel(); m == nil { + t.Fatal("Gray.ColorModel returned nil") + } + if got := g.GrayAt(0, 0).Y; got == 0 { + t.Fatalf("Gray.GrayAt = %d, want non-zero", got) + } + if !g.Opaque() { + t.Fatal("Gray.Opaque should be true") + } + if got := g.PixOffset(0, 0); got != 0 { + t.Fatalf("Gray.PixOffset = %d, want 0", got) + } + if got := g.RGBA64At(0, 0).A; got != 0xFFFF { + t.Fatalf("Gray.RGBA64At alpha = %d, want 65535", got) + } + if got := g.SubImage(image.Rect(0, 0, 1, 1)).Bounds(); got != (image.Rect(0, 0, 1, 1)) { + t.Fatalf("Gray.SubImage bounds = %v", got) + } + + g16 := image.NewGray16(r) + g16.Set(0, 0, color.Gray16{Y: 11}) + g16.SetGray16(0, 0, color.Gray16{Y: 12}) + g16.SetRGBA64(0, 0, color.RGBA64{A: 0xFFFF}) + g16.SetGray16(0, 0, color.Gray16{Y: 13}) + if c := g16.At(0, 0); c == nil { + t.Fatal("Gray16.At returned nil") + } + if got := g16.Bounds(); got != r { + t.Fatalf("Gray16.Bounds = %v, want %v", got, r) + } + if m := g16.ColorModel(); m == nil { + t.Fatal("Gray16.ColorModel returned nil") + } + if got := g16.Gray16At(0, 0).Y; got == 0 { + t.Fatalf("Gray16.Gray16At = %d, want non-zero", got) + } + if !g16.Opaque() { + t.Fatal("Gray16.Opaque should be true") + } + if got := g16.PixOffset(0, 0); got != 0 { + t.Fatalf("Gray16.PixOffset = %d, want 0", got) + } + if got := g16.RGBA64At(0, 0).A; got != 0xFFFF { + t.Fatalf("Gray16.RGBA64At alpha = %d, want 65535", got) + } + if got := g16.SubImage(image.Rect(0, 0, 1, 1)).Bounds(); got != (image.Rect(0, 0, 1, 1)) { + t.Fatalf("Gray16.SubImage bounds = %v", got) + } + + n := image.NewNRGBA(r) + n.Set(0, 0, color.NRGBA{R: 1, A: 2}) + n.SetNRGBA(0, 0, color.NRGBA{R: 3, A: 4}) + n.SetRGBA64(0, 0, color.RGBA64{R: 0xFFFF, A: 0xFFFF}) + if c := n.At(0, 0); c == nil { + t.Fatal("NRGBA.At returned nil") + } + if got := n.Bounds(); got != r { + t.Fatalf("NRGBA.Bounds = %v, want %v", got, r) + } + if m := n.ColorModel(); m == nil { + t.Fatal("NRGBA.ColorModel returned nil") + } + if got := n.NRGBAAt(0, 0); got.A == 0 { + t.Fatalf("NRGBA.NRGBAAt alpha = %d, want non-zero", got.A) + } + if n.Opaque() { + t.Fatal("NRGBA.Opaque should be false with unset pixels") + } + if got := n.PixOffset(0, 0); got != 0 { + t.Fatalf("NRGBA.PixOffset = %d, want 0", got) + } + if got := n.RGBA64At(0, 0).A; got == 0 { + t.Fatalf("NRGBA.RGBA64At alpha = %d, want non-zero", got) + } + if got := n.SubImage(image.Rect(0, 0, 1, 1)).Bounds(); got != (image.Rect(0, 0, 1, 1)) { + t.Fatalf("NRGBA.SubImage bounds = %v", got) + } + + n64 := image.NewNRGBA64(r) + n64.Set(0, 0, color.NRGBA64{R: 1, A: 2}) + n64.SetNRGBA64(0, 0, color.NRGBA64{R: 3, A: 4}) + n64.SetRGBA64(0, 0, color.RGBA64{R: 0xFFFF, A: 0xFFFF}) + if c := n64.At(0, 0); c == nil { + t.Fatal("NRGBA64.At returned nil") + } + if got := n64.Bounds(); got != r { + t.Fatalf("NRGBA64.Bounds = %v, want %v", got, r) + } + if m := n64.ColorModel(); m == nil { + t.Fatal("NRGBA64.ColorModel returned nil") + } + if got := n64.NRGBA64At(0, 0); got.A == 0 { + t.Fatalf("NRGBA64.NRGBA64At alpha = %d, want non-zero", got.A) + } + if n64.Opaque() { + t.Fatal("NRGBA64.Opaque should be false with unset pixels") + } + if got := n64.PixOffset(0, 0); got != 0 { + t.Fatalf("NRGBA64.PixOffset = %d, want 0", got) + } + if got := n64.RGBA64At(0, 0).A; got == 0 { + t.Fatalf("NRGBA64.RGBA64At alpha = %d, want non-zero", got) + } + if got := n64.SubImage(image.Rect(0, 0, 1, 1)).Bounds(); got != (image.Rect(0, 0, 1, 1)) { + t.Fatalf("NRGBA64.SubImage bounds = %v", got) + } + + ny := image.NewNYCbCrA(r, image.YCbCrSubsampleRatio420) + aoff := ny.AOffset(0, 0) + ny.A[aoff] = 200 + if got := ny.AOffset(0, 0); got != aoff { + t.Fatalf("NYCbCrA.AOffset = %d, want %d", got, aoff) + } + if c := ny.At(0, 0); c == nil { + t.Fatal("NYCbCrA.At returned nil") + } + if got := ny.Bounds(); got != r { + t.Fatalf("NYCbCrA.Bounds = %v, want %v", got, r) + } + if m := ny.ColorModel(); m == nil { + t.Fatal("NYCbCrA.ColorModel returned nil") + } + if got := ny.NYCbCrAAt(0, 0).A; got != 200 { + t.Fatalf("NYCbCrA.NYCbCrAAt alpha = %d, want 200", got) + } + if ny.Opaque() { + t.Fatal("NYCbCrA.Opaque should be false") + } + if got := ny.RGBA64At(0, 0).A; got == 0 { + t.Fatalf("NYCbCrA.RGBA64At alpha = %d, want non-zero", got) + } + if got := ny.SubImage(image.Rect(0, 0, 1, 1)).Bounds(); got != (image.Rect(0, 0, 1, 1)) { + t.Fatalf("NYCbCrA.SubImage bounds = %v", got) + } + + pal := image.NewPaletted(r, color.Palette{color.Black, color.White}) + pal.Set(0, 0, color.White) + pal.SetColorIndex(0, 0, 1) + pal.SetRGBA64(1, 1, color.RGBA64{A: 0xFFFF}) + if c := pal.At(0, 0); c == nil { + t.Fatal("Paletted.At returned nil") + } + if got := pal.Bounds(); got != r { + t.Fatalf("Paletted.Bounds = %v, want %v", got, r) + } + if got := pal.ColorIndexAt(0, 0); got != 1 { + t.Fatalf("Paletted.ColorIndexAt = %d, want 1", got) + } + if m := pal.ColorModel(); m == nil { + t.Fatal("Paletted.ColorModel returned nil") + } + if !pal.Opaque() { + t.Fatal("Paletted.Opaque should be true for opaque palette") + } + if got := pal.PixOffset(0, 0); got != 0 { + t.Fatalf("Paletted.PixOffset = %d, want 0", got) + } + if got := pal.RGBA64At(0, 0).A; got == 0 { + t.Fatalf("Paletted.RGBA64At alpha = %d, want non-zero", got) + } + if got := pal.SubImage(image.Rect(0, 0, 1, 1)).Bounds(); got != (image.Rect(0, 0, 1, 1)) { + t.Fatalf("Paletted.SubImage bounds = %v", got) + } + + rgba := image.NewRGBA(r) + rgba.Set(0, 0, color.RGBA{R: 1, A: 2}) + rgba.SetRGBA(0, 0, color.RGBA{R: 3, A: 4}) + rgba.SetRGBA64(0, 0, color.RGBA64{R: 0xFFFF, A: 0xFFFF}) + if c := rgba.At(0, 0); c == nil { + t.Fatal("RGBA.At returned nil") + } + if got := rgba.Bounds(); got != r { + t.Fatalf("RGBA.Bounds = %v, want %v", got, r) + } + if m := rgba.ColorModel(); m == nil { + t.Fatal("RGBA.ColorModel returned nil") + } + if rgba.Opaque() { + t.Fatal("RGBA.Opaque should be false with unset pixels") + } + if got := rgba.PixOffset(0, 0); got != 0 { + t.Fatalf("RGBA.PixOffset = %d, want 0", got) + } + if got := rgba.RGBA64At(0, 0).A; got == 0 { + t.Fatalf("RGBA.RGBA64At alpha = %d, want non-zero", got) + } + if got := rgba.RGBAAt(0, 0).A; got == 0 { + t.Fatalf("RGBA.RGBAAt alpha = %d, want non-zero", got) + } + if got := rgba.SubImage(image.Rect(0, 0, 1, 1)).Bounds(); got != (image.Rect(0, 0, 1, 1)) { + t.Fatalf("RGBA.SubImage bounds = %v", got) + } + + rgba64 := image.NewRGBA64(r) + rgba64.Set(0, 0, color.RGBA64{R: 1, A: 2}) + rgba64.SetRGBA64(0, 0, color.RGBA64{R: 3, A: 4}) + if c := rgba64.At(0, 0); c == nil { + t.Fatal("RGBA64.At returned nil") + } + if got := rgba64.Bounds(); got != r { + t.Fatalf("RGBA64.Bounds = %v, want %v", got, r) + } + if m := rgba64.ColorModel(); m == nil { + t.Fatal("RGBA64.ColorModel returned nil") + } + if rgba64.Opaque() { + t.Fatal("RGBA64.Opaque should be false with unset pixels") + } + if got := rgba64.PixOffset(0, 0); got != 0 { + t.Fatalf("RGBA64.PixOffset = %d, want 0", got) + } + if got := rgba64.RGBA64At(0, 0).A; got == 0 { + t.Fatalf("RGBA64.RGBA64At alpha = %d, want non-zero", got) + } + if got := rgba64.SubImage(image.Rect(0, 0, 1, 1)).Bounds(); got != (image.Rect(0, 0, 1, 1)) { + t.Fatalf("RGBA64.SubImage bounds = %v", got) + } + + u := image.NewUniform(color.RGBA{R: 9, G: 8, B: 7, A: 6}) + if c := u.At(0, 0); c == nil { + t.Fatal("Uniform.At returned nil") + } + if got := u.Bounds(); got != (image.Rect(-1e9, -1e9, 1e9, 1e9)) { + t.Fatalf("Uniform.Bounds = %v", got) + } + if m := u.ColorModel(); m == nil { + t.Fatal("Uniform.ColorModel returned nil") + } + if c := u.Convert(color.Black); c == nil { + t.Fatal("Uniform.Convert returned nil") + } + if u.Opaque() { + t.Fatal("Uniform.Opaque should be false for alpha<255") + } + r0, g0, b0, a0 := u.RGBA() + if r0 == 0 && g0 == 0 && b0 == 0 && a0 == 0 { + t.Fatal("Uniform.RGBA returned all zeros") + } + if got := u.RGBA64At(0, 0).A; got == 0 { + t.Fatalf("Uniform.RGBA64At alpha = %d, want non-zero", got) + } + + y := image.NewYCbCr(r, image.YCbCrSubsampleRatio420) + y.Y[y.YOffset(0, 0)] = 128 + y.Cb[y.COffset(0, 0)] = 64 + y.Cr[y.COffset(0, 0)] = 192 + if c := y.At(0, 0); c == nil { + t.Fatal("YCbCr.At returned nil") + } + if got := y.Bounds(); got != r { + t.Fatalf("YCbCr.Bounds = %v, want %v", got, r) + } + if got := y.COffset(0, 0); got != 0 { + t.Fatalf("YCbCr.COffset = %d, want 0", got) + } + if m := y.ColorModel(); m == nil { + t.Fatal("YCbCr.ColorModel returned nil") + } + if !y.Opaque() { + t.Fatal("YCbCr.Opaque should be true") + } + if got := y.RGBA64At(0, 0).A; got != 0xFFFF { + t.Fatalf("YCbCr.RGBA64At alpha = %d, want 65535", got) + } + if got := y.SubImage(image.Rect(0, 0, 1, 1)).Bounds(); got != (image.Rect(0, 0, 1, 1)) { + t.Fatalf("YCbCr.SubImage bounds = %v", got) + } + if got := y.YCbCrAt(0, 0); got.Y != 128 || got.Cb != 64 || got.Cr != 192 { + t.Fatalf("YCbCr.YCbCrAt = %#v, want {128,64,192}", got) + } + if got := y.YOffset(0, 0); got != 0 { + t.Fatalf("YCbCr.YOffset = %d, want 0", got) + } +} + +func TestDecodeInvalidFormat(t *testing.T) { + _, _, err := image.Decode(bytes.NewReader([]byte("not-image"))) + if err == nil { + t.Fatal("Decode should fail for invalid input") + } + if !errors.Is(err, image.ErrFormat) { + t.Logf("Decode error = %v (acceptable as non-ErrFormat on malformed data)", err) + } +} diff --git a/test/std/image/jpeg/jpeg_test.go b/test/std/image/jpeg/jpeg_test.go new file mode 100644 index 0000000000..aa0d8fcaeb --- /dev/null +++ b/test/std/image/jpeg/jpeg_test.go @@ -0,0 +1,68 @@ +package jpeg_test + +import ( + "bytes" + "image" + "image/color" + "image/jpeg" + "strings" + "testing" +) + +func TestEncodeDecode(t *testing.T) { + img := image.NewRGBA(image.Rect(0, 0, 2, 1)) + img.Set(0, 0, color.RGBA{R: 255, A: 255}) + img.Set(1, 0, color.RGBA{G: 255, A: 255}) + + var buf bytes.Buffer + if err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: jpeg.DefaultQuality}); err != nil { + t.Fatalf("Encode: %v", err) + } + + cfg, err := jpeg.DecodeConfig(bytes.NewReader(buf.Bytes())) + if err != nil { + t.Fatalf("DecodeConfig: %v", err) + } + if cfg.Width != 2 || cfg.Height != 1 { + t.Fatalf("DecodeConfig size = %dx%d, want 2x1", cfg.Width, cfg.Height) + } + + decoded, err := jpeg.Decode(bytes.NewReader(buf.Bytes())) + if err != nil { + t.Fatalf("Decode: %v", err) + } + if decoded.Bounds().Dx() != 2 || decoded.Bounds().Dy() != 1 { + t.Fatalf("decoded size = %dx%d, want 2x1", decoded.Bounds().Dx(), decoded.Bounds().Dy()) + } +} + +func TestDecodeInvalid(t *testing.T) { + _, err := jpeg.Decode(bytes.NewReader([]byte("not-a-jpeg"))) + if err == nil { + t.Fatal("Decode should fail for invalid JPEG") + } +} + +func TestErrorTypesAndSymbols(t *testing.T) { + if got := jpeg.FormatError("bad").Error(); !strings.Contains(got, "bad") { + t.Fatalf("FormatError.Error() = %q", got) + } + if got := jpeg.UnsupportedError("x").Error(); !strings.Contains(got, "x") { + t.Fatalf("UnsupportedError.Error() = %q", got) + } + + _ = jpeg.DefaultQuality + _ = jpeg.Decode + _ = jpeg.DecodeConfig + _ = jpeg.Encode + + _ = jpeg.Options{} + if got := jpeg.FormatError("x").Error(); !strings.Contains(got, "x") { + t.Fatalf("FormatError.Error() = %q", got) + } + if got := jpeg.UnsupportedError("x").Error(); !strings.Contains(got, "x") { + t.Fatalf("UnsupportedError.Error() = %q", got) + } + + var _ jpeg.Reader = bytes.NewReader(nil) +} diff --git a/test/std/image/png/png_test.go b/test/std/image/png/png_test.go new file mode 100644 index 0000000000..4bdc131e1a --- /dev/null +++ b/test/std/image/png/png_test.go @@ -0,0 +1,83 @@ +package png_test + +import ( + "bytes" + "image" + "image/color" + "image/png" + "strings" + "testing" +) + +type dummyPool struct{} + +func (dummyPool) Get() *png.EncoderBuffer { return nil } +func (dummyPool) Put(*png.EncoderBuffer) {} + +func TestEncodeDecode(t *testing.T) { + img := image.NewNRGBA(image.Rect(0, 0, 2, 2)) + img.Set(0, 0, color.NRGBA{R: 255, A: 255}) + img.Set(1, 1, color.NRGBA{B: 255, A: 255}) + + var buf bytes.Buffer + if err := png.Encode(&buf, img); err != nil { + t.Fatalf("Encode: %v", err) + } + + cfg, err := png.DecodeConfig(bytes.NewReader(buf.Bytes())) + if err != nil { + t.Fatalf("DecodeConfig: %v", err) + } + if cfg.Width != 2 || cfg.Height != 2 { + t.Fatalf("DecodeConfig size = %dx%d, want 2x2", cfg.Width, cfg.Height) + } + + decoded, err := png.Decode(bytes.NewReader(buf.Bytes())) + if err != nil { + t.Fatalf("Decode: %v", err) + } + if decoded.Bounds().Dx() != 2 || decoded.Bounds().Dy() != 2 { + t.Fatalf("decoded size = %dx%d, want 2x2", decoded.Bounds().Dx(), decoded.Bounds().Dy()) + } +} + +func TestEncoder(t *testing.T) { + enc := png.Encoder{CompressionLevel: png.BestSpeed, BufferPool: dummyPool{}} + img := image.NewGray(image.Rect(0, 0, 1, 1)) + var buf bytes.Buffer + if err := enc.Encode(&buf, img); err != nil { + t.Fatalf("Encoder.Encode: %v", err) + } +} + +func TestErrorTypesAndSymbols(t *testing.T) { + if got := png.FormatError("bad").Error(); !strings.Contains(got, "bad") { + t.Fatalf("FormatError.Error() = %q", got) + } + if got := png.UnsupportedError("x").Error(); !strings.Contains(got, "x") { + t.Fatalf("UnsupportedError.Error() = %q", got) + } + + _ = png.Decode + _ = png.DecodeConfig + _ = png.Encode + _ = png.DefaultCompression + _ = png.NoCompression + _ = png.BestSpeed + _ = png.BestCompression + + _ = png.Encoder{} + level := png.CompressionLevel(0) + if level != 0 { + t.Fatalf("CompressionLevel conversion mismatch: got %v, want 0", level) + } + if got := png.FormatError("x").Error(); !strings.Contains(got, "x") { + t.Fatalf("FormatError.Error() = %q", got) + } + if got := png.UnsupportedError("x").Error(); !strings.Contains(got, "x") { + t.Fatalf("UnsupportedError.Error() = %q", got) + } + + var _ *png.EncoderBuffer + var _ png.EncoderBufferPool = dummyPool{} +} diff --git a/test/std/index/suffixarray/suffixarray_test.go b/test/std/index/suffixarray/suffixarray_test.go new file mode 100644 index 0000000000..0a1afb40bd --- /dev/null +++ b/test/std/index/suffixarray/suffixarray_test.go @@ -0,0 +1,69 @@ +package suffixarray_test + +import ( + "bytes" + "index/suffixarray" + "regexp" + "slices" + "sort" + "testing" +) + +func TestNewBytesLookupAndFindAllIndex(t *testing.T) { + data := []byte("banana bandana") + idx := suffixarray.New(data) + var _ *suffixarray.Index = idx + + if got := idx.Bytes(); !bytes.Equal(got, data) { + t.Fatalf("Bytes mismatch: got %q, want %q", got, data) + } + + all := idx.Lookup([]byte("ana"), -1) + sort.Ints(all) + if !slices.Equal(all, []int{1, 3, 11}) { + t.Fatalf("Lookup(ana,-1) = %v, want [1 3 11]", all) + } + + limited := idx.Lookup([]byte("ana"), 2) + if len(limited) > 2 { + t.Fatalf("Lookup(ana,2) len=%d, want <=2", len(limited)) + } + if got := idx.Lookup([]byte(""), -1); got != nil { + t.Fatalf("Lookup(empty,-1) = %v, want nil", got) + } + if got := idx.Lookup([]byte("zzz"), -1); got != nil { + t.Fatalf("Lookup(zzz,-1) = %v, want nil", got) + } + + r := regexp.MustCompile("ana") + matches := idx.FindAllIndex(r, -1) + if len(matches) != 2 { + t.Fatalf("FindAllIndex(ana,-1) len=%d, want 2", len(matches)) + } + if !slices.Equal(matches[0], []int{1, 4}) || !slices.Equal(matches[1], []int{11, 14}) { + t.Fatalf("FindAllIndex(ana,-1) = %v, want [[1 4] [11 14]]", matches) + } + if got := idx.FindAllIndex(r, 0); got != nil { + t.Fatalf("FindAllIndex(ana,0) = %v, want nil", got) + } +} + +func TestWriteAndRead(t *testing.T) { + src := suffixarray.New([]byte("abracadabra")) + + var buf bytes.Buffer + if err := src.Write(&buf); err != nil { + t.Fatalf("Write failed: %v", err) + } + + var dst suffixarray.Index + if err := dst.Read(&buf); err != nil { + t.Fatalf("Read failed: %v", err) + } + + got := dst.Lookup([]byte("abra"), -1) + sort.Ints(got) + if !slices.Equal(got, []int{0, 7}) { + t.Fatalf("dst.Lookup(abra,-1) = %v, want [0 7]", got) + } +} diff --git a/test/std/io/fs/fs_test.go b/test/std/io/fs/fs_test.go new file mode 100644 index 0000000000..ae3199cb01 --- /dev/null +++ b/test/std/io/fs/fs_test.go @@ -0,0 +1,395 @@ +package fs_test + +import ( + "errors" + "io/fs" + "testing" + "testing/fstest" +) + +func TestValidPath(t *testing.T) { + tests := []struct { + path string + valid bool + }{ + {".", true}, + {"a/b", true}, + {"a/b/c", true}, + {".git", true}, + {"", false}, + {"..", false}, + {"../", false}, + {"a/..", false}, + {"/a", false}, + {"a/", false}, + } + + for _, tt := range tests { + got := fs.ValidPath(tt.path) + if got != tt.valid { + t.Errorf("ValidPath(%q) = %v, want %v", tt.path, got, tt.valid) + } + } +} + +func TestGlob(t *testing.T) { + fsys := fstest.MapFS{ + "a.txt": {Data: []byte("a")}, + "b.txt": {Data: []byte("b")}, + "c.go": {Data: []byte("c")}, + "dir/d.txt": {Data: []byte("d")}, + } + + tests := []struct { + pattern string + matches []string + }{ + {"*.txt", []string{"a.txt", "b.txt"}}, + {"*.go", []string{"c.go"}}, + {"dir/*.txt", []string{"dir/d.txt"}}, + {"*", []string{"a.txt", "b.txt", "c.go", "dir"}}, + } + + for _, tt := range tests { + matches, err := fs.Glob(fsys, tt.pattern) + if err != nil { + t.Errorf("Glob(%q) error: %v", tt.pattern, err) + continue + } + if len(matches) != len(tt.matches) { + t.Errorf("Glob(%q) = %v, want %v", tt.pattern, matches, tt.matches) + continue + } + for i, m := range matches { + if m != tt.matches[i] { + t.Errorf("Glob(%q)[%d] = %q, want %q", tt.pattern, i, m, tt.matches[i]) + } + } + } +} + +func TestReadFile(t *testing.T) { + fsys := fstest.MapFS{ + "test.txt": {Data: []byte("hello world")}, + } + + data, err := fs.ReadFile(fsys, "test.txt") + if err != nil { + t.Fatalf("ReadFile error: %v", err) + } + if string(data) != "hello world" { + t.Errorf("ReadFile = %q, want %q", data, "hello world") + } + + _, err = fs.ReadFile(fsys, "nonexistent.txt") + if err == nil { + t.Error("ReadFile nonexistent should error") + } +} + +func TestReadDir(t *testing.T) { + fsys := fstest.MapFS{ + "a.txt": {Data: []byte("a")}, + "b.txt": {Data: []byte("b")}, + "dir/c.txt": {Data: []byte("c")}, + } + + entries, err := fs.ReadDir(fsys, ".") + if err != nil { + t.Fatalf("ReadDir error: %v", err) + } + if len(entries) != 3 { + t.Errorf("ReadDir len = %d, want 3", len(entries)) + } + + for _, entry := range entries { + name := entry.Name() + if name != "a.txt" && name != "b.txt" && name != "dir" { + t.Errorf("unexpected entry: %s", name) + } + } +} + +func TestStat(t *testing.T) { + fsys := fstest.MapFS{ + "test.txt": {Data: []byte("hello")}, + } + + info, err := fs.Stat(fsys, "test.txt") + if err != nil { + t.Fatalf("Stat error: %v", err) + } + if info.Name() != "test.txt" { + t.Errorf("Stat Name = %q, want %q", info.Name(), "test.txt") + } + if info.Size() != 5 { + t.Errorf("Stat Size = %d, want 5", info.Size()) + } +} + +func TestSub(t *testing.T) { + fsys := fstest.MapFS{ + "dir/a.txt": {Data: []byte("a")}, + "dir/b.txt": {Data: []byte("b")}, + } + + sub, err := fs.Sub(fsys, "dir") + if err != nil { + t.Fatalf("Sub error: %v", err) + } + + data, err := fs.ReadFile(sub, "a.txt") + if err != nil { + t.Fatalf("ReadFile from sub error: %v", err) + } + if string(data) != "a" { + t.Errorf("ReadFile from sub = %q, want %q", data, "a") + } +} + +func TestWalkDir(t *testing.T) { + fsys := fstest.MapFS{ + "a.txt": {Data: []byte("a")}, + "dir/b.txt": {Data: []byte("b")}, + "dir/sub/c.txt": {Data: []byte("c")}, + } + + var paths []string + err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + paths = append(paths, path) + return nil + }) + if err != nil { + t.Fatalf("WalkDir error: %v", err) + } + + expected := []string{".", "a.txt", "dir", "dir/b.txt", "dir/sub", "dir/sub/c.txt"} + if len(paths) != len(expected) { + t.Errorf("WalkDir found %d paths, want %d", len(paths), len(expected)) + } +} + +func TestWalkDirSkipDir(t *testing.T) { + fsys := fstest.MapFS{ + "a.txt": {Data: []byte("a")}, + "dir/b.txt": {Data: []byte("b")}, + "dir/sub/c.txt": {Data: []byte("c")}, + } + + var paths []string + err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + paths = append(paths, path) + if d.IsDir() && d.Name() == "dir" { + return fs.SkipDir + } + return nil + }) + if err != nil { + t.Fatalf("WalkDir error: %v", err) + } + + for _, p := range paths { + if p == "dir/b.txt" || p == "dir/sub" { + t.Errorf("WalkDir should skip %q", p) + } + } +} + +func TestFileInfoToDirEntry(t *testing.T) { + fsys := fstest.MapFS{ + "test.txt": {Data: []byte("hello")}, + } + + info, err := fs.Stat(fsys, "test.txt") + if err != nil { + t.Fatalf("Stat error: %v", err) + } + + entry := fs.FileInfoToDirEntry(info) + if entry.Name() != info.Name() { + t.Errorf("FileInfoToDirEntry Name = %q, want %q", entry.Name(), info.Name()) + } + if entry.IsDir() != info.IsDir() { + t.Errorf("FileInfoToDirEntry IsDir = %v, want %v", entry.IsDir(), info.IsDir()) + } +} + +func TestFormatFileInfo(t *testing.T) { + fsys := fstest.MapFS{ + "test.txt": {Data: []byte("hello")}, + } + + info, err := fs.Stat(fsys, "test.txt") + if err != nil { + t.Fatalf("Stat error: %v", err) + } + + formatted := fs.FormatFileInfo(info) + if formatted == "" { + t.Error("FormatFileInfo returned empty string") + } +} + +func TestFormatDirEntry(t *testing.T) { + fsys := fstest.MapFS{ + "test.txt": {Data: []byte("hello")}, + } + + entries, err := fs.ReadDir(fsys, ".") + if err != nil { + t.Fatalf("ReadDir error: %v", err) + } + if len(entries) == 0 { + t.Fatal("ReadDir returned no entries") + } + + formatted := fs.FormatDirEntry(entries[0]) + if formatted == "" { + t.Error("FormatDirEntry returned empty string") + } +} + +func TestPathError(t *testing.T) { + err := &fs.PathError{ + Op: "open", + Path: "/test", + Err: errors.New("test error"), + } + + errStr := err.Error() + if errStr == "" { + t.Error("PathError.Error() returned empty string") + } +} + +func TestFileMode(t *testing.T) { + tests := []struct { + mode fs.FileMode + isDir bool + }{ + {fs.ModeDir, true}, + {0, false}, + {fs.ModeDir | 0755, true}, + } + + for _, tt := range tests { + if tt.mode.IsDir() != tt.isDir { + t.Errorf("FileMode(%v).IsDir() = %v, want %v", tt.mode, tt.mode.IsDir(), tt.isDir) + } + } +} + +func TestErrors(t *testing.T) { + if fs.ErrInvalid == nil { + t.Error("ErrInvalid should not be nil") + } + if fs.SkipDir == nil { + t.Error("SkipDir should not be nil") + } + if fs.SkipAll == nil { + t.Error("SkipAll should not be nil") + } + if fs.ErrClosed == nil { + t.Error("ErrClosed should not be nil") + } + if fs.ErrExist == nil { + t.Error("ErrExist should not be nil") + } + if fs.ErrNotExist == nil { + t.Error("ErrNotExist should not be nil") + } + if fs.ErrPermission == nil { + t.Error("ErrPermission should not be nil") + } +} + +func TestInterfaces(t *testing.T) { + fsys := fstest.MapFS{ + "test.txt": {Data: []byte("hello")}, + } + + var _ fs.FS = fsys + var _ fs.GlobFS = fsys + var _ fs.ReadDirFS = fsys + var _ fs.ReadFileFS = fsys + var _ fs.StatFS = fsys + var _ fs.SubFS = fsys + + file, err := fsys.Open("test.txt") + if err != nil { + t.Fatalf("Open error: %v", err) + } + defer file.Close() + + var _ fs.File = file + + if rdFile, ok := file.(fs.ReadDirFile); ok { + var _ fs.ReadDirFile = rdFile + } +} + +func TestFileModeExtended(t *testing.T) { + tests := []struct { + mode fs.FileMode + isRegular bool + perm fs.FileMode + typeMode fs.FileMode + }{ + {0644, true, 0644, 0}, + {fs.ModeDir | 0755, false, 0755, fs.ModeDir}, + {fs.ModeSymlink | 0777, false, 0777, fs.ModeSymlink}, + } + + for _, tt := range tests { + if tt.mode.IsRegular() != tt.isRegular { + t.Errorf("FileMode(%v).IsRegular() = %v, want %v", tt.mode, tt.mode.IsRegular(), tt.isRegular) + } + if tt.mode.Perm() != tt.perm { + t.Errorf("FileMode(%v).Perm() = %v, want %v", tt.mode, tt.mode.Perm(), tt.perm) + } + if tt.mode.Type() != tt.typeMode { + t.Errorf("FileMode(%v).Type() = %v, want %v", tt.mode, tt.mode.Type(), tt.typeMode) + } + str := tt.mode.String() + if str == "" { + t.Errorf("FileMode(%v).String() returned empty string", tt.mode) + } + } +} + +func TestPathErrorExtended(t *testing.T) { + baseErr := errors.New("test error") + err := &fs.PathError{ + Op: "open", + Path: "/test", + Err: baseErr, + } + + if err.Unwrap() != baseErr { + t.Errorf("PathError.Unwrap() = %v, want %v", err.Unwrap(), baseErr) + } + if err.Timeout() { + t.Error("PathError.Timeout() should return false for non-timeout error") + } +} + +func TestWalkDirFunc(t *testing.T) { + fsys := fstest.MapFS{ + "a.txt": {Data: []byte("a")}, + } + + var walkFunc fs.WalkDirFunc = func(path string, d fs.DirEntry, err error) error { + return nil + } + + err := fs.WalkDir(fsys, ".", walkFunc) + if err != nil { + t.Errorf("WalkDir with WalkDirFunc error: %v", err) + } +} diff --git a/test/std/io/fs/go126_symbols_test.go b/test/std/io/fs/go126_symbols_test.go new file mode 100644 index 0000000000..abaaacbfa3 --- /dev/null +++ b/test/std/io/fs/go126_symbols_test.go @@ -0,0 +1,35 @@ +//go:build go1.26 + +package fs_test + +import ( + "io/fs" + "testing" + "testing/fstest" +) + +func TestReadLinkFS(t *testing.T) { + filesystem := fstest.MapFS{ + "target.txt": &fstest.MapFile{Data: []byte("target")}, + "link.txt": &fstest.MapFile{Data: []byte("target.txt"), Mode: fs.ModeSymlink}, + } + var readLinkFS fs.ReadLinkFS = filesystem + got, err := fs.ReadLink(readLinkFS, "link.txt") + if err != nil || got != "target.txt" { + t.Fatalf("ReadLink = %q, %v; want %q, nil", got, err, "target.txt") + } + info, err := fs.Lstat(filesystem, "link.txt") + if err != nil { + t.Fatal(err) + } + if info.Mode()&fs.ModeSymlink == 0 { + t.Fatalf("Lstat mode = %v, want symlink", info.Mode()) + } + targetInfo, err := fs.Stat(filesystem, "link.txt") + if err != nil { + t.Fatal(err) + } + if targetInfo.Mode()&fs.ModeSymlink != 0 || targetInfo.Size() != int64(len("target")) { + t.Fatalf("Stat followed link to mode %v, size %d", targetInfo.Mode(), targetInfo.Size()) + } +} diff --git a/test/std/io/io_bench_test.go b/test/std/io/io_bench_test.go new file mode 100644 index 0000000000..57e6ce5c3b --- /dev/null +++ b/test/std/io/io_bench_test.go @@ -0,0 +1,26 @@ +package io_test + +import ( + "bytes" + "io" + "strings" + "testing" +) + +func BenchmarkCopy(b *testing.B) { + data := []byte(strings.Repeat("go", 128)) + for i := 0; i < b.N; i++ { + var dst bytes.Buffer + if _, err := io.Copy(&dst, bytes.NewReader(data)); err != nil { + b.Fatalf("copy err %v", err) + } + } +} + +func BenchmarkReadAll(b *testing.B) { + for i := 0; i < b.N; i++ { + if _, err := io.ReadAll(strings.NewReader(strings.Repeat("abc", 256))); err != nil { + b.Fatalf("readall err %v", err) + } + } +} diff --git a/test/std/io/io_test.go b/test/std/io/io_test.go new file mode 100644 index 0000000000..6b2b293eae --- /dev/null +++ b/test/std/io/io_test.go @@ -0,0 +1,457 @@ +package io_test + +import ( + "bytes" + "errors" + "fmt" + "io" + "strings" + "testing" + "time" +) + +type staticWriterAt struct { + buf []byte +} + +func newStaticWriterAt(size int) *staticWriterAt { + return &staticWriterAt{buf: make([]byte, size)} +} + +func (s *staticWriterAt) WriteAt(p []byte, off int64) (int, error) { + if int(off)+len(p) > len(s.buf) { + return 0, io.ErrShortWrite + } + copy(s.buf[int(off):], p) + return len(p), nil +} + +func TestCopyAndCopyBuffer(t *testing.T) { + src := strings.NewReader(strings.Repeat("goplus", 3)) + var dst bytes.Buffer + n, err := io.Copy(&dst, src) + if err != nil { + t.Fatalf("Copy error: %v", err) + } + if got, want := n, int64(dst.Len()); got != want { + t.Fatalf("Copy wrote %d bytes, want %d", got, want) + } + if !strings.Contains(dst.String(), "goplus") { + t.Fatalf("Copy produced %q", dst.String()) + } + + buf := make([]byte, 4) + src2 := strings.NewReader("hello world") + dst.Reset() + n, err = io.CopyBuffer(&dst, src2, buf) + if err != nil { + t.Fatalf("CopyBuffer error: %v", err) + } + if n != int64(len("hello world")) { + t.Fatalf("CopyBuffer wrote %d", n) + } +} + +func TestCopyNAndReadHelpers(t *testing.T) { + src := strings.NewReader("abcdefghij") + var dst bytes.Buffer + n, err := io.CopyN(&dst, src, 4) + if err != nil || n != 4 { + t.Fatalf("CopyN got (%d,%v)", n, err) + } + if dst.String() != "abcd" { + t.Fatalf("CopyN result %q", dst.String()) + } + + src2 := strings.NewReader("short") + dst.Reset() + _, err = io.CopyN(&dst, src2, 10) + if !errors.Is(err, io.EOF) { + t.Fatalf("CopyN expected EOF, got %v", err) + } + + data := strings.NewReader("read-all") + all, err := io.ReadAll(data) + if err != nil || string(all) != "read-all" { + t.Fatalf("ReadAll = %q err=%v", all, err) + } + + buf := make([]byte, 3) + _, err = io.ReadAtLeast(strings.NewReader("abc"), buf, 2) + if err != nil { + t.Fatalf("ReadAtLeast unexpected error %v", err) + } + _, err = io.ReadAtLeast(strings.NewReader("xy"), buf[:2], 3) + if !errors.Is(err, io.ErrShortBuffer) { + t.Fatalf("ReadAtLeast expected ErrShortBuffer got %v", err) + } + fullEnough := make([]byte, 4) + _, err = io.ReadAtLeast(strings.NewReader("zz"), fullEnough, 4) + if !errors.Is(err, io.ErrUnexpectedEOF) { + t.Fatalf("ReadAtLeast expected ErrUnexpectedEOF got %v", err) + } + + fullBuf := make([]byte, 5) + _, err = io.ReadFull(strings.NewReader("12345"), fullBuf) + if err != nil || string(fullBuf) != "12345" { + t.Fatalf("ReadFull=%q err=%v", fullBuf, err) + } + _, err = io.ReadFull(strings.NewReader("123"), make([]byte, 5)) + if !errors.Is(err, io.ErrUnexpectedEOF) { + t.Fatalf("ReadFull expected ErrUnexpectedEOF got %v", err) + } +} + +func TestLimitReaderAndLimitedReader(t *testing.T) { + base := strings.NewReader("abcdef") + limited := io.LimitReader(base, 3) + got, err := io.ReadAll(limited) + if err != nil || string(got) != "abc" { + t.Fatalf("LimitReader => %q err=%v", got, err) + } + + lr := io.LimitedReader{R: strings.NewReader("12345"), N: 2} + buf := make([]byte, 4) + n, err := lr.Read(buf) + if err != nil && !errors.Is(err, io.EOF) { + t.Fatalf("LimitedReader read err %v", err) + } + if n != 2 || string(buf[:n]) != "12" { + t.Fatalf("LimitedReader read %d %q", n, buf[:n]) + } +} + +func TestMultiReaderAndTeeReader(t *testing.T) { + mr := io.MultiReader(strings.NewReader("go"), strings.NewReader("+"), strings.NewReader("plus")) + res, err := io.ReadAll(mr) + if err != nil || string(res) != "go+plus" { + t.Fatalf("MultiReader => %q err=%v", res, err) + } + + var teeTarget bytes.Buffer + teed := io.TeeReader(strings.NewReader("mirror"), &teeTarget) + data, err := io.ReadAll(teed) + if err != nil || string(data) != "mirror" { + t.Fatalf("TeeReader read %q", data) + } + if teeTarget.String() != "mirror" { + t.Fatalf("TeeReader duplicated %q", teeTarget.String()) + } +} + +func TestMultiWriterAndWriteString(t *testing.T) { + var a, b bytes.Buffer + mw := io.MultiWriter(&a, &b, io.Discard) + if _, err := mw.Write([]byte("hi")); err != nil { + t.Fatalf("MultiWriter err %v", err) + } + if a.String() != "hi" || b.String() != "hi" { + t.Fatalf("MultiWriter outputs %q %q", a.String(), b.String()) + } + + var buf bytes.Buffer + n, err := io.WriteString(&buf, "string") + if err != nil || n != len("string") || buf.String() != "string" { + t.Fatalf("WriteString => %d %q err=%v", n, buf.String(), err) + } + + var builder strings.Builder + if sw, ok := interface{}(&builder).(io.StringWriter); !ok { + t.Fatal("strings.Builder should implement io.StringWriter") + } else { + if _, err := sw.WriteString("builder"); err != nil { + t.Fatalf("StringWriter err %v", err) + } + } +} + +func TestPipeAndErrSentinels(t *testing.T) { + r, w := io.Pipe() + errCh := make(chan error, 2) + go func() { + _, err := io.WriteString(w, "piped") + errCh <- err + errCh <- w.Close() + }() + result, err := io.ReadAll(r) + if err != nil || string(result) != "piped" { + t.Fatalf("Pipe read %q err=%v", result, err) + } + if err := r.Close(); err != nil { + t.Fatalf("PipeReader close err %v", err) + } + for i := 0; i < 2; i++ { + if err := <-errCh; err != nil { + t.Fatalf("pipe goroutine err %v", err) + } + } + + if !errors.Is(io.ErrShortWrite, io.ErrShortWrite) || !errors.Is(io.EOF, io.EOF) { + t.Fatal("error sentinels should compare equal") + } + if !strings.Contains(io.ErrNoProgress.Error(), "no data") { + t.Fatalf("ErrNoProgress message %q", io.ErrNoProgress) + } + + closedR, closedW := io.Pipe() + if err := closedR.Close(); err != nil { + t.Fatalf("closedR.Close() err %v", err) + } + if _, err := io.WriteString(closedW, "x"); !errors.Is(err, io.ErrClosedPipe) { + t.Fatalf("expected ErrClosedPipe got %v", err) + } +} + +func TestPipeWriterAndReaderCloseWithError(t *testing.T) { + pr, pw := io.Pipe() + readDone := make(chan error, 2) + go func() { + buf := make([]byte, 2) + if n, err := pr.Read(buf); n != 2 || err != nil { + readDone <- fmt.Errorf("first read n=%d err=%v", n, err) + } else { + readDone <- nil + } + _, err := pr.Read(buf) + readDone <- err + }() + if _, err := pw.Write([]byte("hi")); err != nil { + t.Fatalf("PipeWriter Write err %v", err) + } + if err := pr.CloseWithError(io.ErrClosedPipe); err != nil { + t.Fatalf("PipeReader CloseWithError err %v", err) + } + if _, err := pw.Write([]byte("!")); !errors.Is(err, io.ErrClosedPipe) { + t.Fatalf("PipeWriter Write after close err %v", err) + } + if err := pw.Close(); err != nil { + t.Fatalf("PipeWriter close err %v", err) + } + if err := <-readDone; err != nil { + t.Fatal(err) + } + if err := <-readDone; !errors.Is(err, io.ErrClosedPipe) { + t.Fatalf("second read expected ErrClosedPipe got %v", err) + } +} + +func TestNopCloserAndReadSeekInterfaces(t *testing.T) { + r := strings.NewReader("closer") + nc := io.NopCloser(r) + data, err := io.ReadAll(nc) + if err != nil || string(data) != "closer" { + t.Fatalf("NopCloser read %q err=%v", data, err) + } + if err := nc.Close(); err != nil { + t.Fatalf("NopCloser close err %v", err) + } + + if _, ok := interface{}(readSeekCloserWrapper{strings.NewReader("data")}).(io.ReadSeekCloser); !ok { + t.Fatal("wrapper should satisfy ReadSeekCloser") + } + + var _ io.ReadSeeker = strings.NewReader("rs") + var _ io.Seeker = io.NewSectionReader(strings.NewReader("seek"), 0, 4) + var _ io.ReadWriteCloser = pipeLike{} + var _ io.ReadWriter = &bytes.Buffer{} + var _ io.ReadWriteSeeker = newSectionSeeker() + var _ io.WriterAt = newStaticWriterAt(0) + var _ io.ReaderAt = io.NewSectionReader(strings.NewReader("abcd"), 0, 4) + var _ io.WriterTo = &bytes.Buffer{} + var _ io.ReaderFrom = &bytes.Buffer{} + var _ io.ByteReader = bytes.NewReader([]byte("b")) + var _ io.ByteScanner = bufioLike{} + var _ io.ByteWriter = &byteRecorder{} + var _ io.RuneReader = strings.NewReader("rune") + var _ io.RuneScanner = bufioLike{} + var _ io.WriteSeeker = writeSeekerStub{} +} + +type pipeLike struct{} + +func (p pipeLike) Read(b []byte) (int, error) { return copy(b, "rw"), nil } +func (p pipeLike) Write(b []byte) (int, error) { return len(b), nil } +func (p pipeLike) Close() error { return nil } + +type byteRecorder struct{} + +func (byteRecorder) WriteByte(c byte) error { return nil } + +type bufioLike struct{} + +func (bufioLike) ReadByte() (byte, error) { return 'x', nil } +func (bufioLike) UnreadByte() error { return nil } +func (bufioLike) ReadRune() (rune, int, error) { + return '好', 3, nil +} +func (bufioLike) UnreadRune() error { return nil } + +type readSeekCloserWrapper struct { + *strings.Reader +} + +func (readSeekCloserWrapper) Close() error { return nil } + +type writeSeekerStub struct{} + +func (writeSeekerStub) Write(p []byte) (int, error) { return len(p), nil } +func (writeSeekerStub) Seek(offset int64, whence int) (int64, error) { + return offset, nil +} + +type sectionSeeker struct { + *io.SectionReader +} + +func newSectionSeeker() *sectionSeeker { + return §ionSeeker{io.NewSectionReader(strings.NewReader("abcdef"), 0, 6)} +} + +func (s *sectionSeeker) Read(p []byte) (int, error) { return s.SectionReader.Read(p) } +func (s *sectionSeeker) Write(p []byte) (int, error) { return len(p), nil } +func (s *sectionSeeker) Seek(offset int64, whence int) (int64, error) { + return s.SectionReader.Seek(offset, whence) +} + +func TestSectionReaderAndOffsetWriter(t *testing.T) { + base := strings.NewReader("0123456789") + section := io.NewSectionReader(base, 2, 5) + buf := make([]byte, 5) + n, err := section.Read(buf) + if err != nil && !errors.Is(err, io.EOF) { + t.Fatalf("SectionReader read err %v", err) + } + if n != 5 || string(buf) != "23456" { + t.Fatalf("SectionReader read %d %q", n, buf) + } + + if _, err := section.Seek(1, io.SeekStart); err != nil { + t.Fatalf("SeekStart err %v", err) + } + if pos, err := section.Seek(-1, io.SeekCurrent); err != nil || pos != 0 { + t.Fatalf("SeekCurrent pos=%d err=%v", pos, err) + } + if pos, err := section.Seek(0, io.SeekEnd); err != nil || pos != 5 { + t.Fatalf("SeekEnd pos=%d err=%v", pos, err) + } + readBack := make([]byte, 3) + if _, err := section.ReadAt(readBack, 1); err != nil && !errors.Is(err, io.EOF) { + t.Fatalf("SectionReader ReadAt err %v", err) + } + if string(readBack) != "345" { + t.Fatalf("SectionReader ReadAt got %q", readBack) + } + if section.Size() != 5 { + t.Fatalf("SectionReader size=%d", section.Size()) + } + if outer, off, n := section.Outer(); outer == nil || off != 2 || n != 5 { + t.Fatalf("SectionReader Outer returned reader=%v off=%d n=%d", outer, off, n) + } + + writer := newStaticWriterAt(16) + offset := io.NewOffsetWriter(writer, 4) + if _, err := offset.Write([]byte("DATA")); err != nil { + t.Fatalf("OffsetWriter err %v", err) + } + if string(writer.buf[4:8]) != "DATA" { + t.Fatalf("OffsetWriter wrote %q", writer.buf) + } + if _, err := offset.Seek(0, io.SeekStart); err != nil { + t.Fatalf("OffsetWriter seek err %v", err) + } + if _, err := offset.WriteAt([]byte("++"), 1); err != nil { + t.Fatalf("OffsetWriter WriteAt err %v", err) + } + if string(writer.buf[5:7]) != "++" { + t.Fatalf("OffsetWriter WriteAt result %q", writer.buf[4:8]) + } +} + +func TestDiscardAndInterfaces(t *testing.T) { + if _, err := io.Discard.Write([]byte("ignored")); err != nil { + t.Fatalf("Discard write err %v", err) + } + + var deadlinePipe struct { + r *io.PipeReader + w *io.PipeWriter + } + deadlinePipe.r, deadlinePipe.w = io.Pipe() + if err := deadlinePipe.w.CloseWithError(io.ErrClosedPipe); err != nil { + t.Fatalf("PipeWriter.CloseWithError err %v", err) + } + if err := deadlinePipe.r.Close(); err != nil { + t.Fatalf("PipeReader.Close err %v", err) + } + + var _ io.Closer = io.NopCloser(strings.NewReader("c")) + var _ io.ReadCloser = io.NopCloser(strings.NewReader("rc")) + var _ io.WriteCloser = closeWriter{} +} + +type closeWriter struct{} + +func (closeWriter) Write(p []byte) (int, error) { return len(p), nil } +func (closeWriter) Close() error { return nil } + +func TestReaderFromAndWriterToInteractions(t *testing.T) { + r := strings.NewReader(strings.Repeat("abc", 3)) + var buf bytes.Buffer + if wt, ok := interface{}(&buf).(io.WriterTo); !ok { + t.Fatal("bytes.Buffer should implement WriterTo") + } else { + if _, err := wt.WriteTo(io.Discard); err != nil { + t.Fatalf("WriterTo err %v", err) + } + } + if rf, ok := interface{}(&buf).(io.ReaderFrom); !ok { + t.Fatal("bytes.Buffer should implement ReaderFrom") + } else { + if _, err := rf.ReadFrom(r); err != nil { + t.Fatalf("ReaderFrom err %v", err) + } + } + if buf.String() != "abcabcabc" { + t.Fatalf("ReaderFrom copied %q", buf.String()) + } +} + +func TestReaderInterfaceBasic(t *testing.T) { + var reader io.Reader = strings.NewReader("reader") + buf := make([]byte, 3) + if n, err := reader.Read(buf); n == 0 || (err != nil && !errors.Is(err, io.EOF)) { + t.Fatalf("Reader.Read n=%d err=%v", n, err) + } +} + +func TestStringReaderRuneScanner(t *testing.T) { + s := strings.NewReader("héllo") + r, size, err := s.ReadRune() + if err != nil || r != 'h' || size != 1 { + t.Fatalf("ReadRune first=%q size=%d err=%v", r, size, err) + } + if err := s.UnreadRune(); err != nil { + t.Fatalf("UnreadRune err %v", err) + } + r, size, err = s.ReadRune() + if err != nil || r != 'h' || size != 1 { + t.Fatalf("ReadRune after unread=%q size=%d err=%v", r, size, err) + } +} + +func TestDeadlineLikePipe(t *testing.T) { + r, w := io.Pipe() + errCh := make(chan error, 1) + go func() { + time.Sleep(5 * time.Millisecond) + errCh <- w.CloseWithError(io.ErrClosedPipe) + }() + buf := make([]byte, 8) + _, err := r.Read(buf) + if !errors.Is(err, io.ErrClosedPipe) { + t.Fatalf("Pipe read expected ErrClosedPipe got %v", err) + } + if err := <-errCh; err != nil { + t.Fatalf("CloseWithError err %v", err) + } +} diff --git a/test/std/io/ioutil/ioutil_readdir_llgo_test.go b/test/std/io/ioutil/ioutil_readdir_llgo_test.go new file mode 100644 index 0000000000..a18afbd5fc --- /dev/null +++ b/test/std/io/ioutil/ioutil_readdir_llgo_test.go @@ -0,0 +1,9 @@ +//go:build llgo + +package ioutil_test + +import "testing" + +func TestReadDir(t *testing.T) { + t.Skip("TODO(llgo#os-readdir): os.File.Readdir not implemented") +} diff --git a/test/std/io/ioutil/ioutil_readdir_test.go b/test/std/io/ioutil/ioutil_readdir_test.go new file mode 100644 index 0000000000..ecabb8f926 --- /dev/null +++ b/test/std/io/ioutil/ioutil_readdir_test.go @@ -0,0 +1,45 @@ +//go:build !llgo + +package ioutil_test + +import ( + "io/fs" + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +func TestReadDir(t *testing.T) { + dir := t.TempDir() + names := []string{"alpha.txt", "beta.txt", "gopher"} + for _, name := range names[:2] { + if err := os.WriteFile(filepath.Join(dir, name), []byte(name), 0o600); err != nil { + t.Fatalf("WriteFile %s error: %v", name, err) + } + } + if err := os.Mkdir(filepath.Join(dir, names[2]), 0o755); err != nil { + t.Fatalf("Mkdir %s error: %v", names[2], err) + } + + infos, err := ioutil.ReadDir(dir) + if err != nil { + t.Fatalf("ReadDir error: %v", err) + } + if len(infos) != len(names) { + t.Fatalf("ReadDir returned %d entries, want %d", len(infos), len(names)) + } + + got := make(map[string]fs.FileInfo) + for _, info := range infos { + got[info.Name()] = info + } + for _, name := range names { + if _, ok := got[name]; !ok { + t.Fatalf("ReadDir missing entry %s", name) + } + } + if !got[names[2]].IsDir() { + t.Fatalf("ReadDir expected %s to be directory", names[2]) + } +} diff --git a/test/std/io/ioutil/ioutil_test.go b/test/std/io/ioutil/ioutil_test.go new file mode 100644 index 0000000000..5815cbedd2 --- /dev/null +++ b/test/std/io/ioutil/ioutil_test.go @@ -0,0 +1,124 @@ +package ioutil_test + +import ( + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestDiscard(t *testing.T) { + wrote, err := ioutil.Discard.Write([]byte("hello")) + if err != nil { + t.Fatalf("Discard.Write returned error: %v", err) + } + if wrote != len("hello") { + t.Fatalf("Discard.Write wrote %d bytes, want %d", wrote, len("hello")) + } +} + +func TestNopCloser(t *testing.T) { + rc := ioutil.NopCloser(strings.NewReader("data")) + defer func() { + if err := rc.Close(); err != nil { + t.Fatalf("Close returned error: %v", err) + } + }() + + content, err := io.ReadAll(rc) + if err != nil { + t.Fatalf("ReadAll error: %v", err) + } + if string(content) != "data" { + t.Fatalf("ReadAll content = %q, want %q", content, "data") + } +} + +func TestReadAll(t *testing.T) { + reader := strings.NewReader("prefix:" + strings.Repeat("x", 32)) + data, err := ioutil.ReadAll(reader) + if err != nil { + t.Fatalf("ReadAll error: %v", err) + } + if string(data) != "prefix:"+strings.Repeat("x", 32) { + t.Fatalf("ReadAll data mismatch: %q", data) + } +} + +func TestReadFileAndWriteFile(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "sample.txt") + payload := []byte("sample data") + + if err := ioutil.WriteFile(path, payload, 0o600); err != nil { + t.Fatalf("WriteFile error: %v", err) + } + data, err := ioutil.ReadFile(path) + if err != nil { + t.Fatalf("ReadFile error: %v", err) + } + if string(data) != string(payload) { + t.Fatalf("ReadFile data = %q, want %q", data, payload) + } +} + +func TestTempDir(t *testing.T) { + base := t.TempDir() + temp, err := ioutil.TempDir(base, "ioutil-dir-") + if err != nil { + t.Fatalf("TempDir error: %v", err) + } + t.Cleanup(func() { + if err := os.RemoveAll(temp); err != nil { + t.Errorf("RemoveAll(%q): %v", temp, err) + } + }) + + info, err := os.Stat(temp) + if err != nil { + t.Fatalf("Stat temp dir error: %v", err) + } + if !info.IsDir() { + t.Fatalf("TempDir path %s not a directory", temp) + } + if !strings.HasPrefix(filepath.Base(temp), "ioutil-dir-") { + t.Fatalf("TempDir base %q missing prefix", filepath.Base(temp)) + } +} + +func TestTempFile(t *testing.T) { + base := t.TempDir() + f, err := ioutil.TempFile(base, "ioutil-file-") + if err != nil { + t.Fatalf("TempFile error: %v", err) + } + t.Cleanup(func() { + name := f.Name() + if err := f.Close(); err != nil { + t.Errorf("TempFile.Close: %v", err) + } + if err := os.Remove(name); err != nil && !os.IsNotExist(err) { + t.Errorf("Remove(%q): %v", name, err) + } + }) + + if !strings.HasPrefix(filepath.Base(f.Name()), "ioutil-file-") { + t.Fatalf("TempFile base %q missing prefix", filepath.Base(f.Name())) + } + + if _, err := f.Write([]byte("temp")); err != nil { + t.Fatalf("TempFile write error: %v", err) + } + if _, err := f.Seek(0, io.SeekStart); err != nil { + t.Fatalf("TempFile seek error: %v", err) + } + content, err := io.ReadAll(f) + if err != nil { + t.Fatalf("TempFile read error: %v", err) + } + if string(content) != "temp" { + t.Fatalf("TempFile content = %q, want %q", content, "temp") + } +} diff --git a/test/std/iter/iter_test.go b/test/std/iter/iter_test.go new file mode 100644 index 0000000000..6ff8433464 --- /dev/null +++ b/test/std/iter/iter_test.go @@ -0,0 +1,303 @@ +package iter_test + +import ( + "testing" + + "iter" +) + +func TestPullIteratesSequence(t *testing.T) { + seq := iter.Seq[int](func(yield func(int) bool) { + for _, v := range [...]int{10, 11, 12} { + if !yield(v) { + return + } + } + }) + next, stop := iter.Pull(seq) + defer stop() + + wants := []int{10, 11, 12} + for i, want := range wants { + got, ok := next() + if !ok || got != want { + t.Fatalf("next call %d = (%d, %v), want (%d, true)", i, got, ok, want) + } + } + + if got, ok := next(); ok || got != 0 { + t.Fatalf("next after exhaustion = (%d, %v), want (0, false)", got, ok) + } + if got, ok := next(); ok || got != 0 { + t.Fatalf("next after exhaustion repeat = (%d, %v), want (0, false)", got, ok) + } +} + +func TestPullStopEarly(t *testing.T) { + produced := 0 + seq := iter.Seq[int](func(yield func(int) bool) { + for i := 0; i < 5; i++ { + produced++ + if !yield(i) { + return + } + } + }) + + next, stop := iter.Pull(seq) + if got, ok := next(); !ok || got != 0 { + t.Fatalf("first next = (%d, %v), want (0, true)", got, ok) + } + stop() + stop() + if produced != 1 { + t.Fatalf("sequence produced %d values, want 1 after early stop", produced) + } + if got, ok := next(); ok || got != 0 { + t.Fatalf("next after stop = (%d, %v), want (0, false)", got, ok) + } +} + +func TestPull2IteratesPairs(t *testing.T) { + seq := iter.Seq2[int, string](func(yield func(int, string) bool) { + pairs := []struct { + k int + v string + }{ + {0, "zero"}, + {1, "one"}, + {2, "two"}, + } + for _, p := range pairs { + if !yield(p.k, p.v) { + return + } + } + }) + + next, stop := iter.Pull2(seq) + defer stop() + + for idx, want := range []struct { + k int + v string + }{ + {0, "zero"}, + {1, "one"}, + {2, "two"}, + } { + k, v, ok := next() + if !ok || k != want.k || v != want.v { + t.Fatalf("next call %d = (%d, %s, %v), want (%d, %s, true)", idx, k, v, ok, want.k, want.v) + } + } + + if k, v, ok := next(); ok || k != 0 || v != "" { + t.Fatalf("next after exhaustion = (%d, %q, %v), want (0, %q, false)", k, v, ok, "") + } + if k, v, ok := next(); ok || k != 0 || v != "" { + t.Fatalf("next after exhaustion repeat = (%d, %q, %v), want (0, %q, false)", k, v, ok, "") + } +} + +var ( + doubleNextSlot func() (int, bool) + doubleNextSlot2 func() (int, int, bool) +) + +func TestPullDoubleNextPanics(t *testing.T) { + seq := iter.Seq[int](func(_ func(int) bool) { + defer func() { + if recover() != nil { + doubleNextSlot = nil + } + }() + doubleNextSlot() + }) + + next, stop := iter.Pull(seq) + defer stop() + doubleNextSlot = next + if _, ok := next(); ok { + t.Fatal("double next returned ok, want panic path") + } + if doubleNextSlot != nil { + t.Fatal("double next did not trigger panic guard") + } + doubleNextSlot = nil +} + +func TestPull2DoubleNextPanics(t *testing.T) { + seq := iter.Seq2[int, int](func(_ func(int, int) bool) { + defer func() { + if recover() != nil { + doubleNextSlot2 = nil + } + }() + doubleNextSlot2() + }) + + next, stop := iter.Pull2(seq) + defer stop() + doubleNextSlot2 = next + if _, _, ok := next(); ok { + t.Fatal("double next returned ok, want panic path") + } + if doubleNextSlot2 != nil { + t.Fatal("double next did not trigger panic guard") + } + doubleNextSlot2 = nil +} + +var ( + doubleYieldSlot func(int) bool + doubleYieldSlot2 func(int, int) bool +) + +func TestPullDoubleYieldPanics(t *testing.T) { + seq := iter.Seq[int](func(yield func(int) bool) { + doubleYieldSlot = yield + if !yield(5) { + return + } + }) + + next, stop := iter.Pull(seq) + defer stop() + if _, ok := next(); !ok { + t.Fatal("first next failed") + } + if doubleYieldSlot == nil { + t.Fatal("yield function not captured") + } + panicked := false + func() { + defer func() { + if recover() != nil { + doubleYieldSlot = nil + panicked = true + } + }() + doubleYieldSlot(10) + }() + if !panicked { + t.Fatal("double yield did not panic") + } + if doubleYieldSlot != nil { + t.Fatal("double yield did not trigger panic guard") + } +} + +func TestPull2DoubleYieldPanics(t *testing.T) { + seq := iter.Seq2[int, int](func(yield func(int, int) bool) { + doubleYieldSlot2 = yield + if !yield(7, 9) { + return + } + }) + + next, stop := iter.Pull2(seq) + defer stop() + if _, _, ok := next(); !ok { + t.Fatal("first next failed") + } + if doubleYieldSlot2 == nil { + t.Fatal("yield function not captured") + } + panicked := false + func() { + defer func() { + if recover() != nil { + doubleYieldSlot2 = nil + panicked = true + } + }() + doubleYieldSlot2(11, 13) + }() + if !panicked { + t.Fatal("double yield did not panic") + } + if doubleYieldSlot2 != nil { + t.Fatal("double yield did not trigger panic guard") + } +} + +func TestPullPropagatesPanic(t *testing.T) { + seq := iter.Seq[int](func(func(int) bool) { + panic("boom") + }) + + next, stop := iter.Pull(seq) + defer stop() + assertPanicsWith(t, "boom", func() { next() }) + if v, ok := next(); ok || v != 0 { + t.Fatalf("next after panic = (%d, %v), want (0, false)", v, ok) + } +} + +func TestPull2PropagatesPanic(t *testing.T) { + seq := iter.Seq2[int, int](func(func(int, int) bool) { + panic("boom2") + }) + + next, stop := iter.Pull2(seq) + defer stop() + assertPanicsWith(t, "boom2", func() { next() }) + if k, v, ok := next(); ok || k != 0 || v != 0 { + t.Fatalf("next after panic = (%d, %d, %v), want (0, 0, false)", k, v, ok) + } +} + +func TestPullPanicOnStop(t *testing.T) { + seq := iter.Seq[int](func(yield func(int) bool) { + for { + if !yield(55) { + panic("cleanup") + } + } + }) + + next, stop := iter.Pull(seq) + if v, ok := next(); !ok || v != 55 { + t.Fatalf("first next = (%d, %v), want (55, true)", v, ok) + } + assertPanicsWith(t, "cleanup", func() { stop() }) + if v, ok := next(); ok || v != 0 { + t.Fatalf("next after stop panic = (%d, %v), want (0, false)", v, ok) + } + stop() +} + +func TestPull2PanicOnStop(t *testing.T) { + seq := iter.Seq2[int, int](func(yield func(int, int) bool) { + for { + if !yield(21, 34) { + panic("cleanup2") + } + } + }) + + next, stop := iter.Pull2(seq) + if k, v, ok := next(); !ok || k != 21 || v != 34 { + t.Fatalf("first next = (%d, %d, %v), want (21, 34, true)", k, v, ok) + } + assertPanicsWith(t, "cleanup2", func() { stop() }) + if k, v, ok := next(); ok || k != 0 || v != 0 { + t.Fatalf("next after stop panic = (%d, %d, %v), want (0, 0, false)", k, v, ok) + } + stop() +} + +func assertPanicsWith(t *testing.T, want any, fn func()) { + t.Helper() + defer func() { + recovered := recover() + switch { + case recovered == nil: + t.Fatalf("expected panic %v, but function returned normally", want) + case recovered != want: + t.Fatalf("panic = %v, want %v", recovered, want) + } + }() + fn() +} diff --git a/test/std/log/log_test.go b/test/std/log/log_test.go new file mode 100644 index 0000000000..773da9cc83 --- /dev/null +++ b/test/std/log/log_test.go @@ -0,0 +1,485 @@ +package log_test + +import ( + "bytes" + "log" + "strings" + "testing" +) + +// Test log constants +func TestConstants(t *testing.T) { + // Test that constants are defined + _ = log.Ldate + _ = log.Ltime + _ = log.Lmicroseconds + _ = log.Llongfile + _ = log.Lshortfile + _ = log.LUTC + _ = log.Lmsgprefix + _ = log.LstdFlags + + // Test that LstdFlags is Ldate | Ltime + if log.LstdFlags != (log.Ldate | log.Ltime) { + t.Errorf("LstdFlags = %d, want %d", log.LstdFlags, log.Ldate|log.Ltime) + } +} + +// Test log.New +func TestNew(t *testing.T) { + var buf bytes.Buffer + logger := log.New(&buf, "TEST: ", log.Ldate|log.Ltime) + if logger == nil { + t.Fatal("New returned nil") + } + + logger.Print("hello") + output := buf.String() + if !strings.Contains(output, "TEST:") { + t.Errorf("Output missing prefix: %q", output) + } + if !strings.Contains(output, "hello") { + t.Errorf("Output missing message: %q", output) + } +} + +// Test Logger.Print, Printf, Println +func TestLoggerPrint(t *testing.T) { + var buf bytes.Buffer + logger := log.New(&buf, "", 0) + + // Test Print + logger.Print("test") + if !strings.Contains(buf.String(), "test") { + t.Errorf("Print: got %q", buf.String()) + } + + // Test Printf + buf.Reset() + logger.Printf("%s %d", "number", 42) + if !strings.Contains(buf.String(), "number 42") { + t.Errorf("Printf: got %q", buf.String()) + } + + // Test Println + buf.Reset() + logger.Println("line") + if !strings.Contains(buf.String(), "line") { + t.Errorf("Println: got %q", buf.String()) + } +} + +// Test Logger.Fatal (we can't actually call it as it exits) +func TestLoggerFatalExists(t *testing.T) { + var buf bytes.Buffer + logger := log.New(&buf, "", 0) + + // Just verify the methods exist + var _ func(...any) = logger.Fatal + var _ func(string, ...any) = logger.Fatalf + var _ func(...any) = logger.Fatalln +} + +// Test Logger.Panic +func TestLoggerPanic(t *testing.T) { + var buf bytes.Buffer + logger := log.New(&buf, "", 0) + + // Test Panic + defer func() { + if r := recover(); r == nil { + t.Error("Panic should have panicked") + } + if !strings.Contains(buf.String(), "panic test") { + t.Errorf("Panic output: got %q", buf.String()) + } + }() + logger.Panic("panic test") +} + +// Test Logger.Panicf +func TestLoggerPanicf(t *testing.T) { + var buf bytes.Buffer + logger := log.New(&buf, "", 0) + + defer func() { + if r := recover(); r == nil { + t.Error("Panicf should have panicked") + } + if !strings.Contains(buf.String(), "panic 42") { + t.Errorf("Panicf output: got %q", buf.String()) + } + }() + logger.Panicf("panic %d", 42) +} + +// Test Logger.Panicln +func TestLoggerPanicln(t *testing.T) { + var buf bytes.Buffer + logger := log.New(&buf, "", 0) + + defer func() { + if r := recover(); r == nil { + t.Error("Panicln should have panicked") + } + if !strings.Contains(buf.String(), "panicln") { + t.Errorf("Panicln output: got %q", buf.String()) + } + }() + logger.Panicln("panicln") +} + +// Test Logger.Prefix and SetPrefix +func TestLoggerPrefix(t *testing.T) { + var buf bytes.Buffer + logger := log.New(&buf, "OLD: ", 0) + + if logger.Prefix() != "OLD: " { + t.Errorf("Prefix() = %q, want %q", logger.Prefix(), "OLD: ") + } + + logger.SetPrefix("NEW: ") + if logger.Prefix() != "NEW: " { + t.Errorf("After SetPrefix, Prefix() = %q, want %q", logger.Prefix(), "NEW: ") + } + + logger.Print("test") + if !strings.Contains(buf.String(), "NEW:") { + t.Errorf("Output should contain new prefix: %q", buf.String()) + } +} + +// Test Logger.Flags and SetFlags +func TestLoggerFlags(t *testing.T) { + var buf bytes.Buffer + logger := log.New(&buf, "", log.Ldate) + + if logger.Flags() != log.Ldate { + t.Errorf("Flags() = %d, want %d", logger.Flags(), log.Ldate) + } + + logger.SetFlags(log.Ltime) + if logger.Flags() != log.Ltime { + t.Errorf("After SetFlags, Flags() = %d, want %d", logger.Flags(), log.Ltime) + } +} + +// Test Logger.Writer and SetOutput +func TestLoggerWriter(t *testing.T) { + var buf1 bytes.Buffer + logger := log.New(&buf1, "", 0) + + writer := logger.Writer() + if writer != &buf1 { + t.Error("Writer() should return original writer") + } + + var buf2 bytes.Buffer + logger.SetOutput(&buf2) + logger.Print("test") + + if buf1.Len() != 0 { + t.Error("Old buffer should be empty") + } + if !strings.Contains(buf2.String(), "test") { + t.Error("New buffer should contain output") + } +} + +// Test Logger.Output +func TestLoggerOutput(t *testing.T) { + var buf bytes.Buffer + logger := log.New(&buf, "", 0) + + err := logger.Output(2, "custom output") + if err != nil { + t.Errorf("Output error: %v", err) + } + if !strings.Contains(buf.String(), "custom output") { + t.Errorf("Output: got %q", buf.String()) + } +} + +// Test package-level Print functions +func TestPackagePrint(t *testing.T) { + var buf bytes.Buffer + log.SetOutput(&buf) + defer log.SetOutput(nil) // Reset after test + + // Test Print + log.Print("test") + if !strings.Contains(buf.String(), "test") { + t.Errorf("Print: got %q", buf.String()) + } + + // Test Printf + buf.Reset() + log.Printf("%s %d", "number", 42) + if !strings.Contains(buf.String(), "number 42") { + t.Errorf("Printf: got %q", buf.String()) + } + + // Test Println + buf.Reset() + log.Println("line") + if !strings.Contains(buf.String(), "line") { + t.Errorf("Println: got %q", buf.String()) + } +} + +// Test package-level Fatal functions exist +func TestPackageFatalExists(t *testing.T) { + // Just verify the functions exist + var _ func(...any) = log.Fatal + var _ func(string, ...any) = log.Fatalf + var _ func(...any) = log.Fatalln +} + +// Test package-level Panic functions +func TestPackagePanic(t *testing.T) { + var buf bytes.Buffer + log.SetOutput(&buf) + defer log.SetOutput(nil) + + defer func() { + if r := recover(); r == nil { + t.Error("Panic should have panicked") + } + if !strings.Contains(buf.String(), "panic") { + t.Errorf("Panic output: got %q", buf.String()) + } + }() + log.Panic("panic") +} + +// Test package-level Panicf +func TestPackagePanicf(t *testing.T) { + var buf bytes.Buffer + log.SetOutput(&buf) + defer log.SetOutput(nil) + + defer func() { + if r := recover(); r == nil { + t.Error("Panicf should have panicked") + } + if !strings.Contains(buf.String(), "panic 42") { + t.Errorf("Panicf output: got %q", buf.String()) + } + }() + log.Panicf("panic %d", 42) +} + +// Test package-level Panicln +func TestPackagePanicln(t *testing.T) { + var buf bytes.Buffer + log.SetOutput(&buf) + defer log.SetOutput(nil) + + defer func() { + if r := recover(); r == nil { + t.Error("Panicln should have panicked") + } + if !strings.Contains(buf.String(), "panicln") { + t.Errorf("Panicln output: got %q", buf.String()) + } + }() + log.Panicln("panicln") +} + +// Test package-level Prefix/SetPrefix +func TestPackagePrefix(t *testing.T) { + oldPrefix := log.Prefix() + defer log.SetPrefix(oldPrefix) // Restore after test + + log.SetPrefix("TEST: ") + if log.Prefix() != "TEST: " { + t.Errorf("Prefix() = %q, want %q", log.Prefix(), "TEST: ") + } +} + +// Test package-level Flags/SetFlags +func TestPackageFlags(t *testing.T) { + oldFlags := log.Flags() + defer log.SetFlags(oldFlags) // Restore after test + + log.SetFlags(log.Ldate) + if log.Flags() != log.Ldate { + t.Errorf("Flags() = %d, want %d", log.Flags(), log.Ldate) + } +} + +// Test package-level Writer +func TestPackageWriter(t *testing.T) { + // Set output first to ensure writer is not nil + var buf bytes.Buffer + log.SetOutput(&buf) + defer log.SetOutput(nil) + + writer := log.Writer() + if writer == nil { + t.Error("Writer() returned nil") + } + if writer != &buf { + t.Error("Writer() should return the set output writer") + } +} + +// Test package-level Output +func TestPackageOutput(t *testing.T) { + var buf bytes.Buffer + log.SetOutput(&buf) + defer log.SetOutput(nil) + + err := log.Output(2, "custom") + if err != nil { + t.Errorf("Output error: %v", err) + } + if !strings.Contains(buf.String(), "custom") { + t.Errorf("Output: got %q", buf.String()) + } +} + +// Test log.Default +func TestDefault(t *testing.T) { + defaultLogger := log.Default() + if defaultLogger == nil { + t.Fatal("Default() returned nil") + } + + // Verify it's the same logger used by package functions + var buf bytes.Buffer + log.SetOutput(&buf) + defer log.SetOutput(nil) + + defaultLogger.Print("test") + if !strings.Contains(buf.String(), "test") { + t.Error("Default logger should use same output as package functions") + } +} + +// Test different flag combinations +func TestFlagCombinations(t *testing.T) { + var buf bytes.Buffer + + tests := []struct { + name string + flags int + }{ + {"Ldate", log.Ldate}, + {"Ltime", log.Ltime}, + {"Lmicroseconds", log.Ltime | log.Lmicroseconds}, + {"Lshortfile", log.Lshortfile}, + {"Llongfile", log.Llongfile}, + {"LUTC", log.Ldate | log.Ltime | log.LUTC}, + {"Lmsgprefix", log.Lmsgprefix}, + {"LstdFlags", log.LstdFlags}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + buf.Reset() + logger := log.New(&buf, "PREFIX: ", tt.flags) + logger.Print("message") + output := buf.String() + if !strings.Contains(output, "message") { + t.Errorf("Output missing message: %q", output) + } + }) + } +} + +// Test prefix with Lmsgprefix +func TestMsgPrefix(t *testing.T) { + var buf bytes.Buffer + + // Without Lmsgprefix - prefix at beginning + logger := log.New(&buf, "PREFIX: ", 0) + logger.Print("message") + output := buf.String() + if !strings.HasPrefix(output, "PREFIX:") { + t.Errorf("Without Lmsgprefix, prefix should be at start: %q", output) + } + + // With Lmsgprefix - prefix before message + buf.Reset() + logger = log.New(&buf, "PREFIX: ", log.Lmsgprefix) + logger.Print("message") + output = buf.String() + // Should have prefix before message but after other fields + if !strings.Contains(output, "PREFIX:") || !strings.Contains(output, "message") { + t.Errorf("With Lmsgprefix: %q", output) + } +} + +// Test concurrent logging (Logger is safe for concurrent use) +func TestConcurrentLogging(t *testing.T) { + var buf bytes.Buffer + logger := log.New(&buf, "", 0) + + done := make(chan bool) + for i := 0; i < 10; i++ { + go func(n int) { + logger.Printf("goroutine %d", n) + done <- true + }(i) + } + + for i := 0; i < 10; i++ { + <-done + } + + output := buf.String() + // Should have 10 log lines + lines := strings.Split(strings.TrimSpace(output), "\n") + if len(lines) != 10 { + t.Errorf("Expected 10 lines, got %d", len(lines)) + } +} + +// Test that output ends with newline +func TestOutputNewline(t *testing.T) { + var buf bytes.Buffer + logger := log.New(&buf, "", 0) + + // Message without newline + logger.Print("test") + output := buf.String() + if !strings.HasSuffix(output, "\n") { + t.Error("Output should end with newline") + } + + // Message with newline + buf.Reset() + logger.Print("test\n") + output = buf.String() + if !strings.HasSuffix(output, "\n") { + t.Error("Output should end with newline") + } + // Should not add extra newline + if strings.HasSuffix(output, "\n\n") { + t.Error("Output should not have double newline") + } +} + +// Test empty prefix +func TestEmptyPrefix(t *testing.T) { + var buf bytes.Buffer + logger := log.New(&buf, "", 0) + + logger.Print("test") + output := buf.String() + if output != "test\n" { + t.Errorf("With empty prefix, got %q, want %q", output, "test\n") + } +} + +// Test Logger type +func TestLoggerType(t *testing.T) { + var buf bytes.Buffer + var logger *log.Logger + logger = log.New(&buf, "", 0) + + if logger == nil { + t.Fatal("Logger should not be nil") + } +} diff --git a/test/std/log/slog/go126_symbols_test.go b/test/std/log/slog/go126_symbols_test.go new file mode 100644 index 0000000000..abfdbdadbd --- /dev/null +++ b/test/std/log/slog/go126_symbols_test.go @@ -0,0 +1,79 @@ +//go:build go1.26 + +package slog_test + +import ( + "bytes" + "context" + "encoding/json" + "log/slog" + "runtime" + "strings" + "testing" + "time" +) + +func TestMultiHandlerAndRecordSource(t *testing.T) { + var first, second bytes.Buffer + multi := slog.NewMultiHandler( + slog.NewTextHandler(&first, nil), + slog.NewJSONHandler(&second, nil), + ) + ctx := context.Background() + if !multi.Enabled(ctx, slog.LevelInfo) { + t.Fatal("MultiHandler unexpectedly disabled info records") + } + + direct := slog.NewRecord(time.Unix(1, 0), slog.LevelInfo, "direct", 0) + if err := multi.Handle(ctx, direct); err != nil { + t.Fatal(err) + } + withAttrs := multi.WithAttrs([]slog.Attr{slog.String("component", "compiler")}) + if err := withAttrs.Handle(ctx, slog.NewRecord(time.Unix(1, 0), slog.LevelInfo, "compiled", 0)); err != nil { + t.Fatal(err) + } + withGroup := multi.WithGroup("details") + record := slog.NewRecord(time.Unix(1, 0), slog.LevelInfo, "grouped", 0) + record.AddAttrs(slog.GroupAttrs("build", slog.Int("files", 2))) + if err := withGroup.Handle(ctx, record); err != nil { + t.Fatal(err) + } + textOutput := first.String() + for _, want := range []string{"msg=direct", "msg=compiled component=compiler", "msg=grouped details.build.files=2"} { + if !strings.Contains(textOutput, want) { + t.Fatalf("text handler output %q does not contain %q", textOutput, want) + } + } + jsonLines := bytes.Split(bytes.TrimSpace(second.Bytes()), []byte("\n")) + if len(jsonLines) != 3 { + t.Fatalf("JSON handler wrote %d records, want 3: %q", len(jsonLines), second.String()) + } + records := make([]map[string]any, len(jsonLines)) + for i, line := range jsonLines { + if err := json.Unmarshal(line, &records[i]); err != nil { + t.Fatalf("JSON record %d is invalid: %v: %q", i, err, line) + } + } + if records[0]["msg"] != "direct" { + t.Fatalf("first JSON record = %#v", records[0]) + } + if records[1]["msg"] != "compiled" || records[1]["component"] != "compiler" { + t.Fatalf("second JSON record = %#v", records[1]) + } + details, ok := records[2]["details"].(map[string]any) + if !ok { + t.Fatalf("third JSON record has no details group: %#v", records[2]) + } + build, ok := details["build"].(map[string]any) + if !ok || build["files"] != float64(2) { + t.Fatalf("third JSON record has wrong build group: %#v", records[2]) + } + + pcs := make([]uintptr, 1) + runtime.Callers(1, pcs) + sourceRecord := slog.NewRecord(time.Time{}, slog.LevelInfo, "source", pcs[0]) + source := sourceRecord.Source() + if source == nil || !strings.Contains(source.Function, "TestMultiHandlerAndRecordSource") || source.Line == 0 { + t.Fatalf("Record.Source = %#v", source) + } +} diff --git a/test/std/log/slog/slog_test.go b/test/std/log/slog/slog_test.go new file mode 100644 index 0000000000..aec2ce8274 --- /dev/null +++ b/test/std/log/slog/slog_test.go @@ -0,0 +1,345 @@ +package slog_test + +import ( + "bytes" + "context" + "encoding/json" + "log" + "log/slog" + "strings" + "testing" + "time" +) + +type secretValuer struct{} + +func (secretValuer) LogValue() slog.Value { + return slog.StringValue("redacted") +} + +func TestTextAndJSONHandlers(t *testing.T) { + ctx := context.Background() + + var textBuf bytes.Buffer + th := slog.NewTextHandler(&textBuf, &slog.HandlerOptions{ + Level: slog.LevelDebug, + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + if len(groups) == 0 && a.Key == slog.MessageKey { + return slog.String("message", a.Value.String()) + } + return a + }, + }) + if !th.Enabled(ctx, slog.LevelInfo) { + t.Fatal("TextHandler should enable info") + } + + r := slog.NewRecord(time.Now(), slog.LevelInfo, "hello", 0) + r.AddAttrs(slog.String("k", "v")) + if err := th.Handle(ctx, r); err != nil { + t.Fatalf("TextHandler.Handle: %v", err) + } + if !strings.Contains(textBuf.String(), "message=hello") { + t.Fatalf("Text output missing replaced message key: %q", textBuf.String()) + } + + twh := th.WithAttrs([]slog.Attr{slog.String("svc", "api")}).WithGroup("req") + lg := slog.New(twh) + lg.Info("call", "id", 7) + twg := th.WithGroup("direct") + slog.New(twg).Info("direct-call", "k", 1) + out := textBuf.String() + if !strings.Contains(out, "svc=api") || !strings.Contains(out, "req.id=7") { + t.Fatalf("Text output missing grouped attrs: %q", out) + } + if !strings.Contains(out, "direct.k=1") { + t.Fatalf("Text output missing direct WithGroup field: %q", out) + } + if !strings.Contains(out, slog.LevelKey) || !strings.Contains(out, slog.TimeKey) { + t.Fatalf("Text output missing standard keys: %q", out) + } + if slog.SourceKey != "source" { + t.Fatalf("unexpected SourceKey: %q", slog.SourceKey) + } + + var jsonBuf bytes.Buffer + jh := slog.NewJSONHandler(&jsonBuf, &slog.HandlerOptions{Level: slog.LevelDebug}) + if !jh.Enabled(ctx, slog.LevelInfo) { + t.Fatal("JSONHandler should enable info") + } + r2 := slog.NewRecord(time.Now(), slog.LevelWarn, "json-msg", 0) + r2.AddAttrs(slog.Int("n", 3)) + if err := jh.Handle(ctx, r2); err != nil { + t.Fatalf("JSONHandler.Handle: %v", err) + } + if !strings.Contains(jsonBuf.String(), `"msg":"json-msg"`) { + t.Fatalf("JSON output missing msg: %q", jsonBuf.String()) + } + if !strings.Contains(jsonBuf.String(), `"n":3`) { + t.Fatalf("JSON output missing attr: %q", jsonBuf.String()) + } + jhWithAttrs := jh.WithAttrs([]slog.Attr{slog.String("a", "b")}) + if jhWithAttrs == nil { + t.Fatal("JSONHandler.WithAttrs returned nil") + } + jhWithGroup := jh.WithGroup("g") + if jhWithGroup == nil { + t.Fatal("JSONHandler.WithGroup returned nil") + } +} + +func TestLoggerTopLevelAndMethods(t *testing.T) { + ctx := context.Background() + oldDefault := slog.Default() + defer slog.SetDefault(oldDefault) + + var buf bytes.Buffer + logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug})) + slog.SetDefault(logger) + + if slog.Default() == nil { + t.Fatal("Default logger is nil") + } + if !logger.Enabled(ctx, slog.LevelInfo) { + t.Fatal("logger should enable info") + } + if logger.Handler() == nil { + t.Fatal("logger handler is nil") + } + + logger.Debug("d1", "a", 1) + logger.DebugContext(ctx, "d2", "a", 2) + logger.Info("i1", "a", 3) + logger.InfoContext(ctx, "i2", "a", 4) + logger.Warn("w1", "a", 5) + logger.WarnContext(ctx, "w2", "a", 6) + logger.Error("e1", "a", 7) + logger.ErrorContext(ctx, "e2", "a", 8) + logger.Log(ctx, slog.LevelInfo, "l1", "a", 9) + logger.LogAttrs(ctx, slog.LevelInfo, "l2", slog.Int("a", 10)) + + slog.Debug("td1", "x", 1) + slog.DebugContext(ctx, "td2", "x", 2) + slog.Info("ti1", "x", 3) + slog.InfoContext(ctx, "ti2", "x", 4) + slog.Warn("tw1", "x", 5) + slog.WarnContext(ctx, "tw2", "x", 6) + slog.Error("te1", "x", 7) + slog.ErrorContext(ctx, "te2", "x", 8) + slog.Log(ctx, slog.LevelInfo, "tl1", "x", 9) + slog.LogAttrs(ctx, slog.LevelInfo, "tl2", slog.Int("x", 10)) + + slog.With("scope", "pkg").Info("with-info", "k", "v") + logger.With("scope", "logger").WithGroup("g").Info("with-group", "k", "v") + + out := buf.String() + for _, msg := range []string{"d1", "i1", "w1", "e1", "tl2", "with-group"} { + if !strings.Contains(out, "msg="+msg) { + t.Fatalf("missing log message %q in output: %q", msg, out) + } + } + if !strings.Contains(out, "g.k=v") { + t.Fatalf("missing grouped logger field in output: %q", out) + } +} + +func TestLevelAndLevelVar(t *testing.T) { + if got := slog.LevelDebug.String(); got == "" { + t.Fatal("LevelDebug.String empty") + } + if got := slog.LevelWarn.Level(); got != slog.LevelWarn { + t.Fatalf("Level().got=%v want=%v", got, slog.LevelWarn) + } + + var l slog.Level + if err := l.UnmarshalText([]byte("ERROR")); err != nil { + t.Fatalf("Level.UnmarshalText: %v", err) + } + if l != slog.LevelError { + t.Fatalf("UnmarshalText level = %v, want %v", l, slog.LevelError) + } + text, err := l.MarshalText() + if err != nil || !strings.Contains(string(text), "ERROR") { + t.Fatalf("Level.MarshalText = %q, %v", text, err) + } + appended, err := l.AppendText([]byte("L=")) + if err != nil || !strings.Contains(string(appended), "ERROR") { + t.Fatalf("Level.AppendText = %q, %v", appended, err) + } + j, err := l.MarshalJSON() + if err != nil { + t.Fatalf("Level.MarshalJSON: %v", err) + } + var l2 slog.Level + if err := l2.UnmarshalJSON(j); err != nil { + t.Fatalf("Level.UnmarshalJSON: %v", err) + } + if l2 != l { + t.Fatalf("Level roundtrip mismatch: %v != %v", l2, l) + } + + var lv slog.LevelVar + if lv.Level() != slog.LevelInfo { + t.Fatalf("LevelVar default = %v, want %v", lv.Level(), slog.LevelInfo) + } + lv.Set(slog.LevelDebug) + if lv.Level() != slog.LevelDebug { + t.Fatalf("LevelVar.Set failed: %v", lv.Level()) + } + if _, err := lv.AppendText(nil); err != nil { + t.Fatalf("LevelVar.AppendText: %v", err) + } + if _, err := lv.MarshalText(); err != nil { + t.Fatalf("LevelVar.MarshalText: %v", err) + } + if err := lv.UnmarshalText([]byte("WARN")); err != nil { + t.Fatalf("LevelVar.UnmarshalText: %v", err) + } + if lv.String() == "" { + t.Fatal("LevelVar.String empty") + } + + old := slog.SetLogLoggerLevel(slog.LevelInfo) + prev := slog.SetLogLoggerLevel(old) + if prev != slog.LevelInfo { + t.Fatalf("SetLogLoggerLevel restore returned %v, want %v", prev, slog.LevelInfo) + } +} + +func TestRecordAttrAndValue(t *testing.T) { + ctx := context.Background() + now := time.Now().Round(0) + + r := slog.NewRecord(now, slog.LevelInfo, "msg", 0) + r.Add("k", "v") + r.AddAttrs( + slog.Any("any", map[string]int{"a": 1}), + slog.Bool("b", true), + slog.Duration("dur", time.Second), + slog.Float64("f", 1.5), + slog.Group("grp", "x", 1), + slog.Int("i", 2), + slog.Int64("i64", 3), + slog.String("s", "x"), + slog.Time("t", now), + slog.Uint64("u", 4), + ) + if r.NumAttrs() < 10 { + t.Fatalf("NumAttrs too small: %d", r.NumAttrs()) + } + + clone := r.Clone() + clone.Add("extra", 1) + if clone.NumAttrs() <= r.NumAttrs() { + t.Fatalf("Clone/Add should not affect original: clone=%d, orig=%d", clone.NumAttrs(), r.NumAttrs()) + } + + seen := map[string]bool{} + r.Attrs(func(a slog.Attr) bool { + seen[a.Key] = true + if a.String() == "" { + t.Fatalf("Attr.String empty for key %q", a.Key) + } + return true + }) + if !seen["k"] || !seen["i"] { + t.Fatalf("Attrs iteration missing keys: %#v", seen) + } + + a1 := slog.Int("n", 1) + a2 := slog.Int("n", 1) + if !a1.Equal(a2) { + t.Fatalf("Attr.Equal false for equal attrs: %v vs %v", a1, a2) + } + + vals := []slog.Value{ + slog.AnyValue("x"), + slog.BoolValue(true), + slog.DurationValue(time.Second), + slog.Float64Value(1.25), + slog.GroupValue(slog.String("k", "v")), + slog.Int64Value(7), + slog.IntValue(8), + slog.StringValue("s"), + slog.TimeValue(now), + slog.Uint64Value(9), + slog.AnyValue(secretValuer{}), + } + for _, v := range vals { + if v.Kind().String() == "" { + t.Fatalf("Kind.String empty for value: %v", v) + } + if v.String() == "" { + t.Fatalf("Value.String empty for kind: %v", v.Kind()) + } + if v.Any() == nil { + t.Fatalf("Value.Any returned nil for kind: %v", v.Kind()) + } + } + if !slog.BoolValue(true).Bool() { + t.Fatal("BoolValue.Bool false") + } + if slog.DurationValue(time.Second).Duration() != time.Second { + t.Fatal("DurationValue.Duration mismatch") + } + if slog.Float64Value(1.25).Float64() != 1.25 { + t.Fatal("Float64Value.Float64 mismatch") + } + if slog.Int64Value(7).Int64() != 7 { + t.Fatal("Int64Value.Int64 mismatch") + } + if slog.Uint64Value(9).Uint64() != 9 { + t.Fatal("Uint64Value.Uint64 mismatch") + } + if !slog.TimeValue(now).Time().Equal(now) { + t.Fatal("TimeValue.Time mismatch") + } + if len(slog.GroupValue(slog.Int("x", 1)).Group()) != 1 { + t.Fatal("GroupValue.Group size mismatch") + } + if !slog.StringValue("s").Equal(slog.StringValue("s")) { + t.Fatal("Value.Equal false for equal values") + } + + lv := slog.AnyValue(secretValuer{}) + if lv.LogValuer() == nil { + t.Fatal("Value.LogValuer returned nil") + } + if resolved := lv.Resolve(); resolved.String() != "redacted" { + t.Fatalf("Value.Resolve mismatch: %q", resolved.String()) + } + + var out bytes.Buffer + lh := slog.NewTextHandler(&out, &slog.HandlerOptions{Level: slog.LevelDebug}) + slog.New(lh).Log(ctx, slog.LevelInfo, "vtest", slog.Any("secret", secretValuer{})) + if !strings.Contains(out.String(), "redacted") { + t.Fatalf("LogValuer output missing resolved value: %q", out.String()) + } + + src := slog.Source{Function: "f", File: "f.go", Line: 12} + b, err := json.Marshal(src) + if err != nil { + t.Fatalf("json.Marshal(Source): %v", err) + } + if !strings.Contains(string(b), `"line":12`) { + t.Fatalf("Source JSON mismatch: %s", string(b)) + } +} + +func TestNewLogLoggerAndInterfaces(t *testing.T) { + var _ slog.Handler = slog.NewTextHandler(&bytes.Buffer{}, nil) + var _ slog.Leveler = &slog.LevelVar{} + var _ slog.LogValuer = secretValuer{} + + var buf bytes.Buffer + std := slog.NewLogLogger(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo}), slog.LevelInfo) + std.Print("std-log") + if !strings.Contains(buf.String(), "std-log") { + t.Fatalf("NewLogLogger output mismatch: %q", buf.String()) + } + + // Keep package log reachable through slog redirection path. + if log.Flags() < 0 { + t.Fatalf("log.Flags should be non-negative, got %d", log.Flags()) + } +} diff --git a/test/std/log/syslog/syslog_test.go b/test/std/log/syslog/syslog_test.go new file mode 100644 index 0000000000..389a738341 --- /dev/null +++ b/test/std/log/syslog/syslog_test.go @@ -0,0 +1,134 @@ +package syslog_test + +import ( + "log/syslog" + "net" + "testing" + "time" +) + +func TestPriorityConstants(t *testing.T) { + severities := []syslog.Priority{ + syslog.LOG_EMERG, + syslog.LOG_ALERT, + syslog.LOG_CRIT, + syslog.LOG_ERR, + syslog.LOG_WARNING, + syslog.LOG_NOTICE, + syslog.LOG_INFO, + syslog.LOG_DEBUG, + } + for i, p := range severities { + if int(p) != i { + t.Fatalf("severity %d = %d, want %d", i, p, i) + } + } + + facilities := []syslog.Priority{ + syslog.LOG_KERN, + syslog.LOG_USER, + syslog.LOG_MAIL, + syslog.LOG_DAEMON, + syslog.LOG_AUTH, + syslog.LOG_SYSLOG, + syslog.LOG_LPR, + syslog.LOG_NEWS, + syslog.LOG_UUCP, + syslog.LOG_CRON, + syslog.LOG_AUTHPRIV, + syslog.LOG_FTP, + syslog.LOG_LOCAL0, + syslog.LOG_LOCAL1, + syslog.LOG_LOCAL2, + syslog.LOG_LOCAL3, + syslog.LOG_LOCAL4, + syslog.LOG_LOCAL5, + syslog.LOG_LOCAL6, + syslog.LOG_LOCAL7, + } + for i, p := range facilities { + if i > 0 && p <= facilities[i-1] { + t.Fatalf("facility constants must be increasing: %d <= %d", p, facilities[i-1]) + } + } + + pri := syslog.LOG_LOCAL0 | syslog.LOG_INFO + if pri == 0 { + t.Fatal("combined priority should not be zero") + } +} + +func TestPublicAPISymbols(t *testing.T) { + _ = syslog.NewLogger + _ = syslog.Dial + _ = syslog.New +} + +func TestWriterMethodsOverUDP(t *testing.T) { + pc, err := net.ListenPacket("udp", "127.0.0.1:0") + if err != nil { + t.Fatalf("ListenPacket: %v", err) + } + defer pc.Close() + + const wantMessages = 9 + recvDone := make(chan int, 1) + go func() { + if err := pc.SetReadDeadline(time.Now().Add(2 * time.Second)); err != nil { + recvDone <- 0 + return + } + buf := make([]byte, 2048) + count := 0 + for count < wantMessages { + n, _, err := pc.ReadFrom(buf) + if err != nil { + break + } + if n > 0 { + count++ + } + } + recvDone <- count + }() + + w, err := syslog.Dial("udp", pc.LocalAddr().String(), syslog.LOG_INFO|syslog.LOG_LOCAL0, "llgo-test") + if err != nil { + t.Fatalf("Dial: %v", err) + } + if err := w.Alert("alert"); err != nil { + t.Fatalf("Alert: %v", err) + } + if err := w.Crit("crit"); err != nil { + t.Fatalf("Crit: %v", err) + } + if err := w.Debug("debug"); err != nil { + t.Fatalf("Debug: %v", err) + } + if err := w.Emerg("emerg"); err != nil { + t.Fatalf("Emerg: %v", err) + } + if err := w.Err("err"); err != nil { + t.Fatalf("Err: %v", err) + } + if err := w.Info("info"); err != nil { + t.Fatalf("Info: %v", err) + } + if err := w.Notice("notice"); err != nil { + t.Fatalf("Notice: %v", err) + } + if err := w.Warning("warning"); err != nil { + t.Fatalf("Warning: %v", err) + } + if _, err := w.Write([]byte("write")); err != nil { + t.Fatalf("Write: %v", err) + } + if err := w.Close(); err != nil { + t.Fatalf("Close: %v", err) + } + + got := <-recvDone + if got < wantMessages { + t.Fatalf("received %d syslog datagrams, want at least %d", got, wantMessages) + } +} diff --git a/test/std/maps/maps_bench_test.go b/test/std/maps/maps_bench_test.go new file mode 100644 index 0000000000..d7f2bed5fe --- /dev/null +++ b/test/std/maps/maps_bench_test.go @@ -0,0 +1,65 @@ +package maps_test + +import ( + "iter" + "maps" + "testing" +) + +func BenchmarkClone(b *testing.B) { + base := make(map[int]int, 256) + for i := 0; i < 256; i++ { + base[i] = i + 1 + } + + b.ResetTimer() + var sum int + for i := 0; i < b.N; i++ { + clone := maps.Clone(base) + sum += len(clone) + } + if sum == 0 { + b.Fatalf("unexpected zero sum") + } +} + +func BenchmarkEqual(b *testing.B) { + a := make(map[int]int, 128) + bMap := make(map[int]int, 128) + for i := 0; i < 128; i++ { + a[i] = i * 2 + bMap[i] = i * 2 + } + + b.ResetTimer() + var last bool + for i := 0; i < b.N; i++ { + last = maps.Equal(a, bMap) + } + if !last { + b.Fatalf("maps.Equal reported inequality") + } +} + +func BenchmarkInsert(b *testing.B) { + seq := iter.Seq2[int, int](func(yield func(int, int) bool) { + for i := 0; i < 64; i++ { + if !yield(i, i*i) { + return + } + } + }) + + b.ResetTimer() + var total int + for i := 0; i < b.N; i++ { + m := make(map[int]int, 64) + maps.Insert(m, seq) + for _, v := range m { + total += v + } + } + if total == 0 { + b.Fatalf("total should be non-zero") + } +} diff --git a/test/std/maps/maps_test.go b/test/std/maps/maps_test.go new file mode 100644 index 0000000000..0787b81296 --- /dev/null +++ b/test/std/maps/maps_test.go @@ -0,0 +1,182 @@ +package maps_test + +import ( + "iter" + "maps" + "slices" + "strings" + "testing" +) + +func collectSeq[T any](seq iter.Seq[T]) []T { + var out []T + for v := range seq { + out = append(out, v) + } + return out +} + +func collectSeq2[K comparable, V any](seq iter.Seq2[K, V]) map[K]V { + result := make(map[K]V) + for k, v := range seq { + result[k] = v + } + return result +} + +func equalMap[K comparable, V comparable](a, b map[K]V) bool { + if len(a) != len(b) { + return false + } + for k, v := range a { + if bv, ok := b[k]; !ok || bv != v { + return false + } + } + return true +} + +func TestCloneProducesIndependentCopy(t *testing.T) { + src := map[string]int{"go": 1, "plus": 2} + cloned := maps.Clone(src) + if !equalMap(src, cloned) { + t.Fatalf("Clone mismatch: %v vs %v", src, cloned) + } + cloned["go"] = 42 + if src["go"] == 42 { + t.Fatal("Clone should not share backing map") + } + + var nilMap map[int]int + if cloneNil := maps.Clone(nilMap); cloneNil != nil { + t.Fatalf("Clone of nil should be nil, got %v", cloneNil) + } +} + +func TestCopyMergesValues(t *testing.T) { + dst := map[string]int{"alpha": 1} + src := map[string]int{"beta": 2, "alpha": 3} + maps.Copy(dst, src) + if want := map[string]int{"alpha": 3, "beta": 2}; !equalMap(dst, want) { + t.Fatalf("Copy result = %v", dst) + } +} + +func TestCopyPanicsOnNilDestination(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Fatal("maps.Copy should panic when writing to nil map") + } + }() + var dst map[string]int + maps.Copy(dst, map[string]int{"x": 1}) +} + +func TestDeleteFuncRemovesMatchingEntries(t *testing.T) { + m := map[string]int{"even": 2, "odd": 3, "zero": 0} + maps.DeleteFunc(m, func(_ string, v int) bool { return v%2 == 0 }) + if want := map[string]int{"odd": 3}; !equalMap(m, want) { + t.Fatalf("DeleteFunc result = %v", m) + } + + maps.DeleteFunc(m, func(_ string, v int) bool { return v > 100 }) + if want := map[string]int{"odd": 3}; !equalMap(m, want) { + t.Fatalf("DeleteFunc should keep map unchanged, got %v", m) + } +} + +func TestEqualAndEqualFunc(t *testing.T) { + a := map[string]int{"go": 1, "plus": 2} + b := map[string]int{"plus": 2, "go": 1} + c := map[string]int{"go": 1, "plus": 3} + + if !maps.Equal(a, b) { + t.Fatal("Equal should report maps as identical") + } + if maps.Equal(a, c) { + t.Fatal("Equal should detect differing values") + } + + m1 := map[string]string{"go": "Go", "plus": "Plus"} + m2 := map[string]string{"go": "go", "plus": "plus"} + if !maps.EqualFunc(m1, m2, func(x, y string) bool { return strings.EqualFold(x, y) }) { + t.Fatal("EqualFunc should use custom comparer") + } + if maps.EqualFunc(m1, map[string]string{"go": "Go", "plus": "Plus", "extra": "value"}, func(x, y string) bool { return x == y }) { + t.Fatal("EqualFunc should fail with mismatched keys") + } +} + +func TestKeysAndValues(t *testing.T) { + data := map[string]int{"go": 1, "plus": 2, "rocks": 3} + keys := collectSeq(maps.Keys(data)) + vals := collectSeq(maps.Values(data)) + slices.Sort(keys) + slices.Sort(vals) + if !slices.Equal(keys, []string{"go", "plus", "rocks"}) { + t.Fatalf("Keys mismatch: %v", keys) + } + if !slices.Equal(vals, []int{1, 2, 3}) { + t.Fatalf("Values mismatch: %v", vals) + } + + var nilMap map[string]int + if keys := collectSeq(maps.Keys(nilMap)); len(keys) != 0 { + t.Fatalf("Keys(nil) should be empty, got %v", keys) + } + if vals := collectSeq(maps.Values(nilMap)); len(vals) != 0 { + t.Fatalf("Values(nil) should be empty, got %v", vals) + } +} + +func TestAllAndCollect(t *testing.T) { + input := map[string]int{"x": 10, "y": 20} + pairs := collectSeq2(maps.All(input)) + if !equalMap(pairs, input) { + t.Fatalf("All sequence mismatch: %v", pairs) + } + + seq := iter.Seq2[string, int](func(yield func(string, int) bool) { + entries := []struct { + k string + v int + }{ + {"alpha", 1}, + {"beta", 2}, + {"alpha", 3}, + } + for _, e := range entries { + if !yield(e.k, e.v) { + return + } + } + }) + + m := maps.Collect(seq) + if want := map[string]int{"alpha": 3, "beta": 2}; !equalMap(m, want) { + t.Fatalf("Collect result = %v", m) + } +} + +func TestInsertPopulatesMapFromSequence(t *testing.T) { + m := map[string]int{"existing": 1} + seq := iter.Seq2[string, int](func(yield func(string, int) bool) { + pairs := []struct { + k string + v int + }{ + {"new", 2}, + {"existing", 3}, + } + for _, p := range pairs { + if !yield(p.k, p.v) { + return + } + } + }) + + maps.Insert(m, seq) + if want := map[string]int{"existing": 3, "new": 2}; !equalMap(m, want) { + t.Fatalf("Insert result = %v", m) + } +} diff --git a/test/std/math/big/big_test.go b/test/std/math/big/big_test.go new file mode 100644 index 0000000000..cc8fc325e8 --- /dev/null +++ b/test/std/math/big/big_test.go @@ -0,0 +1,833 @@ +package big_test + +import ( + "encoding/json" + "fmt" + "math" + "math/big" + "math/rand" + "strings" + "testing" + "unicode" +) + +func mustInt(t *testing.T, literal string) *big.Int { + t.Helper() + v, ok := new(big.Int).SetString(literal, 0) + if !ok { + t.Fatalf("failed to parse %q as *big.Int", literal) + } + return v +} + +func mustRat(t *testing.T, literal string) *big.Rat { + t.Helper() + v, ok := new(big.Rat).SetString(literal) + if !ok { + t.Fatalf("failed to parse %q as *big.Rat", literal) + } + return v +} + +func mustFloat(t *testing.T, literal string) *big.Float { + t.Helper() + f, _, err := big.ParseFloat(literal, 0, 256, big.ToNearestEven) + if err != nil { + t.Fatalf("failed to parse float %q: %v", literal, err) + } + return f +} + +type formatBuffer struct { + strings.Builder +} + +func (f *formatBuffer) Write(b []byte) (int, error) { return f.Builder.Write(b) } +func (f *formatBuffer) Width() (int, bool) { return 0, false } +func (f *formatBuffer) Precision() (int, bool) { return 0, false } +func (f *formatBuffer) Flag(c int) bool { return false } + +type scanState struct { + *strings.Reader +} + +func (s *scanState) ReadRune() (rune, int, error) { return s.Reader.ReadRune() } +func (s *scanState) UnreadRune() error { return s.Reader.UnreadRune() } +func (s *scanState) SkipSpace() { + for { + r, _, err := s.ReadRune() + if err != nil { + return + } + if !unicode.IsSpace(r) { + s.UnreadRune() + return + } + } +} + +func (s *scanState) Token(skipSpace bool, f func(rune) bool) ([]byte, error) { + if skipSpace { + s.SkipSpace() + } + var buf []rune + for { + r, _, err := s.ReadRune() + if err != nil { + if len(buf) == 0 { + return nil, err + } + break + } + if !f(r) { + s.UnreadRune() + break + } + buf = append(buf, r) + } + if len(buf) == 0 { + return nil, fmt.Errorf("empty token") + } + return []byte(string(buf)), nil +} + +func (s *scanState) Width() (int, bool) { return 0, false } +func (s *scanState) Precision() (int, bool) { return 0, false } +func (s *scanState) Flag(int) bool { return false } + +func expectInt(t *testing.T, got *big.Int, want string) { + t.Helper() + if cmp := got.Cmp(mustInt(t, want)); cmp != 0 { + t.Fatalf("integer mismatch: got %s, want %s", got.String(), want) + } +} + +func expectRat(t *testing.T, got *big.Rat, want string) { + t.Helper() + if cmp := got.Cmp(mustRat(t, want)); cmp != 0 { + t.Fatalf("rational mismatch: got %s, want %s", got.RatString(), want) + } +} + +func TestIntArithmetic(t *testing.T) { + a := mustInt(t, "12345678901234567890") + b := mustInt(t, "-9876543210987654321") + + sum := new(big.Int).Add(new(big.Int).Set(a), b) + expectInt(t, sum, "2469135690246913569") + + diff := new(big.Int).Sub(new(big.Int).Set(a), b) + expectInt(t, diff, "22222222112222222211") + + product := new(big.Int).Mul(new(big.Int).Set(a), b) + expectInt(t, product, "-121932631137021795223746380111126352690") + + neg := new(big.Int).Neg(new(big.Int).Set(b)) + expectInt(t, neg, "9876543210987654321") + + abs := new(big.Int).Abs(new(big.Int).Set(b)) + expectInt(t, abs, "9876543210987654321") +} + +func TestIntQuoRemAndMod(t *testing.T) { + dividend := mustInt(t, "-12345678901234567890") + divisor := big.NewInt(97) + + quotient := new(big.Int) + remainder := new(big.Int) + quotient.QuoRem(dividend, divisor, remainder) + + expectInt(t, quotient, "-127275040218913071") + expectInt(t, remainder, "-3") + + lhs := new(big.Int).Mul(quotient, divisor) + lhs.Add(lhs, remainder) + if lhs.Cmp(dividend) != 0 { + t.Fatalf("quotient/remainder identity failed: got %s, want %s", lhs, dividend) + } + + modulus := new(big.Int).Mod(dividend, divisor) + if modulus.Sign() < 0 || modulus.Cmp(divisor) >= 0 { + t.Fatalf("Mod should produce non-negative remainder < divisor: %s", modulus) + } + + expectedMod := new(big.Int).Add(remainder, divisor) + if modulus.Cmp(expectedMod) != 0 { + t.Fatalf("Mod result mismatch: got %s, want %s", modulus, expectedMod) + } +} + +func TestIntParsingAndBinomial(t *testing.T) { + cases := []struct { + literal string + want string + }{ + {"0xff", "255"}, + {"0b101010", "42"}, + {"0o755", "493"}, + {"-0X2A", "-42"}, + {"12345678", "12345678"}, + } + for _, tc := range cases { + got := mustInt(t, tc.literal) + if got.Cmp(mustInt(t, tc.want)) != 0 { + t.Fatalf("SetString mismatch for %q: got %s, want %s", tc.literal, got, tc.want) + } + } + + base36 := new(big.Int) + if _, ok := base36.SetString("zzzz", 36); !ok { + t.Fatalf("SetString failed for base36 literal") + } + expectInt(t, base36, "1679615") + + gcdA := mustInt(t, "1989") + gcdB := mustInt(t, "867") + x := new(big.Int) + y := new(big.Int) + gcd := new(big.Int).GCD(x, y, gcdA, gcdB) + expectInt(t, gcd, "51") + + left := new(big.Int).Mul(x, gcdA) + right := new(big.Int).Mul(y, gcdB) + left.Add(left, right) + if left.Cmp(gcd) != 0 { + t.Fatalf("Bézout identity failed: %s != %s", left, gcd) + } + + bin := new(big.Int).Binomial(100, 50) + expectInt(t, bin, "100891344545564193334812497256") +} + +func TestFloatRatConversions(t *testing.T) { + rat := mustRat(t, "355/113") + f := new(big.Float).SetPrec(256) + f.SetRat(rat) + + recovered, acc := f.Rat(new(big.Rat)) + if acc != big.Exact { + t.Fatalf("expected exact conversion, got %v", acc) + } + delta := new(big.Rat).Sub(recovered, rat) + delta.Abs(delta) + threshold := new(big.Rat).SetFrac(big.NewInt(1), new(big.Int).Lsh(big.NewInt(1), 200)) + if delta.Cmp(threshold) > 0 { + t.Fatalf("Rat conversion drift too large: diff=%s", delta.RatString()) + } + + if text := f.Text('f', 6); text != "3.141593" { + t.Fatalf("Float.Text mismatch: got %q", text) + } + + three := new(big.Float).SetPrec(256).SetInt64(3) + quotient := new(big.Float).SetPrec(256).Quo(f, three) + if text := quotient.Text('f', 18); text != "1.047197640117994100" { + t.Fatalf("unexpected quotient string: %q", text) + } +} + +func TestFloatParsingAndRounding(t *testing.T) { + f := new(big.Float).SetPrec(80) + if _, ok := f.SetString("-1.234567890123456789e+42"); !ok { + t.Fatal("SetString failed for scientific notation") + } + + if text := f.Text('e', 6); text != "-1.234568e+42" { + t.Fatalf("rounded text mismatch: %q", text) + } + if !f.Signbit() { + t.Fatal("expected negative signbit") + } + mant := new(big.Float) + exp := f.MantExp(mant) + if exp <= 0 { + t.Fatalf("MantExp returned non-positive exponent: %d", exp) + } + if !mant.Signbit() { + t.Fatalf("mantissa should inherit sign: %s", mant.Text('p', 0)) + } + + twoThirds := new(big.Float).SetPrec(80).SetRat(big.NewRat(2, 3)) + truncated := new(big.Float).SetPrec(20) + truncated.SetMode(big.ToZero) + truncated.Set(twoThirds) + if truncated.Cmp(twoThirds) >= 0 { + t.Fatalf("expected truncated value to be < original: %s vs %s", truncated.Text('p', 0), twoThirds.Text('p', 0)) + } + if truncated.Sign() != 1 { + t.Fatalf("unexpected sign for truncated value: %d", truncated.Sign()) + } +} + +func TestRatOperations(t *testing.T) { + a := mustRat(t, "2/3") + b := mustRat(t, "-4/9") + + sum := new(big.Rat).Add(new(big.Rat).Set(a), b) + expectRat(t, sum, "2/9") + + product := new(big.Rat).Mul(new(big.Rat).Set(a), b) + expectRat(t, product, "-8/27") + + quotient := new(big.Rat).Quo(new(big.Rat).Set(a), b) + expectRat(t, quotient, "-3/2") + + neg := new(big.Rat).Neg(b) + expectRat(t, neg, "4/9") + + abs := new(big.Rat).Abs(b) + expectRat(t, abs, "4/9") + + if a.Sign() != 1 || b.Sign() != -1 { + t.Fatalf("unexpected sign results: %d %d", a.Sign(), b.Sign()) + } + + sumFloat := sum.FloatString(4) + if sumFloat != "0.2222" { + t.Fatalf("FloatString mismatch: %q", sumFloat) + } +} + +func TestRatSetStringForms(t *testing.T) { + cases := []struct { + literal string + want string + }{ + {"-0.125", "-1/8"}, + {"3/21", "1/7"}, + {"12.5", "25/2"}, + {"1p-4", "1/16"}, + } + for _, tc := range cases { + r := mustRat(t, tc.literal) + if r.Cmp(mustRat(t, tc.want)) != 0 { + t.Fatalf("SetString mismatch for %q: got %s, want %s", tc.literal, r.RatString(), tc.want) + } + } +} + +func TestPackageLevelSymbols(t *testing.T) { + if s := big.Accuracy(1).String(); s == "" { + t.Fatal("Accuracy.String returned empty string") + } + if s := big.RoundingMode(0).String(); s == "" { + t.Fatal("RoundingMode.String returned empty string") + } + if _, ok := any(big.ErrNaN{}).(error); !ok { + t.Fatal("ErrNaN should implement error") + } + if big.MaxBase <= 1 { + t.Fatalf("unexpected MaxBase: %d", big.MaxBase) + } + if big.MaxExp <= big.MinExp { + t.Fatalf("MinExp/MaxExp out of order: %d >= %d", big.MinExp, big.MaxExp) + } + if big.MaxPrec == 0 { + t.Fatal("MaxPrec should be > 0") + } + + n := big.NewInt(1001) + m := big.NewInt(9907) + if v := big.Jacobi(n, m); v == 0 { + t.Fatalf("Jacobi(%s,%s) returned 0", n, m) + } + + parsed, base, err := big.ParseFloat("3.125", 10, 80, big.ToNearestEven) + if err != nil { + t.Fatalf("ParseFloat failed: %v", err) + } + if base != 10 { + t.Fatalf("ParseFloat base = %d, want 10", base) + } + if text := parsed.Text('f', 3); text != "3.125" { + t.Fatalf("ParseFloat text = %q", text) + } + if w := big.Word(7); w != 7 { + t.Fatalf("Word conversion mismatch: got %d, want 7", w) + } +} + +func TestFloatExtendedAPI(t *testing.T) { + f := new(big.Float).SetPrec(128).SetMode(big.ToNearestAway) + if f.Mode() != big.ToNearestAway { + t.Fatalf("unexpected rounding mode: %v", f.Mode()) + } + f.SetFloat64(123.5) + if f.Prec() != 128 { + t.Fatalf("unexpected precision: %d", f.Prec()) + } + if f.MinPrec() == 0 { + t.Fatal("MinPrec should be > 0 for finite values") + } + + buf := f.Append(nil, 'f', 1) + if !strings.HasPrefix(string(buf), "123.5") { + t.Fatalf("Append produced %q", buf) + } + buf, err := f.AppendText(nil) + if err != nil || len(buf) == 0 { + t.Fatalf("AppendText failed: %v %q", err, buf) + } + if s := fmt.Sprintf("%+.2f", f); !strings.Contains(s, "+123.50") { + t.Fatalf("Format mismatch: %q", s) + } + if f.String() == "" { + t.Fatal("String returned empty") + } + fb := &formatBuffer{} + f.Format(fb, 'f') + if fb.String() == "" { + t.Fatal("Format buffer should capture output") + } + + dup := new(big.Float).Copy(f) + if dup.Cmp(f) != 0 { + t.Fatalf("Copy mismatch: %s vs %s", dup.String(), f.String()) + } + + abs := new(big.Float).Abs(new(big.Float).SetFloat64(-5.25)) + if abs.Sign() != 1 { + t.Fatalf("Abs produced sign %d", abs.Sign()) + } + + sum := new(big.Float).Add(new(big.Float).SetFloat64(1.5), new(big.Float).SetFloat64(2.25)) + if v, _ := sum.Float64(); math.Abs(v-3.75) > 1e-9 { + t.Fatalf("Add result mismatch: %v", v) + } + diff := new(big.Float).Sub(new(big.Float).SetFloat64(5), new(big.Float).SetFloat64(2)) + if v, _ := diff.Float64(); v != 3 { + t.Fatalf("Sub result mismatch: %v", v) + } + prod := new(big.Float).Mul(new(big.Float).SetFloat64(3), new(big.Float).SetFloat64(4)) + if v, _ := prod.Float64(); v != 12 { + t.Fatalf("Mul result mismatch: %v", v) + } + neg := new(big.Float).Neg(new(big.Float).SetFloat64(7)) + if neg.Sign() != -1 { + t.Fatalf("Neg sign mismatch: %d", neg.Sign()) + } + + if f32, acc := sum.Float32(); math.Abs(float64(f32)-3.75) > 1e-5 || acc.String() == "" { + t.Fatalf("Float32 conversion (%v,%v)", f32, acc) + } + if v, acc := sum.Float64(); math.Abs(v-3.75) > 1e-9 || acc.String() == "" { + t.Fatalf("Float64 conversion (%v,%v)", v, acc) + } + + frac := mustFloat(t, "42.75") + intPart := new(big.Int) + if _, acc := frac.Int(intPart); acc != big.Below { + t.Fatalf("Int accuracy = %v, want Below", acc) + } + if intPart.String() != "42" { + t.Fatalf("Int result = %s", intPart) + } + if _, acc := new(big.Float).SetInt64(17).Int64(); acc != big.Exact { + t.Fatalf("Int64 accuracy %v, want Exact", acc) + } + if u, acc := new(big.Float).SetUint64(21).Uint64(); u != 21 || acc != big.Exact { + t.Fatalf("Uint64 result (%d,%v)", u, acc) + } + if !new(big.Float).SetInt64(5).IsInt() { + t.Fatal("IsInt should report true") + } + if new(big.Float).SetFloat64(3.14).IsInt() { + t.Fatal("IsInt should report false") + } + + func() { + defer func() { + if r := recover(); r == nil { + t.Fatal("expected ErrNaN panic") + } else if err, ok := r.(big.ErrNaN); !ok { + t.Fatalf("unexpected panic type: %T", r) + } else if err.Error() == "" { + t.Fatal("ErrNaN.Error returned empty string") + } + }() + new(big.Float).Quo(big.NewFloat(0), big.NewFloat(0)) + }() + if parsed, base, err := new(big.Float).Parse("0x1.2p+2", 0); err != nil || base != 16 { + t.Fatalf("Parse hex failed: %v base=%d", err, base) + } else if text := parsed.Text('f', 1); text != "4.5" { + t.Fatalf("Parse hex value mismatch: %s", text) + } + + var scanned big.Float + if _, err := fmt.Fscan(strings.NewReader("-56.5"), &scanned); err != nil { + t.Fatalf("Scan failed: %v", err) + } + if scanned.String() != "-56.5" { + t.Fatalf("Scan value mismatch: %s", scanned.String()) + } + floatScan := &scanState{strings.NewReader("12.5")} + var directScanned big.Float + if err := directScanned.Scan(floatScan, 'f'); err != nil { + t.Fatalf("Float.Scan failed: %v", err) + } + if directScanned.Text('f', 1) != "12.5" { + t.Fatalf("Float.Scan value mismatch: %s", directScanned.Text('f', 1)) + } + + text, err := f.MarshalText() + if err != nil { + t.Fatalf("MarshalText failed: %v", err) + } + var unmarshaled big.Float + if err := unmarshaled.UnmarshalText(text); err != nil { + t.Fatalf("UnmarshalText failed: %v", err) + } + if unmarshaled.Cmp(f) != 0 { + t.Fatalf("UnmarshalText mismatch: %v vs %v", &unmarshaled, f) + } + + gobBytes, err := f.GobEncode() + if err != nil { + t.Fatalf("GobEncode failed: %v", err) + } + var gobDecoded big.Float + if err := gobDecoded.GobDecode(gobBytes); err != nil { + t.Fatalf("GobDecode failed: %v", err) + } + if gobDecoded.Cmp(f) != 0 { + t.Fatalf("Gob round-trip mismatch: %v vs %v", &gobDecoded, f) + } + + if err := f.UnmarshalText([]byte("1.5")); err != nil { + t.Fatalf("UnmarshalText overwrite failed: %v", err) + } + if f.Text('f', 1) != "1.5" { + t.Fatalf("UnmarshalText value mismatch: %s", f.Text('f', 1)) + } + + if _, ok := f.SetString("-9.25"); !ok { + t.Fatal("SetString should report success") + } + if !strings.HasPrefix(f.Text('f', 2), "-9.25") { + t.Fatalf("SetString value mismatch: %s", f.Text('f', 2)) + } + + if _, _, err := f.Parse("1.5", 10); err != nil { + t.Fatalf("Parse overwrite failed: %v", err) + } + + f.SetInf(true) + if !f.IsInf() { + t.Fatal("SetInf(+Inf) did not produce infinity") + } + f.SetInf(false) + if !f.IsInf() { + t.Fatal("SetInf(-Inf) did not produce infinity") + } + + f.SetInt(big.NewInt(-12)) + if v, acc := f.Int64(); v != -12 || acc != big.Exact { + t.Fatalf("SetInt/Int64 mismatch: (%d,%v)", v, acc) + } + f.SetUint64(33) + if v, acc := f.Uint64(); v != 33 || acc != big.Exact { + t.Fatalf("SetUint64/Uint64 mismatch: (%d,%v)", v, acc) + } + + rounded := new(big.Float).SetPrec(24).SetMode(big.ToZero) + if _, _, err := rounded.Parse("0.1", 10); err != nil { + t.Fatalf("Parse for accuracy failed: %v", err) + } + if rounded.Acc() == big.Exact { + t.Fatal("expected inexact accuracy for 0.1") + } + + mant := new(big.Float).SetFloat64(0.75) + value := new(big.Float).SetPrec(80).SetMantExp(mant, 4) + if v, _ := value.Float64(); math.Abs(v-12) > 1e-9 { + t.Fatalf("SetMantExp value mismatch: %v", v) + } + + sqrt := new(big.Float).SetPrec(200).Sqrt(mustFloat(t, "49")) + if sqrt.Text('f', 1) != "7.0" { + t.Fatalf("Sqrt mismatch: %s", sqrt) + } + + inf := new(big.Float).SetInf(true) + if !inf.IsInf() { + t.Fatal("SetInf(true) not recognized") + } +} + +func TestIntExtendedAPI(t *testing.T) { + a := mustInt(t, "12345678901234567890") + b := mustInt(t, "9876543210987654321") + + and := new(big.Int).And(new(big.Int).Set(a), b) + or := new(big.Int).Or(new(big.Int).Set(a), b) + xor := new(big.Int).Xor(new(big.Int).Set(a), b) + andNot := new(big.Int).AndNot(new(big.Int).Set(a), b) + not := new(big.Int).Not(big.NewInt(3)) + if and.BitLen() == 0 || or.Cmp(and) == 0 || xor.BitLen() == 0 || andNot.Sign() == 0 || not.Sign() >= 0 { + t.Fatal("bitwise operations produced unexpected results") + } + + buf := new(big.Int).SetUint64(255).Append(nil, 16) + if string(buf) != "ff" { + t.Fatalf("Append hex mismatch: %q", buf) + } + text, err := new(big.Int).SetInt64(-42).AppendText(nil) + if err != nil || string(text) != "-42" { + t.Fatalf("AppendText mismatch: %q %v", text, err) + } + + value := mustInt(t, "0x1234") + if bit := value.Bit(4); bit != 1 { + t.Fatalf("Bit(4) = %d", bit) + } + if value.BitLen() <= 0 { + t.Fatal("BitLen should be positive") + } + if bits := value.Bits(); len(bits) == 0 { + t.Fatal("Bits slice empty") + } + if bytes := value.Bytes(); len(bytes) == 0 { + t.Fatal("Bytes slice empty") + } + bufFill := make([]byte, 4) + if out := value.FillBytes(bufFill); len(out) != len(bufFill) || bufFill[len(bufFill)-1] == 0 { + t.Fatalf("FillBytes unexpected result: %x", bufFill) + } + + if cmp := new(big.Int).Set(a).CmpAbs(new(big.Int).Neg(b)); cmp <= 0 { + t.Fatalf("CmpAbs result: %d", cmp) + } + + dividend := mustInt(t, "1234567890") + divisor := big.NewInt(321) + quot := new(big.Int).Div(new(big.Int).Set(dividend), divisor) + rem := new(big.Int).Rem(new(big.Int).Set(dividend), divisor) + otherQuot := new(big.Int) + otherRem := new(big.Int) + otherQuot.DivMod(new(big.Int).Set(dividend), divisor, otherRem) + if quot.Cmp(otherQuot) != 0 || rem.Cmp(otherRem) != 0 { + t.Fatal("Div/DivMod mismatch") + } + if q := new(big.Int).Quo(new(big.Int).Set(dividend), divisor); q.Cmp(quot) != 0 { + t.Fatal("Quo mismatch") + } + if r := new(big.Int).Mod(new(big.Int).Set(dividend), divisor); r.Cmp(rem) != 0 { + t.Fatal("Mod mismatch") + } + + if exp := new(big.Int).Exp(big.NewInt(3), big.NewInt(5), nil); exp.Cmp(big.NewInt(243)) != 0 { + t.Fatalf("Exp mismatch: %s", exp) + } + if mul := new(big.Int).MulRange(1, 5); mul.Cmp(big.NewInt(120)) != 0 { + t.Fatalf("MulRange mismatch: %s", mul) + } + + if v, acc := value.Float64(); v <= 0 || acc.String() == "" { + t.Fatalf("Float64 returned (%v,%v)", v, acc) + } + if s := fmt.Sprintf("%#x", value); !strings.HasPrefix(s, "0x") { + t.Fatalf("Format mismatch: %q", s) + } + intFb := &formatBuffer{} + value.Format(intFb, 'd') + if intFb.String() == "" { + t.Fatal("Int.Format produced empty output") + } + + gobBytes, err := value.GobEncode() + if err != nil { + t.Fatalf("GobEncode failed: %v", err) + } + var gobDecoded big.Int + if err := gobDecoded.GobDecode(gobBytes); err != nil || gobDecoded.Cmp(value) != 0 { + t.Fatalf("GobDecode mismatch: %s %v", &gobDecoded, err) + } + + small := big.NewInt(42) + if v := small.Int64(); v != 42 { + t.Fatalf("Int64 mismatch: %d", v) + } + if v := small.Uint64(); v != 42 { + t.Fatalf("Uint64 mismatch: %d", v) + } + if !small.IsInt64() || !small.IsUint64() { + t.Fatal("IsInt64/IsUint64 should be true") + } + + jsonBytes, err := json.Marshal(value) + if err != nil || len(jsonBytes) == 0 { + t.Fatalf("MarshalJSON failed: %v", err) + } + if _, err := value.MarshalJSON(); err != nil { + t.Fatalf("direct MarshalJSON failed: %v", err) + } + var parsed big.Int + if err := parsed.UnmarshalJSON(jsonBytes); err != nil || parsed.Cmp(value) != 0 { + t.Fatalf("UnmarshalJSON mismatch: %s %v", &parsed, err) + } + + textBytes, err := value.MarshalText() + if err != nil { + t.Fatalf("MarshalText failed: %v", err) + } + if err := parsed.UnmarshalText(textBytes); err != nil || parsed.Cmp(value) != 0 { + t.Fatalf("UnmarshalText mismatch: %s %v", &parsed, err) + } + + if inv := new(big.Int).ModInverse(big.NewInt(3), big.NewInt(11)); inv.Cmp(big.NewInt(4)) != 0 { + t.Fatalf("ModInverse mismatch: %s", inv) + } + if root := new(big.Int).ModSqrt(big.NewInt(9), big.NewInt(23)); root == nil || (root.Cmp(big.NewInt(3)) != 0 && root.Cmp(big.NewInt(20)) != 0) { + t.Fatalf("ModSqrt mismatch: %s", root) + } + + if !big.NewInt(101).ProbablyPrime(5) { + t.Fatal("ProbablyPrime(101) should be true") + } + + rng := rand.New(rand.NewSource(1)) + randMax := big.NewInt(1000) + randVal := new(big.Int).Rand(rng, randMax) + if randVal.Sign() < 0 || randVal.Cmp(randMax) >= 0 { + t.Fatalf("Rand produced out of range value: %s", randVal) + } + + shift := new(big.Int).Lsh(big.NewInt(1), 10) + shift.Rsh(shift, 4) + if shift.Cmp(big.NewInt(64)) != 0 { + t.Fatalf("Rsh mismatch: %s", shift) + } + + var scannedInt big.Int + if _, err := fmt.Fscan(strings.NewReader("0xff"), &scannedInt); err != nil || scannedInt.Int64() != 255 { + t.Fatalf("Scan mismatch: %s %v", &scannedInt, err) + } + intScan := &scanState{strings.NewReader("42")} + var directInt big.Int + if err := directInt.Scan(intScan, 'd'); err != nil || directInt.Int64() != 42 { + t.Fatalf("Int.Scan mismatch: %s %v", &directInt, err) + } + + setBits := []big.Word{0x1, 0x2} + fromBits := new(big.Int).SetBits(setBits) + if fromBits.BitLen() <= value.BitLen() { + t.Fatalf("SetBits unexpected value: %s", fromBits) + } + + setBytes := new(big.Int).SetBytes([]byte{0x12, 0x34}) + if setBytes.Cmp(big.NewInt(0x1234)) != 0 { + t.Fatalf("SetBytes mismatch: %s", setBytes) + } + + v := new(big.Int) + v.SetBit(v, 5, 1) + if v.Bit(5) != 1 { + t.Fatal("SetBit failed") + } + + if tz := new(big.Int).SetUint64(64).TrailingZeroBits(); tz != 6 { + t.Fatalf("TrailingZeroBits mismatch: %d", tz) + } + + sqrt := new(big.Int).SetUint64(81) + sqrt.Sqrt(sqrt) + if sqrt.Cmp(big.NewInt(9)) != 0 { + t.Fatalf("Sqrt mismatch: %s", sqrt) + } + + if text := value.Text(16); text == "" { + t.Fatal("Text should not be empty") + } +} + +func TestRatExtendedAPI(t *testing.T) { + val := mustRat(t, "3/7") + if val.Num().Cmp(big.NewInt(3)) != 0 || val.Denom().Cmp(big.NewInt(7)) != 0 { + t.Fatalf("Num/Denom mismatch: %s/%s", val.Num(), val.Denom()) + } + + buf, err := val.AppendText(nil) + if err != nil || string(buf) != "3/7" { + t.Fatalf("AppendText mismatch: %q %v", buf, err) + } + + if f32, exact := val.Float32(); math.Abs(float64(f32)-0.4285714) > 1e-5 || exact { + t.Fatalf("Float32 conversion (%v,%v)", f32, exact) + } + if f64, exact := val.Float64(); math.Abs(f64-3.0/7.0) > 1e-12 || exact { + t.Fatalf("Float64 conversion (%v,%v)", f64, exact) + } + + prec, exact := mustRat(t, "1/4").FloatPrec() + if prec != 2 || !exact { + t.Fatalf("FloatPrec(1/4) = (%d,%v)", prec, exact) + } + prec, exact = mustRat(t, "2/3").FloatPrec() + if prec != 0 || exact { + t.Fatalf("FloatPrec(2/3) = (%d,%v)", prec, exact) + } + + gobBytes, err := val.GobEncode() + if err != nil { + t.Fatalf("GobEncode failed: %v", err) + } + var gobDecoded big.Rat + if err := gobDecoded.GobDecode(gobBytes); err != nil || gobDecoded.Cmp(val) != 0 { + t.Fatalf("GobDecode mismatch: %s %v", gobDecoded.RatString(), err) + } + + inv := new(big.Rat).Inv(val) + if inv.Cmp(mustRat(t, "7/3")) != 0 { + t.Fatalf("Inv mismatch: %s", inv.RatString()) + } + + if val.IsInt() || !mustRat(t, "8").IsInt() { + t.Fatal("IsInt mismatch") + } + + text, err := val.MarshalText() + if err != nil { + t.Fatalf("MarshalText failed: %v", err) + } + var parsed big.Rat + if err := parsed.UnmarshalText(text); err != nil || parsed.Cmp(val) != 0 { + t.Fatalf("UnmarshalText mismatch: %s %v", parsed.RatString(), err) + } + + if parsed.SetFloat64(0.125) == nil { + t.Fatal("SetFloat64 returned nil") + } + if parsed.Cmp(mustRat(t, "1/8")) != 0 { + t.Fatalf("SetFloat64 mismatch: %s", parsed.RatString()) + } + parsed.SetInt(big.NewInt(-5)) + if parsed.Num().Cmp(big.NewInt(-5)) != 0 || parsed.Denom().Cmp(big.NewInt(1)) != 0 { + t.Fatalf("SetInt mismatch: %s/%s", parsed.Num(), parsed.Denom()) + } + parsed.SetInt64(6) + if parsed.Num().Cmp(big.NewInt(6)) != 0 || parsed.Denom().Cmp(big.NewInt(1)) != 0 { + t.Fatalf("SetInt64 mismatch: %s/%s", parsed.Num(), parsed.Denom()) + } + parsed.SetUint64(15) + if parsed.Num().Cmp(big.NewInt(15)) != 0 || parsed.Denom().Cmp(big.NewInt(1)) != 0 { + t.Fatalf("SetUint64 mismatch: %s/%s", parsed.Num(), parsed.Denom()) + } + parsed.SetFrac64(7, 9) + if parsed.Cmp(mustRat(t, "7/9")) != 0 { + t.Fatalf("SetFrac64 mismatch: %s", parsed.RatString()) + } + + var scanned big.Rat + if _, err := fmt.Fscan(strings.NewReader("-11/13"), &scanned); err != nil || scanned.Cmp(mustRat(t, "-11/13")) != 0 { + t.Fatalf("Scan mismatch: %s %v", scanned.RatString(), err) + } + ratScan := &scanState{strings.NewReader("1.25")} + var directRat big.Rat + if err := directRat.Scan(ratScan, 'f'); err != nil || directRat.Cmp(mustRat(t, "5/4")) != 0 { + t.Fatalf("Rat.Scan mismatch: %s %v", directRat.RatString(), err) + } + + if parsed.String() == "" { + t.Fatal("String should not be empty") + } +} diff --git a/test/std/math/bits/bits_bench_test.go b/test/std/math/bits/bits_bench_test.go new file mode 100644 index 0000000000..e461b26a02 --- /dev/null +++ b/test/std/math/bits/bits_bench_test.go @@ -0,0 +1,37 @@ +package bits_test + +import ( + "math/bits" + "testing" +) + +func BenchmarkOnesCount64(b *testing.B) { + var total int + for i := 0; i < b.N; i++ { + total += bits.OnesCount64(uint64(i)) + } + if total == 0 { + b.Fatalf("unexpected zero total") + } +} + +func BenchmarkRotateLeft64(b *testing.B) { + var acc uint64 = 0x123456789ABCDEF0 + for i := 0; i < b.N; i++ { + acc = bits.RotateLeft64(acc, 13) + } + if acc == 0 { + b.Fatalf("rotate produced zero") + } +} + +func BenchmarkDiv64(b *testing.B) { + var sum uint64 + for i := 0; i < b.N; i++ { + q, r := bits.Div64(1, uint64(i)+12345, 37) + sum += q + r + } + if sum == 0 { + b.Fatalf("sum should be non-zero") + } +} diff --git a/test/std/math/bits/bits_test.go b/test/std/math/bits/bits_test.go new file mode 100644 index 0000000000..20cacb8c8a --- /dev/null +++ b/test/std/math/bits/bits_test.go @@ -0,0 +1,432 @@ +package bits_test + +import ( + "math/big" + "math/bits" + "strconv" + "testing" +) + +func TestUintSizeMatchesIntSize(t *testing.T) { + if bits.UintSize != strconv.IntSize { + t.Fatalf("UintSize=%d IntSize=%d", bits.UintSize, strconv.IntSize) + } + if bits.UintSize != 32 && bits.UintSize != 64 { + t.Fatalf("unexpected UintSize %d", bits.UintSize) + } +} + +func TestLeadingLenTrailingCounts(t *testing.T) { + values := []uint64{0, 1, 2, 3, 8, 0xFFFF, 1 << 63} + for _, v := range values { + lz := bits.LeadingZeros64(v) + ln := bits.Len64(v) + tz := bits.TrailingZeros64(v) + if v == 0 { + if lz != 64 || ln != 0 || tz != 64 { + t.Fatalf("zero counts mismatch: lz=%d ln=%d tz=%d", lz, ln, tz) + } + continue + } + if lz+ln != 64 { + t.Fatalf("value %x: leading+len=%d", v, lz+ln) + } + expectedTZ := trailingZerosSlow64(v) + if tz != expectedTZ { + t.Fatalf("value %x: trailing=%d expected=%d", v, tz, expectedTZ) + } + } + + values32 := []uint32{0, 1, 2, 3, 8, 0x7FFF0000} + for _, v := range values32 { + if v == 0 { + if bits.LeadingZeros32(v) != 32 || bits.Len32(v) != 0 || bits.TrailingZeros32(v) != 32 { + t.Fatalf("zero counts32 mismatch") + } + continue + } + if bits.LeadingZeros32(v)+bits.Len32(v) != 32 { + t.Fatalf("value32 %x: leading+len mismatch", v) + } + if bits.TrailingZeros32(v) != trailingZerosSlow32(v) { + t.Fatalf("value32 %x: trailing mismatch", v) + } + } + + values16 := []uint16{0, 1, 2, 4, 0x00FF, 0x8000} + for _, v := range values16 { + if v == 0 { + if bits.LeadingZeros16(v) != 16 || bits.Len16(v) != 0 || bits.TrailingZeros16(v) != 16 { + t.Fatalf("zero counts16 mismatch") + } + continue + } + if bits.LeadingZeros16(v)+bits.Len16(v) != 16 { + t.Fatalf("value16 %x: leading+len mismatch", v) + } + if bits.TrailingZeros16(v) != trailingZerosSlow16(v) { + t.Fatalf("value16 %x: trailing mismatch", v) + } + } + + values8 := []uint8{0, 1, 2, 4, 0x0F, 0x80} + for _, v := range values8 { + if v == 0 { + if bits.LeadingZeros8(v) != 8 || bits.Len8(v) != 0 || bits.TrailingZeros8(v) != 8 { + t.Fatalf("zero counts8 mismatch") + } + continue + } + if bits.LeadingZeros8(v)+bits.Len8(v) != 8 { + t.Fatalf("value8 %x: leading+len mismatch", v) + } + if bits.TrailingZeros8(v) != trailingZerosSlow8(v) { + t.Fatalf("value8 %x: trailing mismatch", v) + } + } + + valuesUint := []uint{0, 1, 2, 3, 8, 1 << (bits.UintSize - 1)} + for _, v := range valuesUint { + if v == 0 { + if bits.LeadingZeros(v) != bits.UintSize || bits.Len(v) != 0 || bits.TrailingZeros(v) != bits.UintSize { + t.Fatalf("zero counts uint mismatch") + } + continue + } + if bits.LeadingZeros(v)+bits.Len(v) != bits.UintSize { + t.Fatalf("value uint %x: leading+len mismatch", v) + } + if bits.TrailingZeros(v) != trailingZerosSlowUint(v) { + t.Fatalf("value uint %x: trailing mismatch", v) + } + } +} + +func trailingZerosSlow64(v uint64) int { + if v == 0 { + return 64 + } + count := 0 + for (v & 1) == 0 { + count++ + v >>= 1 + } + return count +} + +func trailingZerosSlow32(v uint32) int { + if v == 0 { + return 32 + } + count := 0 + for (v & 1) == 0 { + count++ + v >>= 1 + } + return count +} + +func trailingZerosSlow16(v uint16) int { + if v == 0 { + return 16 + } + count := 0 + for (v & 1) == 0 { + count++ + v >>= 1 + } + return count +} + +func trailingZerosSlow8(v uint8) int { + if v == 0 { + return 8 + } + count := 0 + for (v & 1) == 0 { + count++ + v >>= 1 + } + return count +} + +func trailingZerosSlowUint(v uint) int { + if v == 0 { + return bits.UintSize + } + count := 0 + for (v & 1) == 0 { + count++ + v >>= 1 + } + return count +} + +func TestOnesCount(t *testing.T) { + if bits.OnesCount(0) != 0 { + t.Fatal("OnesCount(0) should be zero") + } + if bits.OnesCount(1) != 1 { + t.Fatal("OnesCount(1) should be one") + } + if ones := bits.OnesCount64(0xF0F0F0F0F0F0F0F0); ones != 32 { + t.Fatalf("OnesCount64 expected 32 got %d", ones) + } + if ones := bits.OnesCount32(0xAAAAAAAA); ones != 16 { + t.Fatalf("OnesCount32 expected 16 got %d", ones) + } + if ones := bits.OnesCount16(0xF0F0); ones != 8 { + t.Fatalf("OnesCount16 expected 8 got %d", ones) + } + if ones := bits.OnesCount8(0xAA); ones != 4 { + t.Fatalf("OnesCount8 expected 4 got %d", ones) + } +} + +func TestReverseAndReverseBytes(t *testing.T) { + if got, want := bits.Reverse8(0x16), uint8(reverseSlow(0x16, 8)); got != want { + t.Fatalf("Reverse8 got 0x%02x want 0x%02x", got, want) + } + if got, want := bits.Reverse16(0x00F3), uint16(reverseSlow(0x00F3, 16)); got != want { + t.Fatalf("Reverse16 got 0x%04x want 0x%04x", got, want) + } + if got, want := bits.Reverse32(0x0000000F), uint32(reverseSlow(0x0000000F, 32)); got != want { + t.Fatalf("Reverse32 got 0x%08x want 0x%08x", got, want) + } + if got, want := bits.Reverse64(0x0123456789ABCDEF), reverseSlow(0x0123456789ABCDEF, 64); got != want { + t.Fatalf("Reverse64 got 0x%016x want 0x%016x", got, want) + } + + var uintValue uint64 + if bits.UintSize == 64 { + uintValue = 0x0123456789ABCDEF + } else { + uintValue = 0x89ABCDEF + } + if got, want := bits.Reverse(uint(uintValue)), uint(reverseSlow(uintValue, uint(bits.UintSize))); got != want { + t.Fatalf("Reverse(uint) got 0x%x want 0x%x", got, want) + } + + if got := bits.ReverseBytes16(0x0102); got != 0x0201 { + t.Fatalf("ReverseBytes16 got 0x%04x", got) + } + if got := bits.ReverseBytes32(0x01020304); got != 0x04030201 { + t.Fatalf("ReverseBytes32 got 0x%08x", got) + } + if got := bits.ReverseBytes64(0x0102030405060708); got != 0x0807060504030201 { + t.Fatalf("ReverseBytes64 got 0x%016x", got) + } + if got, want := bits.ReverseBytes(uint(uintValue)), uint(reverseBytesSlow(uintValue, uint(bits.UintSize))); got != want { + t.Fatalf("ReverseBytes(uint) got 0x%x want 0x%x", got, want) + } +} + +func reverseSlow(value uint64, width uint) uint64 { + var result uint64 + for i := uint(0); i < width; i++ { + if (value>>i)&1 == 1 { + result |= 1 << (width - 1 - i) + } + } + if width < 64 { + mask := uint64(1<> (8 * i)) & 0xFF + result |= byteVal << (8 * (bytes - 1 - i)) + } + if width < 64 { + mask := uint64(1< 0", z, got) + } + if got := cmplx.Conj(z); got != complex(real(z), -imag(z)) { + t.Fatalf("Conj(%v) = %v, want %v", z, got, complex(real(z), -imag(z))) + } + if got := cmplx.Phase(z); !closeFloat(got, math.Atan2(imag(z), real(z)), eps) { + t.Fatalf("Phase(%v) = %v, want %v", z, got, math.Atan2(imag(z), real(z))) + } + + r, theta := cmplx.Polar(z) + if got := cmplx.Rect(r, theta); !closeComplex(got, z, eps) { + t.Fatalf("Rect(Polar(%v)) = %v, want %v", z, got, z) + } + + if got := cmplx.Sin(cmplx.Asin(z)); !closeComplex(got, z, eps) { + t.Fatalf("Sin(Asin(%v)) = %v, want %v", z, got, z) + } + if got := cmplx.Cos(cmplx.Acos(z)); !closeComplex(got, z, eps) { + t.Fatalf("Cos(Acos(%v)) = %v, want %v", z, got, z) + } + if got := cmplx.Tan(cmplx.Atan(z)); !closeComplex(got, z, eps) { + t.Fatalf("Tan(Atan(%v)) = %v, want %v", z, got, z) + } + + w := complex(0.3, -0.2) + if got := cmplx.Sinh(cmplx.Asinh(w)); !closeComplex(got, w, eps) { + t.Fatalf("Sinh(Asinh(%v)) = %v, want %v", w, got, w) + } + if got := cmplx.Cosh(cmplx.Acosh(2 + w)); !closeComplex(got, 2+w, eps) { + t.Fatalf("Cosh(Acosh(%v)) = %v, want %v", 2+w, got, 2+w) + } + if got := cmplx.Tanh(cmplx.Atanh(w)); !closeComplex(got, w, eps) { + t.Fatalf("Tanh(Atanh(%v)) = %v, want %v", w, got, w) + } + + if got := cmplx.Cot(z); !closeComplex(got, 1/cmplx.Tan(z), eps) { + t.Fatalf("Cot(%v) = %v, want %v", z, got, 1/cmplx.Tan(z)) + } +} + +func TestExpLogPowSqrt(t *testing.T) { + z := complex(1.2, 0.6) + eps := 1e-10 + + if got := cmplx.Exp(cmplx.Log(z)); !closeComplex(got, z, eps) { + t.Fatalf("Exp(Log(%v)) = %v, want %v", z, got, z) + } + if got := cmplx.Log10(z); !closeComplex(got, cmplx.Log(z)/complex(math.Ln10, 0), eps) { + t.Fatalf("Log10(%v) = %v, want %v", z, got, cmplx.Log(z)/complex(math.Ln10, 0)) + } + if got := cmplx.Pow(z, 1); !closeComplex(got, z, eps) { + t.Fatalf("Pow(%v, 1) = %v, want %v", z, got, z) + } + if got := cmplx.Pow(z, 0); !closeComplex(got, 1, eps) { + t.Fatalf("Pow(%v, 0) = %v, want 1", z, got) + } + + root := cmplx.Sqrt(z) + if got := root * root; !closeComplex(got, z, eps) { + t.Fatalf("Sqrt(%v)^2 = %v, want %v", z, got, z) + } +} + +func TestInfAndNaN(t *testing.T) { + inf := cmplx.Inf() + if !cmplx.IsInf(inf) { + t.Fatalf("IsInf(Inf()) = false, want true") + } + if cmplx.IsNaN(inf) { + t.Fatalf("IsNaN(Inf()) = true, want false") + } + + nan := cmplx.NaN() + if !cmplx.IsNaN(nan) { + t.Fatalf("IsNaN(NaN()) = false, want true") + } +} diff --git a/test/std/math/math_bench_test.go b/test/std/math/math_bench_test.go new file mode 100644 index 0000000000..ae7b18fd24 --- /dev/null +++ b/test/std/math/math_bench_test.go @@ -0,0 +1,153 @@ +package math_test + +import ( + "math" + "testing" +) + +var ( + floatSink float64 + intSink int +) + +func BenchmarkSin(b *testing.B) { + x := 0.1 + var sum float64 + for i := 0; i < b.N; i++ { + sum += math.Sin(x) + x += 0.0001 + if x > 1.0 { + x = 0.1 + } + } + floatSink = sum +} + +func BenchmarkCos(b *testing.B) { + x := 0.2 + var sum float64 + for i := 0; i < b.N; i++ { + sum += math.Cos(x) + x += 0.0001 + if x > 1.0 { + x = 0.2 + } + } + floatSink = sum +} + +func BenchmarkTan(b *testing.B) { + x := 0.3 + var sum float64 + for i := 0; i < b.N; i++ { + sum += math.Tan(x) + x += 0.0001 + if x > 1.2 { + x = 0.3 + } + } + floatSink = sum +} + +func BenchmarkSqrt(b *testing.B) { + x := 2.0 + var sum float64 + for i := 0; i < b.N; i++ { + sum += math.Sqrt(x) + x += 0.001 + if x > 4.0 { + x = 2.0 + } + } + floatSink = sum +} + +func BenchmarkExp(b *testing.B) { + x := 0.5 + var sum float64 + for i := 0; i < b.N; i++ { + sum += math.Exp(x) + x += 0.0002 + if x > 1.5 { + x = 0.5 + } + } + floatSink = sum +} + +func BenchmarkLog(b *testing.B) { + x := 2.0 + var sum float64 + for i := 0; i < b.N; i++ { + sum += math.Log(x) + x += 0.001 + if x > 3.0 { + x = 2.0 + } + } + floatSink = sum +} + +func BenchmarkPow(b *testing.B) { + x, y := 1.5, 2.5 + var sum float64 + for i := 0; i < b.N; i++ { + sum += math.Pow(x, y) + x += 0.001 + y += 0.001 + if x > 3.0 { + x = 1.5 + } + if y > 3.5 { + y = 2.5 + } + } + floatSink = sum +} + +func BenchmarkAbs(b *testing.B) { + x := -1.5 + var sum float64 + for i := 0; i < b.N; i++ { + sum += math.Abs(x) + x -= 0.001 + if x < -3.0 { + x = -1.5 + } + } + floatSink = sum +} + +func BenchmarkMod(b *testing.B) { + x, y := 10.0, 3.0 + var sum float64 + for i := 0; i < b.N; i++ { + sum += math.Mod(x, y) + x += 0.5 + y += 0.1 + if x > 20.0 { + x = 10.0 + } + if y > 5.0 { + y = 3.0 + } + } + floatSink = sum +} + +func BenchmarkFrexp(b *testing.B) { + x := 8.0 + var sum float64 + var expSum int + for i := 0; i < b.N; i++ { + f, exp := math.Frexp(x) + sum += f + expSum += exp + x += 0.5 + if x > 32.0 { + x = 8.0 + } + } + floatSink = sum + intSink = expSum +} diff --git a/test/std/math/math_go124_test.go b/test/std/math/math_go124_test.go new file mode 100644 index 0000000000..d19ee6688e --- /dev/null +++ b/test/std/math/math_go124_test.go @@ -0,0 +1,15 @@ +//go:build go1.24 +// +build go1.24 + +package math_test + +import ( + "testing" +) + +// TestFutureAPI is a placeholder demonstrating how to gate tests +// for APIs introduced in Go 1.24 or later. +// This pattern allows tests to be written before llgo supports the new version. +func TestFutureAPI(t *testing.T) { + t.Skip("TODO: add tests for Go 1.24+ math APIs when available") +} diff --git a/test/std/math/math_test.go b/test/std/math/math_test.go new file mode 100644 index 0000000000..223823b0d9 --- /dev/null +++ b/test/std/math/math_test.go @@ -0,0 +1,345 @@ +package math_test + +import ( + "math" + "testing" +) + +const tolerance = 1e-12 + +func assertFloatNear(t *testing.T, got, want, tol float64) { + t.Helper() + if math.IsNaN(got) && math.IsNaN(want) { + return + } + if math.IsInf(got, 0) && math.IsInf(want, 0) && math.Signbit(got) == math.Signbit(want) { + return + } + diff := math.Abs(got - want) + if diff > tol { + t.Errorf("got %v, want %v (diff %v > tolerance %v)", got, want, diff, tol) + } +} + +func TestUnaryFloatFunctions(t *testing.T) { + tests := []struct { + name string + fn func(float64) float64 + input float64 + want float64 + tol float64 + }{ + {name: "Abs", fn: math.Abs, input: -3.5, want: 3.5, tol: 0}, + {name: "Acos", fn: math.Acos, input: 0.5, want: math.Pi / 3, tol: 1e-12}, + {name: "Acosh", fn: math.Acosh, input: 2, want: 1.3169578969248166, tol: 1e-12}, + {name: "Asin", fn: math.Asin, input: 0.5, want: math.Pi / 6, tol: 1e-12}, + {name: "Asinh", fn: math.Asinh, input: 1, want: 0.881373587019543, tol: 1e-12}, + {name: "Atan", fn: math.Atan, input: 1, want: math.Pi / 4, tol: 1e-12}, + {name: "Atanh", fn: math.Atanh, input: 0.5, want: 0.5493061443340549, tol: 1e-12}, + {name: "Cbrt", fn: math.Cbrt, input: 27, want: 3, tol: 1e-12}, + {name: "Ceil", fn: math.Ceil, input: -1.2, want: -1, tol: 0}, + {name: "Cos", fn: math.Cos, input: 0, want: 1, tol: 0}, + {name: "Cosh", fn: math.Cosh, input: 1, want: (math.Exp(1) + math.Exp(-1)) / 2, tol: 1e-12}, + {name: "Erf", fn: math.Erf, input: 1, want: 0.8427007929497149, tol: 1e-12}, + {name: "Erfc", fn: math.Erfc, input: 1, want: 0.1572992070502851, tol: 1e-12}, + {name: "Erfcinv", fn: math.Erfcinv, input: 0.5, want: 0.4769362762044699, tol: 1e-12}, + {name: "Erfinv", fn: math.Erfinv, input: 0.5, want: 0.4769362762044699, tol: 1e-12}, + {name: "Exp", fn: math.Exp, input: 2, want: math.E * math.E, tol: 1e-12}, + {name: "Exp2", fn: math.Exp2, input: 5, want: 32, tol: 1e-12}, + {name: "Expm1", fn: math.Expm1, input: 1, want: math.E - 1, tol: 1e-12}, + {name: "Floor", fn: math.Floor, input: -1.2, want: -2, tol: 0}, + {name: "Log", fn: math.Log, input: math.E, want: 1, tol: 1e-12}, + {name: "Log10", fn: math.Log10, input: 1000, want: 3, tol: 1e-12}, + {name: "Log1p", fn: math.Log1p, input: 1, want: math.Log(2), tol: 1e-12}, + {name: "Log2", fn: math.Log2, input: 32, want: 5, tol: 1e-12}, + {name: "Logb", fn: math.Logb, input: 8, want: 3, tol: 1e-12}, + {name: "Round", fn: math.Round, input: -1.5, want: -2, tol: 0}, + {name: "RoundToEven", fn: math.RoundToEven, input: 2.5, want: 2, tol: 0}, + {name: "Sin", fn: math.Sin, input: math.Pi / 2, want: 1, tol: 1e-12}, + {name: "Sinh", fn: math.Sinh, input: 1, want: (math.Exp(1) - math.Exp(-1)) / 2, tol: 1e-12}, + {name: "Sqrt", fn: math.Sqrt, input: 49, want: 7, tol: 1e-12}, + {name: "Tan", fn: math.Tan, input: math.Pi / 4, want: 1, tol: 1e-12}, + {name: "Tanh", fn: math.Tanh, input: 1, want: 0.7615941559557649, tol: 1e-12}, + {name: "Trunc", fn: math.Trunc, input: -1.9, want: -1, tol: 0}, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + got := tc.fn(tc.input) + assertFloatNear(t, got, tc.want, tc.tol) + }) + } +} + +func TestBinaryFloatFunctions(t *testing.T) { + assertFloatNear(t, math.Atan2(1, -1), 3*math.Pi/4, 1e-12) + + if got := math.Copysign(3, -1); got != -3 { + t.Fatalf("Copysign(3, -1) = %v, want -3", got) + } + if got := math.Copysign(-3, 1); got != 3 { + t.Fatalf("Copysign(-3, 1) = %v, want 3", got) + } + + if got := math.Dim(5, 3); got != 2 { + t.Fatalf("Dim(5, 3) = %v, want 2", got) + } + if got := math.Dim(3, 5); got != 0 { + t.Fatalf("Dim(3, 5) = %v, want 0", got) + } + + assertFloatNear(t, math.Hypot(3, 4), 5, 1e-12) + assertFloatNear(t, math.Max(1.2, 3.4), 3.4, 1e-12) + assertFloatNear(t, math.Min(1.2, 3.4), 1.2, 1e-12) + assertFloatNear(t, math.Pow(3, 4), 81, 1e-12) + assertFloatNear(t, math.Remainder(5.5, 2), -0.5, 1e-12) + assertFloatNear(t, math.FMA(2, 3, 4), 10, 1e-12) +} + +func TestMod(t *testing.T) { + testCases := []struct { + x, y float64 + want float64 + }{ + {x: 10, y: 3, want: 1}, + {x: 10.5, y: 3, want: 1.5}, + {x: -10, y: 3, want: -1}, + {x: 10, y: -3, want: 1}, + {x: 7.5, y: 2.5, want: 0}, + } + + for _, tc := range testCases { + got := math.Mod(tc.x, tc.y) + assertFloatNear(t, got, tc.want, 1e-12) + } +} + +func TestPow10AndLdexp(t *testing.T) { + assertFloatNear(t, math.Pow10(3), 1000, 0) + + if got := math.Ilogb(8); got != 3 { + t.Fatalf("Ilogb(8) = %d, want 3", got) + } + + assertFloatNear(t, math.Ldexp(0.75, 2), 3, 1e-12) +} + +func TestFrexp(t *testing.T) { + testCases := []struct { + input float64 + wantFrac float64 + wantExp int + }{ + {input: 0, wantFrac: 0, wantExp: 0}, + {input: 1, wantFrac: 0.5, wantExp: 1}, + {input: 8, wantFrac: 0.5, wantExp: 4}, + {input: -8, wantFrac: -0.5, wantExp: 4}, + {input: 0.25, wantFrac: 0.5, wantExp: -1}, + } + + for _, tc := range testCases { + frac, exp := math.Frexp(tc.input) + assertFloatNear(t, frac, tc.wantFrac, 0) + if exp != tc.wantExp { + t.Fatalf("Frexp(%v) exp = %v, want %v", tc.input, exp, tc.wantExp) + } + } +} + +func TestModf(t *testing.T) { + intPart, frac := math.Modf(3.5) + assertFloatNear(t, intPart, 3, 0) + assertFloatNear(t, frac, 0.5, 0) + + intPart, frac = math.Modf(-3.5) + assertFloatNear(t, intPart, -3, 0) + assertFloatNear(t, frac, -0.5, 0) +} + +func TestLgamma(t *testing.T) { + lgamma, sign := math.Lgamma(5) + assertFloatNear(t, lgamma, 3.1780538303479458, 1e-12) + if sign != 1 { + t.Fatalf("Lgamma(5) sign = %d, want 1", sign) + } +} + +func TestGamma(t *testing.T) { + assertFloatNear(t, math.Gamma(5), 24, 1e-12) +} + +func TestSincos(t *testing.T) { + s, c := math.Sincos(0.5) + assertFloatNear(t, s, 0.479425538604203, 1e-12) + assertFloatNear(t, c, 0.8775825618903728, 1e-12) +} + +func TestFloatBitConversions(t *testing.T) { + if got := math.Float32bits(3.5); got != 0x40600000 { + t.Fatalf("Float32bits(3.5) = 0x%08x, want 0x40600000", got) + } + if got := math.Float32frombits(0x3f800000); got != 1 { + t.Fatalf("Float32frombits(0x3f800000) = %v, want 1", got) + } + if got := math.Float64bits(3.5); got != 0x400c000000000000 { + t.Fatalf("Float64bits(3.5) = 0x%x, want 0x400c000000000000", got) + } + if got := math.Float64frombits(0x3ff0000000000000); got != 1 { + t.Fatalf("Float64frombits(0x3ff0000000000000) = %v, want 1", got) + } +} + +func TestNextafter(t *testing.T) { + next := math.Nextafter(1, 2) + want := math.Float64frombits(math.Float64bits(1) + 1) + assertFloatNear(t, next, want, 0) + + prev := math.Nextafter(1, 0) + wantPrev := math.Float64frombits(math.Float64bits(1) - 1) + assertFloatNear(t, prev, wantPrev, 0) + + next32 := math.Nextafter32(1, 2) + want32 := math.Float32frombits(math.Float32bits(1) + 1) + assertFloatNear(t, float64(next32), float64(want32), 0) + + prev32 := math.Nextafter32(1, 0) + wantPrev32 := math.Float32frombits(math.Float32bits(1) - 1) + assertFloatNear(t, float64(prev32), float64(wantPrev32), 0) +} + +func TestNaNAndInf(t *testing.T) { + if !math.IsNaN(math.NaN()) { + t.Fatal("NaN() should return NaN") + } + + if !math.IsInf(math.Inf(1), 1) { + t.Fatal("Inf(1) should return positive infinity") + } + if !math.IsInf(math.Inf(-1), -1) { + t.Fatal("Inf(-1) should return negative infinity") + } + + if math.IsNaN(1.0) { + t.Fatal("1.0 should not be NaN") + } + if math.IsInf(1.0, 0) { + t.Fatal("1.0 should not be infinity") + } +} + +func TestSignbit(t *testing.T) { + if !math.Signbit(-1) { + t.Fatal("Signbit(-1) = false, want true") + } + if !math.Signbit(math.Copysign(0, -1)) { + t.Fatal("Signbit(-0) = false, want true") + } + if math.Signbit(2) { + t.Fatal("Signbit(2) = true, want false") + } +} + +func TestBesselFunctions(t *testing.T) { + assertFloatNear(t, math.J0(5), -0.1775967713143383, 1e-12) + assertFloatNear(t, math.J1(5), -0.3275791375914652, 1e-12) + assertFloatNear(t, math.Jn(2, 5), 0.0465651162777522, 1e-12) + + assertFloatNear(t, math.Y0(5), -0.3085176252490338, 1e-12) + assertFloatNear(t, math.Y1(5), 0.1478631433912268, 1e-12) + assertFloatNear(t, math.Yn(2, 5), 0.3676628826055245, 1e-12) +} + +func TestMathematicalConstants(t *testing.T) { + tests := []struct { + name string + got float64 + want float64 + }{ + {name: "E", got: math.E, want: math.Exp(1)}, + {name: "Pi", got: math.Pi, want: 4 * math.Atan(1)}, + {name: "Phi", got: math.Phi, want: (1 + math.Sqrt(5)) / 2}, + {name: "Sqrt2", got: math.Sqrt2, want: math.Sqrt(2)}, + {name: "SqrtE", got: math.SqrtE, want: math.Sqrt(math.Exp(1))}, + {name: "SqrtPi", got: math.SqrtPi, want: math.Sqrt(4 * math.Atan(1))}, + {name: "SqrtPhi", got: math.SqrtPhi, want: math.Sqrt((1 + math.Sqrt(5)) / 2)}, + {name: "Ln2", got: math.Ln2, want: math.Log(2)}, + {name: "Log2E", got: math.Log2E, want: 1 / math.Log(2)}, + {name: "Ln10", got: math.Ln10, want: math.Log(10)}, + {name: "Log10E", got: math.Log10E, want: 1 / math.Log(10)}, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + assertFloatNear(t, tc.got, tc.want, tolerance) + }) + } +} + +func TestFloatLimitConstants(t *testing.T) { + if math.Float32bits(math.MaxFloat32) != 0x7f7fffff { + t.Fatalf("Float32bits(MaxFloat32) = 0x%08x, want 0x7f7fffff", math.Float32bits(math.MaxFloat32)) + } + if math.Float32bits(math.SmallestNonzeroFloat32) != 0x00000001 { + t.Fatalf("Float32bits(SmallestNonzeroFloat32) = 0x%08x, want 0x00000001", math.Float32bits(math.SmallestNonzeroFloat32)) + } + if math.Float64bits(math.MaxFloat64) != 0x7fefffffffffffff { + t.Fatalf("Float64bits(MaxFloat64) = 0x%x, want 0x7fefffffffffffff", math.Float64bits(math.MaxFloat64)) + } + if math.Float64bits(math.SmallestNonzeroFloat64) != 0x0000000000000001 { + t.Fatalf("Float64bits(SmallestNonzeroFloat64) = 0x%x, want 0x0000000000000001", math.Float64bits(math.SmallestNonzeroFloat64)) + } +} + +func TestIntegerLimitConstants(t *testing.T) { + expectedMaxInt := int(^uint(0) >> 1) + if int(math.MaxInt) != expectedMaxInt { + t.Fatalf("MaxInt = %d, want %d", int(math.MaxInt), expectedMaxInt) + } + expectedMinInt := -expectedMaxInt - 1 + if int(math.MinInt) != expectedMinInt { + t.Fatalf("MinInt = %d, want %d", int(math.MinInt), expectedMinInt) + } + if math.MaxInt8 != 1<<7-1 { + t.Fatalf("MaxInt8 = %d, want %d", math.MaxInt8, 1<<7-1) + } + if math.MinInt8 != -1<<7 { + t.Fatalf("MinInt8 = %d, want %d", math.MinInt8, -1<<7) + } + if math.MaxInt16 != 1<<15-1 { + t.Fatalf("MaxInt16 = %d, want %d", math.MaxInt16, 1<<15-1) + } + if math.MinInt16 != -1<<15 { + t.Fatalf("MinInt16 = %d, want %d", math.MinInt16, -1<<15) + } + if math.MaxInt32 != 1<<31-1 { + t.Fatalf("MaxInt32 = %d, want %d", math.MaxInt32, 1<<31-1) + } + if math.MinInt32 != -1<<31 { + t.Fatalf("MinInt32 = %d, want %d", math.MinInt32, -1<<31) + } + expectedMaxInt64 := int64(^uint64(0) >> 1) + if int64(math.MaxInt64) != expectedMaxInt64 { + t.Fatalf("MaxInt64 = %d, want %d", int64(math.MaxInt64), expectedMaxInt64) + } + expectedMinInt64 := -expectedMaxInt64 - 1 + if int64(math.MinInt64) != expectedMinInt64 { + t.Fatalf("MinInt64 = %d, want %d", int64(math.MinInt64), expectedMinInt64) + } + if uint(math.MaxUint) != ^uint(0) { + t.Fatalf("MaxUint = %d, want %d", uint64(math.MaxUint), uint64(^uint(0))) + } + if uint8(math.MaxUint8) != ^uint8(0) { + t.Fatalf("MaxUint8 = %d, want %d", uint8(math.MaxUint8), ^uint8(0)) + } + if uint16(math.MaxUint16) != ^uint16(0) { + t.Fatalf("MaxUint16 = %d, want %d", uint16(math.MaxUint16), ^uint16(0)) + } + if uint32(math.MaxUint32) != ^uint32(0) { + t.Fatalf("MaxUint32 = %d, want %d", uint32(math.MaxUint32), ^uint32(0)) + } + if uint64(math.MaxUint64) != ^uint64(0) { + t.Fatalf("MaxUint64 = %d, want %d", uint64(math.MaxUint64), ^uint64(0)) + } +} diff --git a/test/std/math/rand/rand_test.go b/test/std/math/rand/rand_test.go new file mode 100644 index 0000000000..28c68a583c --- /dev/null +++ b/test/std/math/rand/rand_test.go @@ -0,0 +1,172 @@ +package rand_test + +import ( + "math" + rand "math/rand" + "testing" +) + +func TestNewSourceInterfaces(t *testing.T) { + src := rand.NewSource(42) + if _, ok := src.(rand.Source64); !ok { + t.Fatalf("NewSource returned %T, want Source64", src) + } + + var ( + _ rand.Source = src + _ rand.Source64 = src.(rand.Source64) + ) + + s1 := rand.New(src) + s2 := rand.New(rand.NewSource(42)) + for i := 0; i < 8; i++ { + if got, want := s1.Int63(), s2.Int63(); got != want { + t.Fatalf("Int63 mismatch at step %d: %d vs %d", i, got, want) + } + } +} + +func TestRandMethodRanges(t *testing.T) { + r := rand.New(rand.NewSource(1)) + r.Seed(2) + + if v := r.Int(); v < 0 { + t.Fatalf("Int returned %d", v) + } + if v := r.Int31(); v < 0 { + t.Fatalf("Int31 returned %d", v) + } + if v := r.Int31n(7); v < 0 || v >= 7 { + t.Fatalf("Int31n out of range: %d", v) + } + if v := r.Int63(); v < 0 { + t.Fatalf("Int63 returned %d", v) + } + if v := r.Int63n(13); v < 0 || v >= 13 { + t.Fatalf("Int63n out of range: %d", v) + } + if v := r.Intn(21); v < 0 || v >= 21 { + t.Fatalf("Intn out of range: %d", v) + } + if v := r.Uint32(); v > math.MaxUint32 { + t.Fatalf("Uint32 returned %d", v) + } + if v := r.Uint64(); v > math.MaxUint64 { + t.Fatalf("Uint64 returned %d", v) + } + if v := r.Float32(); !(0 <= v && v < 1) { + t.Fatalf("Float32 returned %f", v) + } + if v := r.Float64(); !(0 <= v && v < 1) { + t.Fatalf("Float64 returned %f", v) + } + if v := r.ExpFloat64(); math.IsNaN(v) || math.IsInf(v, 0) || v <= 0 { + t.Fatalf("ExpFloat64 returned %f", v) + } + if v := r.NormFloat64(); math.IsNaN(v) || math.IsInf(v, 0) { + t.Fatalf("NormFloat64 returned %f", v) + } + + buf := make([]byte, 16) + if n, err := r.Read(buf); err != nil || n != len(buf) { + t.Fatalf("Read() = (%d, %v), want (%d, nil)", n, err, len(buf)) + } +} + +func TestRandPermutationHelpers(t *testing.T) { + r := rand.New(rand.NewSource(4)) + perm := r.Perm(12) + assertPermutation(t, perm, 12) + + values := make([]int, 12) + for i := range values { + values[i] = i + } + r.Shuffle(len(values), func(i, j int) { + values[i], values[j] = values[j], values[i] + }) + assertPermutation(t, values, 12) +} + +func TestZipfGeneration(t *testing.T) { + r := rand.New(rand.NewSource(7)) + z := rand.NewZipf(r, 1.5, 1, 10) + for i := 0; i < 100; i++ { + value := z.Uint64() + if value > 10 { + t.Fatalf("Zipf.Uint64 produced %d, want <= 10", value) + } + } +} + +func TestTopLevelConvenienceFunctions(t *testing.T) { + rand.Seed(123) + + if v := rand.Int(); v < 0 { + t.Fatalf("top-level Int returned %d", v) + } + if v := rand.Int31(); v < 0 { + t.Fatalf("top-level Int31 returned %d", v) + } + if v := rand.Int31n(5); v < 0 || v >= 5 { + t.Fatalf("top-level Int31n out of range: %d", v) + } + if v := rand.Int63(); v < 0 { + t.Fatalf("top-level Int63 returned %d", v) + } + if v := rand.Int63n(9); v < 0 || v >= 9 { + t.Fatalf("top-level Int63n out of range: %d", v) + } + if v := rand.Intn(6); v < 0 || v >= 6 { + t.Fatalf("top-level Intn out of range: %d", v) + } + if v := rand.Uint32(); v > math.MaxUint32 { + t.Fatalf("top-level Uint32 returned %d", v) + } + if v := rand.Uint64(); v > math.MaxUint64 { + t.Fatalf("top-level Uint64 returned %d", v) + } + if v := rand.Float32(); !(0 <= v && v < 1) { + t.Fatalf("top-level Float32 returned %f", v) + } + if v := rand.Float64(); !(0 <= v && v < 1) { + t.Fatalf("top-level Float64 returned %f", v) + } + if v := rand.ExpFloat64(); math.IsNaN(v) || math.IsInf(v, 0) || v <= 0 { + t.Fatalf("top-level ExpFloat64 returned %f", v) + } + if v := rand.NormFloat64(); math.IsNaN(v) || math.IsInf(v, 0) { + t.Fatalf("top-level NormFloat64 returned %f", v) + } + + perm := rand.Perm(8) + assertPermutation(t, perm, 8) + + values := []int{0, 1, 2, 3, 4, 5, 6, 7} + rand.Shuffle(len(values), func(i, j int) { + values[i], values[j] = values[j], values[i] + }) + assertPermutation(t, values, len(values)) + + buf := make([]byte, 24) + if n, err := rand.Read(buf); err != nil || n != len(buf) { + t.Fatalf("top-level Read() = (%d, %v), want (%d, nil)", n, err, len(buf)) + } +} + +func assertPermutation(t testing.TB, values []int, n int) { + t.Helper() + if len(values) != n { + t.Fatalf("expected length %d, got %d", n, len(values)) + } + seen := make([]bool, n) + for _, v := range values { + if v < 0 || v >= n { + t.Fatalf("value %d out of bounds [0,%d)", v, n) + } + if seen[v] { + t.Fatalf("value %d appears more than once", v) + } + seen[v] = true + } +} diff --git a/test/std/math/rand/v2/rand_v2_bench_test.go b/test/std/math/rand/v2/rand_v2_bench_test.go new file mode 100644 index 0000000000..f34e8f0aff --- /dev/null +++ b/test/std/math/rand/v2/rand_v2_bench_test.go @@ -0,0 +1,19 @@ +package randv2_test + +import ( + "math/rand/v2" + "testing" +) + +func BenchmarkRandFloat64(b *testing.B) { + r := rand.New(rand.NewPCG(1, 2)) + for i := 0; i < b.N; i++ { + _ = r.Float64() + } +} + +func BenchmarkUint64(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = rand.Uint64() + } +} diff --git a/test/std/math/rand/v2/rand_v2_test.go b/test/std/math/rand/v2/rand_v2_test.go new file mode 100644 index 0000000000..901d40b669 --- /dev/null +++ b/test/std/math/rand/v2/rand_v2_test.go @@ -0,0 +1,291 @@ +package randv2_test + +import ( + "encoding/hex" + "math" + "math/rand/v2" + "testing" +) + +var ( + _ rand.Source = (*rand.PCG)(nil) + _ rand.Source = (*rand.ChaCha8)(nil) +) + +func newDetRand() *rand.Rand { + return rand.New(rand.NewPCG(1, 2)) +} + +func TestRandDeterministicPCG(t *testing.T) { + r1 := newDetRand() + r2 := newDetRand() + for i := 0; i < 8; i++ { + if got1, got2 := r1.Uint64(), r2.Uint64(); got1 != got2 { + t.Fatalf("Uint64 mismatch at %d: %d vs %d", i, got1, got2) + } + } + + r := newDetRand() + if got := r.Int(); got != 4969059760275911952 { + t.Fatalf("Int got %d", got) + } + if got := r.Int32(); got != 1323786710 { + t.Fatalf("Int32 got %d", got) + } + if got := r.Int64(); got != 5246770554000605320 { + t.Fatalf("Int64 got %d", got) + } + if got := r.Uint64(); got != 14694613213362438554 { + t.Fatalf("Uint64 got %d", got) + } + if got := r.Uint32(); got != 1006208920 { + t.Fatalf("Uint32 got %d", got) + } + if got := r.Float64(); got != 0.3883664855410056 { + t.Fatalf("Float64 got %0.16f", got) + } + if got := r.Float32(); got != 0.97740936 { + t.Fatalf("Float32 got %0.8f", got) + } + if got := r.ExpFloat64(); got != 0.5513632046504593 { + t.Fatalf("ExpFloat64 got %0.16f", got) + } + if got := r.NormFloat64(); got != -0.11707238034168332 { + t.Fatalf("NormFloat64 got %0.17f", got) + } + if got := r.Int32N(10); got != 1 { + t.Fatalf("Int32N got %d", got) + } + if got := r.Int64N(100); got != 66 { + t.Fatalf("Int64N got %d", got) + } + if got := r.IntN(7); got != 3 { + t.Fatalf("IntN got %d", got) + } + if got := r.Uint32N(15); got != 14 { + t.Fatalf("Uint32N got %d", got) + } + if got := r.Uint64N(123); got != 14 { + t.Fatalf("Uint64N got %d", got) + } + if got := r.UintN(21); got != 8 { + t.Fatalf("UintN got %d", got) + } + if got := r.Uint(); got != 14454429957748299131 { + t.Fatalf("Uint got %d", got) + } + if got := r.Int(); got != 4257872588489500903 { + t.Fatalf("Int second got %d", got) + } +} + +func TestRandPermAndShuffle(t *testing.T) { + r := newDetRand() + if got, want := r.Perm(6), []int{1, 5, 2, 0, 3, 4}; !equalIntSlice(got, want) { + t.Fatalf("Perm=%v want=%v", got, want) + } + r = newDetRand() + vals := []int{0, 1, 2, 3, 4} + r.Shuffle(len(vals), func(i, j int) { vals[i], vals[j] = vals[j], vals[i] }) + if got, want := vals, []int{1, 4, 2, 0, 3}; !equalIntSlice(got, want) { + t.Fatalf("Shuffle=%v want=%v", got, want) + } + + perm := rand.Perm(10) + if len(perm) != 10 { + t.Fatalf("rand.Perm length=%d", len(perm)) + } + seen := make(map[int]bool) + for _, v := range perm { + if v < 0 || v >= 10 { + t.Fatalf("Perm value out of range %d", v) + } + if seen[v] { + t.Fatalf("duplicate value %d", v) + } + seen[v] = true + } + + data := []int{0, 1, 2} + rand.Shuffle(len(data), func(i, j int) { data[i], data[j] = data[j], data[i] }) + if len(data) != 3 { + t.Fatalf("Shuffle altered length %d", len(data)) + } +} + +func TestTopLevelFunctionsBounds(t *testing.T) { + if v := rand.Float32(); !(v >= 0 && v < 1) { + t.Fatalf("Float32=%f", v) + } + if v := rand.Float64(); !(v >= 0 && v < 1) { + t.Fatalf("Float64=%f", v) + } + if v := rand.Int32(); v == 0 { + // value may be zero, but calling ensures coverage + } + if v := rand.Int64(); v == 0 { + // likewise + } + if v := rand.Int32N(5); v < 0 || v >= 5 { + t.Fatalf("Int32N=%d", v) + } + if v := rand.Int64N(7); v < 0 || v >= 7 { + t.Fatalf("Int64N=%d", v) + } + if v := rand.IntN(9); v < 0 || v >= 9 { + t.Fatalf("IntN=%d", v) + } + if v := rand.Uint32(); v == 0 { + // ensure call exercised + } + if v := rand.Uint32N(11); v >= 11 { + t.Fatalf("Uint32N=%d", v) + } + if v := rand.Uint64N(13); v >= 13 { + t.Fatalf("Uint64N=%d", v) + } + if v := rand.UintN(15); v >= 15 { + t.Fatalf("UintN=%d", v) + } + if rand.ExpFloat64() <= 0 { + t.Fatal("ExpFloat64 not positive") + } + if math.IsNaN(rand.NormFloat64()) { + t.Fatal("NormFloat64 NaN") + } + if rand.Uint() == 0 && rand.Uint() == 0 { + // extremely unlikely but check not constant + t.Fatal("Uint appears stuck at zero") + } + if rand.Int() == rand.Int() { + // possible but astronomically unlikely; mitigate by retry + if rand.Int() == rand.Int() { + t.Fatal("Int produced identical values repeatedly") + } + } +} + +func TestGenericN(t *testing.T) { + if v := rand.N[uint16](10); v >= 10 { + t.Fatalf("N uint16=%d", v) + } + if v := rand.N[int8](12); v < 0 || v >= 12 { + t.Fatalf("N int8=%d", v) + } +} + +func TestZipfDeterministic(t *testing.T) { + r1 := rand.New(rand.NewPCG(1, 2)) + r2 := rand.New(rand.NewPCG(1, 2)) + z1 := rand.NewZipf(r1, 1.5, 1.0, 10) + z2 := rand.NewZipf(r2, 1.5, 1.0, 10) + for i := 0; i < 5; i++ { + v1, v2 := z1.Uint64(), z2.Uint64() + if v1 != v2 { + t.Fatalf("Zipf mismatch %d: %d vs %d", i, v1, v2) + } + if v1 > 10 { + t.Fatalf("Zipf value out of range %d", v1) + } + } +} + +func TestPCGMarshal(t *testing.T) { + p := rand.NewPCG(1, 2) + if got := p.Uint64(); got != 14192431797130687760 { + t.Fatalf("first Uint64=%d", got) + } + state, err := p.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary err %v", err) + } + if len(state) != 20 { + t.Fatalf("encoded length=%d", len(state)) + } + if hex.EncodeToString(state) != "7063673ae299ad9c2bef30ba9b113a4837016dd9" { + t.Fatalf("state hex=%s", hex.EncodeToString(state)) + } + var copy rand.PCG + if err := copy.UnmarshalBinary(state); err != nil { + t.Fatalf("UnmarshalBinary err %v", err) + } + if got := p.Uint64(); got != 11371241257079532652 { + t.Fatalf("post-marsh original=%d", got) + } + if got := copy.Uint64(); got != 11371241257079532652 { + t.Fatalf("post-unmarshal copy=%d", got) + } + buf := []byte{9} + out, err := copy.AppendBinary(buf) + if err != nil { + t.Fatalf("AppendBinary err %v", err) + } + if len(out) != 21 { + t.Fatalf("append length=%d", len(out)) + } + var zero rand.PCG + var zeroNew = rand.NewPCG(0, 0) + if zero.Uint64() != zeroNew.Uint64() { + t.Fatal("zero PCG and NewPCG(0,0) differ") + } + zero.Seed(5, 6) + if v := zero.Uint64(); v == 0 { + t.Fatal("Seed did not affect generator") + } +} + +func TestChaCha8State(t *testing.T) { + var seed [32]byte + for i := range seed { + seed[i] = byte(i) + } + c := rand.NewChaCha8(seed) + if got := c.Uint64(); got != 12537355132343524571 { + t.Fatalf("Uint64=%d", got) + } + buf := make([]byte, 16) + if n, err := c.Read(buf); err != nil || n != len(buf) { + t.Fatalf("Read returned n=%d err=%v", n, err) + } + hexBuf := hex.EncodeToString(buf) + if hexBuf != "ddfe0e8df43bb39a00ae8a375a4e9826" { + t.Fatalf("Read bytes=%s", hexBuf) + } + state, err := c.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary err %v", err) + } + if len(state) != 48 { + t.Fatalf("state length=%d", len(state)) + } + var c2 rand.ChaCha8 + if err := c2.UnmarshalBinary(state); err != nil { + t.Fatalf("UnmarshalBinary err %v", err) + } + if got := c2.Uint64(); got != 8844178409879327515 { + t.Fatalf("Uint64 after unmarshal=%d", got) + } + appended, err := c2.AppendBinary([]byte{1, 2}) + if err != nil { + t.Fatalf("AppendBinary err %v", err) + } + if len(appended) != 50 { + t.Fatalf("append len=%d", len(appended)) + } + c2.Seed(seed) + if got := c2.Uint64(); got != 12537355132343524571 { + t.Fatalf("Uint64 after Seed=%d", got) + } +} + +func equalIntSlice(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} diff --git a/test/std/mime/mime_test.go b/test/std/mime/mime_test.go new file mode 100644 index 0000000000..7d0f1788aa --- /dev/null +++ b/test/std/mime/mime_test.go @@ -0,0 +1,227 @@ +package mime_test + +import ( + "mime" + "strings" + "testing" +) + +func TestTypeByExtension(t *testing.T) { + tests := []struct { + ext string + want string + }{ + {".html", "text/html"}, + {".txt", "text/plain"}, + {".jpg", "image/jpeg"}, + {".png", "image/png"}, + {".pdf", "application/pdf"}, + {".json", "application/json"}, + } + + for _, tt := range tests { + got := mime.TypeByExtension(tt.ext) + if !strings.HasPrefix(got, tt.want) { + t.Errorf("TypeByExtension(%q) = %q, want prefix %q", tt.ext, got, tt.want) + } + } +} + +func TestTypeByExtensionUnknown(t *testing.T) { + got := mime.TypeByExtension(".unknownextension123") + if got != "" { + t.Errorf("TypeByExtension(.unknownextension123) = %q, want empty", got) + } +} + +func TestAddExtensionType(t *testing.T) { + err := mime.AddExtensionType(".test", "application/test") + if err != nil { + t.Fatalf("AddExtensionType() error = %v", err) + } + + got := mime.TypeByExtension(".test") + if !strings.HasPrefix(got, "application/test") { + t.Errorf("After AddExtensionType, TypeByExtension(.test) = %q, want prefix %q", got, "application/test") + } +} + +func TestAddExtensionTypeError(t *testing.T) { + err := mime.AddExtensionType("noperiod", "application/test") + if err == nil { + t.Error("AddExtensionType with extension missing period should error") + } +} + +func TestFormatMediaType(t *testing.T) { + tests := []struct { + typ string + params map[string]string + want string + }{ + {"text/plain", nil, "text/plain"}, + {"text/plain", map[string]string{"charset": "utf-8"}, "text/plain; charset=utf-8"}, + {"multipart/form-data", map[string]string{"boundary": "----WebKitFormBoundary"}, "multipart/form-data; boundary=----WebKitFormBoundary"}, + } + + for _, tt := range tests { + got := mime.FormatMediaType(tt.typ, tt.params) + if got != tt.want { + t.Errorf("FormatMediaType(%q, %v) = %q, want %q", tt.typ, tt.params, got, tt.want) + } + } +} + +func TestParseMediaType(t *testing.T) { + tests := []struct { + input string + wantType string + wantParams map[string]string + wantErr bool + }{ + { + "text/plain", + "text/plain", + map[string]string{}, + false, + }, + { + "text/plain; charset=utf-8", + "text/plain", + map[string]string{"charset": "utf-8"}, + false, + }, + { + "text/html; charset=UTF-8", + "text/html", + map[string]string{"charset": "UTF-8"}, + false, + }, + { + "multipart/form-data; boundary=----WebKitFormBoundary", + "multipart/form-data", + map[string]string{"boundary": "----WebKitFormBoundary"}, + false, + }, + } + + for _, tt := range tests { + gotType, gotParams, err := mime.ParseMediaType(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("ParseMediaType(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr) + continue + } + if gotType != tt.wantType { + t.Errorf("ParseMediaType(%q) type = %q, want %q", tt.input, gotType, tt.wantType) + } + if len(gotParams) != len(tt.wantParams) { + t.Errorf("ParseMediaType(%q) params = %v, want %v", tt.input, gotParams, tt.wantParams) + continue + } + for k, v := range tt.wantParams { + if gotParams[k] != v { + t.Errorf("ParseMediaType(%q) params[%q] = %q, want %q", tt.input, k, gotParams[k], v) + } + } + } +} + +func TestExtensionsByType(t *testing.T) { + exts, err := mime.ExtensionsByType("text/html") + if err != nil { + t.Fatalf("ExtensionsByType(text/html) error = %v", err) + } + if len(exts) == 0 { + t.Error("ExtensionsByType(text/html) returned no extensions") + } + found := false + for _, ext := range exts { + if ext == ".html" || ext == ".htm" { + found = true + break + } + } + if !found { + t.Errorf("ExtensionsByType(text/html) = %v, want to include .html or .htm", exts) + } +} + +func TestWordEncoder(t *testing.T) { + tests := []struct { + encoder mime.WordEncoder + input string + }{ + {mime.BEncoding, "hello"}, + {mime.QEncoding, "hello"}, + {mime.BEncoding, "测试"}, + } + + for _, tt := range tests { + encoded := tt.encoder.Encode("utf-8", tt.input) + if encoded == "" { + t.Errorf("WordEncoder.Encode(%q) returned empty string", tt.input) + } + } + + nonASCII := mime.BEncoding.Encode("utf-8", "测试") + if !strings.Contains(nonASCII, "=?utf-8?") { + t.Errorf("WordEncoder.Encode with non-ASCII should encode, got %q", nonASCII) + } +} + +func TestWordDecoder(t *testing.T) { + var dec mime.WordDecoder + + tests := []struct { + input string + want string + }{ + {"=?utf-8?q?hello?=", "hello"}, + {"=?utf-8?b?aGVsbG8=?=", "hello"}, + } + + for _, tt := range tests { + got, err := dec.Decode(tt.input) + if err != nil { + t.Errorf("WordDecoder.Decode(%q) error = %v", tt.input, err) + continue + } + if got != tt.want { + t.Errorf("WordDecoder.Decode(%q) = %q, want %q", tt.input, got, tt.want) + } + } +} + +func TestWordDecoderDecodeHeader(t *testing.T) { + var dec mime.WordDecoder + + header := "=?utf-8?q?Hello?= World" + got, err := dec.DecodeHeader(header) + if err != nil { + t.Fatalf("WordDecoder.DecodeHeader(%q) error = %v", header, err) + } + if got != "Hello World" { + t.Errorf("WordDecoder.DecodeHeader(%q) = %q, want %q", header, got, "Hello World") + } +} + +func TestErrInvalidMediaParameter(t *testing.T) { + if mime.ErrInvalidMediaParameter == nil { + t.Error("ErrInvalidMediaParameter should not be nil") + } + if mime.ErrInvalidMediaParameter.Error() == "" { + t.Error("ErrInvalidMediaParameter.Error() should not be empty") + } +} + +func TestBEncoding(t *testing.T) { + if mime.BEncoding != 'b' { + t.Errorf("BEncoding = %q, want 'b'", mime.BEncoding) + } +} + +func TestQEncoding(t *testing.T) { + if mime.QEncoding != 'q' { + t.Errorf("QEncoding = %q, want 'q'", mime.QEncoding) + } +} diff --git a/test/std/mime/multipart/go126_symbols_test.go b/test/std/mime/multipart/go126_symbols_test.go new file mode 100644 index 0000000000..58c8382106 --- /dev/null +++ b/test/std/mime/multipart/go126_symbols_test.go @@ -0,0 +1,20 @@ +//go:build go1.26 + +package multipart_test + +import ( + "mime" + "mime/multipart" + "testing" +) + +func TestFileContentDisposition(t *testing.T) { + value := multipart.FileContentDisposition("upload", `report "final".txt`) + mediaType, params, err := mime.ParseMediaType(value) + if err != nil { + t.Fatal(err) + } + if mediaType != "form-data" || params["name"] != "upload" || params["filename"] != `report "final".txt` { + t.Fatalf("FileContentDisposition = %q, parsed as %q, %#v", value, mediaType, params) + } +} diff --git a/test/std/mime/multipart/multipart_test.go b/test/std/mime/multipart/multipart_test.go new file mode 100644 index 0000000000..dd0a9af7ba --- /dev/null +++ b/test/std/mime/multipart/multipart_test.go @@ -0,0 +1,378 @@ +package multipart_test + +import ( + "bytes" + "io" + "mime/multipart" + "strings" + "testing" +) + +func TestNewWriter(t *testing.T) { + var buf bytes.Buffer + w := multipart.NewWriter(&buf) + if w == nil { + t.Fatal("NewWriter returned nil") + } + + boundary := w.Boundary() + if boundary == "" { + t.Error("Boundary is empty") + } +} + +func TestWriterSetBoundary(t *testing.T) { + var buf bytes.Buffer + w := multipart.NewWriter(&buf) + + err := w.SetBoundary("test-boundary-123") + if err != nil { + t.Fatalf("SetBoundary error: %v", err) + } + + if w.Boundary() != "test-boundary-123" { + t.Errorf("Boundary = %q, want %q", w.Boundary(), "test-boundary-123") + } +} + +func TestWriterCreatePart(t *testing.T) { + var buf bytes.Buffer + w := multipart.NewWriter(&buf) + + header := make(map[string][]string) + header["Content-Type"] = []string{"text/plain"} + + part, err := w.CreatePart(header) + if err != nil { + t.Fatalf("CreatePart error: %v", err) + } + + _, err = part.Write([]byte("test data")) + if err != nil { + t.Fatalf("Write to part error: %v", err) + } + + err = w.Close() + if err != nil { + t.Fatalf("Close error: %v", err) + } + + if !strings.Contains(buf.String(), "test data") { + t.Error("Output doesn't contain test data") + } +} + +func TestWriterCreateFormField(t *testing.T) { + var buf bytes.Buffer + w := multipart.NewWriter(&buf) + + part, err := w.CreateFormField("fieldname") + if err != nil { + t.Fatalf("CreateFormField error: %v", err) + } + + _, err = part.Write([]byte("field value")) + if err != nil { + t.Fatalf("Write error: %v", err) + } + + err = w.Close() + if err != nil { + t.Fatalf("Close error: %v", err) + } + + if !strings.Contains(buf.String(), "fieldname") { + t.Error("Output doesn't contain field name") + } + if !strings.Contains(buf.String(), "field value") { + t.Error("Output doesn't contain field value") + } +} + +func TestWriterCreateFormFile(t *testing.T) { + var buf bytes.Buffer + w := multipart.NewWriter(&buf) + + part, err := w.CreateFormFile("file", "test.txt") + if err != nil { + t.Fatalf("CreateFormFile error: %v", err) + } + + _, err = part.Write([]byte("file content")) + if err != nil { + t.Fatalf("Write error: %v", err) + } + + err = w.Close() + if err != nil { + t.Fatalf("Close error: %v", err) + } + + output := buf.String() + if !strings.Contains(output, "file") { + t.Error("Output doesn't contain field name") + } + if !strings.Contains(output, "test.txt") { + t.Error("Output doesn't contain filename") + } + if !strings.Contains(output, "file content") { + t.Error("Output doesn't contain file content") + } +} + +func TestNewReader(t *testing.T) { + body := "--boundary\r\n" + + "Content-Type: text/plain\r\n\r\n" + + "data\r\n" + + "--boundary--\r\n" + + r := multipart.NewReader(strings.NewReader(body), "boundary") + if r == nil { + t.Fatal("NewReader returned nil") + } +} + +func TestReaderNextPart(t *testing.T) { + body := "--boundary\r\n" + + "Content-Type: text/plain\r\n\r\n" + + "test data\r\n" + + "--boundary--\r\n" + + r := multipart.NewReader(strings.NewReader(body), "boundary") + + part, err := r.NextPart() + if err != nil { + t.Fatalf("NextPart error: %v", err) + } + + data, err := io.ReadAll(part) + if err != nil { + t.Fatalf("ReadAll error: %v", err) + } + + if string(data) != "test data" { + t.Errorf("Part data = %q, want %q", data, "test data") + } + + _, err = r.NextPart() + if err != io.EOF { + t.Errorf("Expected EOF, got %v", err) + } +} + +func TestReaderNextRawPart(t *testing.T) { + body := "--boundary\r\n" + + "Content-Type: text/plain\r\n\r\n" + + "raw data\r\n" + + "--boundary--\r\n" + + r := multipart.NewReader(strings.NewReader(body), "boundary") + + part, err := r.NextRawPart() + if err != nil { + t.Fatalf("NextRawPart error: %v", err) + } + + data, err := io.ReadAll(part) + if err != nil { + t.Fatalf("ReadAll error: %v", err) + } + + if !strings.Contains(string(data), "raw data") { + t.Errorf("Part data doesn't contain expected content") + } +} + +func TestReaderReadForm(t *testing.T) { + var buf bytes.Buffer + w := multipart.NewWriter(&buf) + + field, _ := w.CreateFormField("name") + field.Write([]byte("value")) + + w.Close() + + r := multipart.NewReader(&buf, w.Boundary()) + form, err := r.ReadForm(1024) + if err != nil { + t.Fatalf("ReadForm error: %v", err) + } + defer form.RemoveAll() + + if len(form.Value) != 1 { + t.Errorf("Form.Value length = %d, want 1", len(form.Value)) + } + + if form.Value["name"][0] != "value" { + t.Errorf("Form.Value[name][0] = %q, want %q", form.Value["name"][0], "value") + } +} + +func TestPartFormName(t *testing.T) { + body := "--boundary\r\n" + + "Content-Disposition: form-data; name=\"fieldname\"\r\n\r\n" + + "data\r\n" + + "--boundary--\r\n" + + r := multipart.NewReader(strings.NewReader(body), "boundary") + part, err := r.NextPart() + if err != nil { + t.Fatalf("NextPart error: %v", err) + } + + name := part.FormName() + if name != "fieldname" { + t.Errorf("FormName = %q, want %q", name, "fieldname") + } +} + +func TestPartFileName(t *testing.T) { + body := "--boundary\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\r\n\r\n" + + "data\r\n" + + "--boundary--\r\n" + + r := multipart.NewReader(strings.NewReader(body), "boundary") + part, err := r.NextPart() + if err != nil { + t.Fatalf("NextPart error: %v", err) + } + + filename := part.FileName() + if filename != "test.txt" { + t.Errorf("FileName = %q, want %q", filename, "test.txt") + } +} + +func TestFileHeader(t *testing.T) { + fh := &multipart.FileHeader{ + Filename: "test.txt", + Size: 100, + } + + if fh.Filename != "test.txt" { + t.Errorf("Filename = %q, want %q", fh.Filename, "test.txt") + } + if fh.Size != 100 { + t.Errorf("Size = %d, want 100", fh.Size) + } +} + +func TestForm(t *testing.T) { + form := &multipart.Form{ + Value: make(map[string][]string), + File: make(map[string][]*multipart.FileHeader), + } + + form.Value["field"] = []string{"value"} + + if len(form.Value) != 1 { + t.Errorf("Form.Value length = %d, want 1", len(form.Value)) + } +} + +func TestErrMessageTooLarge(t *testing.T) { + if multipart.ErrMessageTooLarge == nil { + t.Error("ErrMessageTooLarge should not be nil") + } +} + +func TestFileHeaderOpen(t *testing.T) { + var buf bytes.Buffer + w := multipart.NewWriter(&buf) + + part, err := w.CreateFormFile("file", "test.txt") + if err != nil { + t.Fatalf("CreateFormFile error: %v", err) + } + part.Write([]byte("file content")) + w.Close() + + r := multipart.NewReader(&buf, w.Boundary()) + form, err := r.ReadForm(1024) + if err != nil { + t.Fatalf("ReadForm error: %v", err) + } + defer form.RemoveAll() + + if len(form.File["file"]) == 0 { + t.Fatal("No file found") + } + + fh := form.File["file"][0] + f, err := fh.Open() + if err != nil { + t.Fatalf("FileHeader.Open error: %v", err) + } + defer f.Close() + + data, err := io.ReadAll(f) + if err != nil { + t.Fatalf("ReadAll error: %v", err) + } + if string(data) != "file content" { + t.Errorf("File content = %q, want %q", data, "file content") + } + + var _ multipart.File = f +} + +func TestPartCloseAndRead(t *testing.T) { + body := "--boundary\r\n" + + "Content-Type: text/plain\r\n\r\n" + + "test data\r\n" + + "--boundary--\r\n" + + r := multipart.NewReader(strings.NewReader(body), "boundary") + part, err := r.NextPart() + if err != nil { + t.Fatalf("NextPart error: %v", err) + } + + buf := make([]byte, 4) + n, err := part.Read(buf) + if err != nil && err != io.EOF { + t.Fatalf("Part.Read error: %v", err) + } + if n == 0 { + t.Error("Part.Read returned 0 bytes") + } + + err = part.Close() + if err != nil { + t.Fatalf("Part.Close error: %v", err) + } +} + +func TestWriterFormDataContentType(t *testing.T) { + var buf bytes.Buffer + w := multipart.NewWriter(&buf) + + contentType := w.FormDataContentType() + if !strings.Contains(contentType, "multipart/form-data") { + t.Errorf("FormDataContentType = %q, should contain 'multipart/form-data'", contentType) + } + if !strings.Contains(contentType, w.Boundary()) { + t.Errorf("FormDataContentType = %q, should contain boundary %q", contentType, w.Boundary()) + } +} + +func TestWriterWriteField(t *testing.T) { + var buf bytes.Buffer + w := multipart.NewWriter(&buf) + + err := w.WriteField("fieldname", "fieldvalue") + if err != nil { + t.Fatalf("WriteField error: %v", err) + } + + w.Close() + + output := buf.String() + if !strings.Contains(output, "fieldname") { + t.Error("Output doesn't contain field name") + } + if !strings.Contains(output, "fieldvalue") { + t.Error("Output doesn't contain field value") + } +} diff --git a/test/std/mime/quotedprintable/quotedprintable_test.go b/test/std/mime/quotedprintable/quotedprintable_test.go new file mode 100644 index 0000000000..7c33ebd5f3 --- /dev/null +++ b/test/std/mime/quotedprintable/quotedprintable_test.go @@ -0,0 +1,154 @@ +package quotedprintable_test + +import ( + "bytes" + "io" + "mime/quotedprintable" + "strings" + "testing" +) + +func TestQuotedPrintableRoundTrip(t *testing.T) { + testCases := []struct { + name string + input string + }{ + {"simple", "hello world"}, + {"with spaces", "hello world"}, + {"with equals", "1=2"}, + {"long line", strings.Repeat("a", 100)}, + {"empty", ""}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var buf bytes.Buffer + + w := quotedprintable.NewWriter(&buf) + _, err := w.Write([]byte(tc.input)) + if err != nil { + t.Fatalf("Write failed: %v", err) + } + + err = w.Close() + if err != nil { + t.Fatalf("Close failed: %v", err) + } + + r := quotedprintable.NewReader(&buf) + output, err := io.ReadAll(r) + if err != nil { + t.Fatalf("ReadAll failed: %v", err) + } + + if string(output) != tc.input { + t.Errorf("Round trip failed: got %q, want %q", output, tc.input) + } + }) + } +} + +func TestReaderRead(t *testing.T) { + input := "hello=20world" + r := quotedprintable.NewReader(strings.NewReader(input)) + + buf := make([]byte, 20) + n, err := r.Read(buf) + if err != nil && err != io.EOF { + t.Fatalf("Read failed: %v", err) + } + + result := string(buf[:n]) + expected := "hello world" + if result != expected { + t.Errorf("Read: got %q, want %q", result, expected) + } +} + +func TestWriterWrite(t *testing.T) { + var buf bytes.Buffer + w := quotedprintable.NewWriter(&buf) + + input := "test data with special=chars" + n, err := w.Write([]byte(input)) + if err != nil { + t.Fatalf("Write failed: %v", err) + } + + if n != len(input) { + t.Errorf("Write returned %d, want %d", n, len(input)) + } + + w.Close() + + if buf.Len() == 0 { + t.Error("Writer produced no output") + } +} + +func TestWriterBinary(t *testing.T) { + var buf bytes.Buffer + w := quotedprintable.NewWriter(&buf) + w.Binary = true + + input := "binary\r\ndata" + w.Write([]byte(input)) + w.Close() + + r := quotedprintable.NewReader(&buf) + output, err := io.ReadAll(r) + if err != nil { + t.Fatalf("ReadAll failed: %v", err) + } + + if string(output) != input { + t.Errorf("Binary mode: got %q, want %q", output, input) + } +} + +func TestReaderInvalidEncoding(t *testing.T) { + invalidInputs := []string{ + "hello=ZZ", + "test=", + "invalid=GG", + } + + for i, input := range invalidInputs { + t.Run("", func(t *testing.T) { + r := quotedprintable.NewReader(strings.NewReader(input)) + output, err := io.ReadAll(r) + if err != nil { + t.Logf("Test %d: Got error as expected: %v", i, err) + } else { + t.Logf("Test %d: No error, output: %q", i, output) + } + }) + } +} + +func TestEmptyInput(t *testing.T) { + r := quotedprintable.NewReader(strings.NewReader("")) + output, err := io.ReadAll(r) + if err != nil { + t.Fatalf("ReadAll failed: %v", err) + } + + if len(output) != 0 { + t.Errorf("Empty input: got %d bytes, want 0", len(output)) + } +} + +func TestSoftLineBreak(t *testing.T) { + input := "long line that needs to be wrapped=\r\ncontinued here" + expected := "long line that needs to be wrappedcontinued here" + + r := quotedprintable.NewReader(strings.NewReader(input)) + output, err := io.ReadAll(r) + if err != nil { + t.Fatalf("ReadAll failed: %v", err) + } + + if string(output) != expected { + t.Errorf("Soft line break: got %q, want %q", output, expected) + } +} diff --git a/test/std/net/go126_symbols_test.go b/test/std/net/go126_symbols_test.go new file mode 100644 index 0000000000..14d42e6928 --- /dev/null +++ b/test/std/net/go126_symbols_test.go @@ -0,0 +1,120 @@ +//go:build go1.26 + +package net_test + +import ( + "context" + "net" + "net/netip" + "os" + "path/filepath" + "strings" + "testing" + "time" +) + +func TestDialerTypedNetworkMethods(t *testing.T) { + t.Run("TCP", func(t *testing.T) { + listener, err := net.ListenTCP("tcp4", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1)}) + if err != nil { + t.Fatal(err) + } + defer listener.Close() + + accepted := make(chan error, 1) + go func() { + connection, err := listener.AcceptTCP() + if err == nil { + defer connection.Close() + buffer := make([]byte, 4) + _, err = connection.Read(buffer) + if err == nil && string(buffer) != "ping" { + err = &net.AddrError{Err: "unexpected TCP payload", Addr: string(buffer)} + } + } + accepted <- err + }() + + remote := listener.Addr().(*net.TCPAddr).AddrPort() + connection, err := new(net.Dialer).DialTCP(t.Context(), "tcp4", netip.AddrPort{}, remote) + if err != nil { + t.Fatal(err) + } + if _, err := connection.Write([]byte("ping")); err != nil { + t.Fatal(err) + } + connection.Close() + if err := <-accepted; err != nil { + t.Fatal(err) + } + }) + + t.Run("UDP", func(t *testing.T) { + listener, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)}) + if err != nil { + t.Fatal(err) + } + defer listener.Close() + listener.SetReadDeadline(time.Now().Add(5 * time.Second)) + + remote := listener.LocalAddr().(*net.UDPAddr).AddrPort() + connection, err := new(net.Dialer).DialUDP(t.Context(), "udp4", netip.AddrPort{}, remote) + if err != nil { + t.Fatal(err) + } + defer connection.Close() + if _, err := connection.Write([]byte("datagram")); err != nil { + t.Fatal(err) + } + buffer := make([]byte, 16) + n, _, err := listener.ReadFromUDP(buffer) + if err != nil || string(buffer[:n]) != "datagram" { + t.Fatalf("ReadFromUDP = %q, %v; want datagram, nil", buffer[:n], err) + } + }) + + t.Run("Unix", func(t *testing.T) { + directory, err := os.MkdirTemp("/tmp", "llgo-net-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(directory) + path := filepath.Join(directory, "listener.sock") + address := &net.UnixAddr{Name: path, Net: "unix"} + listener, err := net.ListenUnix("unix", address) + if err != nil { + t.Fatal(err) + } + defer listener.Close() + + accepted := make(chan error, 1) + go func() { + connection, err := listener.AcceptUnix() + if err == nil { + connection.Close() + } + accepted <- err + }() + connection, err := new(net.Dialer).DialUnix(t.Context(), "unix", nil, address) + if err != nil { + t.Fatal(err) + } + connection.Close() + if err := <-accepted; err != nil { + t.Fatal(err) + } + }) + + t.Run("IPError", func(t *testing.T) { + ctx, cancel := context.WithCancel(t.Context()) + cancel() + connection, err := new(net.Dialer).DialIP(ctx, "not-an-ip-network", netip.Addr{}, netip.Addr{}) + if connection != nil { + connection.Close() + t.Fatal("DialIP returned a connection for an invalid network") + } + if err == nil || (!strings.Contains(err.Error(), "unknown network") && !strings.Contains(err.Error(), "canceled")) { + t.Fatalf("DialIP error = %v, want an invalid-network or canceled error", err) + } + }) +} diff --git a/test/std/net/http/cgi/cgi_test.go b/test/std/net/http/cgi/cgi_test.go new file mode 100644 index 0000000000..a87d50477d --- /dev/null +++ b/test/std/net/http/cgi/cgi_test.go @@ -0,0 +1,83 @@ +package cgi_test + +import ( + "net/http/cgi" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestRequestFromMap(t *testing.T) { + req, err := cgi.RequestFromMap(map[string]string{ + "REQUEST_METHOD": "POST", + "SERVER_PROTOCOL": "HTTP/1.1", + "HTTP_HOST": "example.com", + "REQUEST_URI": "/cgi-bin/app?x=1&y=2", + "SCRIPT_NAME": "/cgi-bin/app", + "QUERY_STRING": "x=1&y=2", + "REMOTE_ADDR": "127.0.0.1", + }) + if err != nil { + t.Fatalf("RequestFromMap: %v", err) + } + if req.Method != "POST" { + t.Fatalf("Method = %q, want POST", req.Method) + } + if req.URL.Path != "/cgi-bin/app" { + t.Fatalf("URL.Path = %q, want /cgi-bin/app", req.URL.Path) + } + if req.URL.RawQuery != "x=1&y=2" { + t.Fatalf("URL.RawQuery = %q, want x=1&y=2", req.URL.RawQuery) + } + if req.Host != "example.com" { + t.Fatalf("Host = %q, want example.com", req.Host) + } +} + +func TestRequestWithoutCGIEnv(t *testing.T) { + t.Setenv("REQUEST_METHOD", "") + if _, err := cgi.Request(); err == nil { + t.Fatal("expected cgi.Request to fail without CGI environment") + } +} + +func TestPublicAPISymbols(t *testing.T) { + _ = cgi.Request + _ = cgi.RequestFromMap + _ = cgi.Serve + + _ = cgi.Handler{} +} + +func TestHandlerServeHTTP(t *testing.T) { + dir := t.TempDir() + script := filepath.Join(dir, "app.sh") + content := "#!/bin/sh\n" + + "echo \"Status: 200 OK\"\n" + + "echo \"Content-Type: text/plain\"\n" + + "echo\n" + + "echo \"method=$REQUEST_METHOD query=$QUERY_STRING\"\n" + if err := os.WriteFile(script, []byte(content), 0o755); err != nil { + t.Fatalf("WriteFile script: %v", err) + } + + h := &cgi.Handler{ + Path: script, + Root: "/cgi-bin", + Dir: dir, + } + req := httptest.NewRequest("GET", "http://example.com/cgi-bin/app.sh?x=1&y=2", nil) + w := httptest.NewRecorder() + h.ServeHTTP(w, req) + + res := w.Result() + if res.StatusCode != 200 { + t.Fatalf("status = %d, want 200", res.StatusCode) + } + body := w.Body.String() + if !strings.Contains(body, "method=GET") || !strings.Contains(body, "query=x=1&y=2") { + t.Fatalf("unexpected body: %q", body) + } +} diff --git a/test/std/net/http/cookiejar/cookiejar_test.go b/test/std/net/http/cookiejar/cookiejar_test.go new file mode 100644 index 0000000000..d0828c1b58 --- /dev/null +++ b/test/std/net/http/cookiejar/cookiejar_test.go @@ -0,0 +1,67 @@ +package cookiejar_test + +import ( + "net/http" + "net/http/cookiejar" + "net/url" + "strings" + "testing" +) + +type dummyPSL struct{} + +func (dummyPSL) PublicSuffix(domain string) string { + if strings.HasSuffix(domain, ".co.uk") { + return "co.uk" + } + if i := strings.LastIndexByte(domain, '.'); i >= 0 { + return domain[i+1:] + } + return "" +} + +func (dummyPSL) String() string { return "dummy-psl" } + +func mustURL(t *testing.T, s string) *url.URL { + t.Helper() + u, err := url.Parse(s) + if err != nil { + t.Fatalf("Parse(%q) failed: %v", s, err) + } + return u +} + +func TestNewSetCookiesAndCookies(t *testing.T) { + var _ cookiejar.PublicSuffixList = dummyPSL{} + + jar, err := cookiejar.New(nil) + if err != nil { + t.Fatalf("New(nil) failed: %v", err) + } + var _ http.CookieJar = jar + + opts := &cookiejar.Options{PublicSuffixList: dummyPSL{}} + jar2, err := cookiejar.New(opts) + if err != nil { + t.Fatalf("New(opts) failed: %v", err) + } + + u := mustURL(t, "https://sub.example.com/path") + jar2.SetCookies(u, []*http.Cookie{ + {Name: "sid", Value: "abc", Path: "/"}, + }) + got := jar2.Cookies(u) + if len(got) != 1 { + t.Fatalf("Cookies len=%d, want 1", len(got)) + } + if got[0].Name != "sid" || got[0].Value != "abc" { + t.Fatalf("Cookies[0] = %+v", got[0]) + } + + // Non-HTTP(S) schemes should be ignored by SetCookies and Cookies. + ftpURL := mustURL(t, "ftp://sub.example.com/path") + jar.SetCookies(ftpURL, []*http.Cookie{{Name: "x", Value: "1"}}) + if c := jar.Cookies(ftpURL); len(c) != 0 { + t.Fatalf("Cookies(ftp) len=%d, want 0", len(c)) + } +} diff --git a/test/std/net/http/fcgi/fcgi_test.go b/test/std/net/http/fcgi/fcgi_test.go new file mode 100644 index 0000000000..80c044141b --- /dev/null +++ b/test/std/net/http/fcgi/fcgi_test.go @@ -0,0 +1,47 @@ +package fcgi_test + +import ( + "errors" + "net" + "net/http" + "net/http/fcgi" + "testing" +) + +type errListener struct{} + +func (errListener) Accept() (net.Conn, error) { return nil, errors.New("accept failed") } +func (errListener) Close() error { return nil } +func (errListener) Addr() net.Addr { return &net.TCPAddr{} } + +func TestProcessEnvDefault(t *testing.T) { + req, err := http.NewRequest(http.MethodGet, "http://example.com/", nil) + if err != nil { + t.Fatalf("NewRequest: %v", err) + } + if env := fcgi.ProcessEnv(req); env != nil { + t.Fatalf("ProcessEnv = %#v, want nil for non-fcgi request", env) + } +} + +func TestServeAcceptError(t *testing.T) { + err := fcgi.Serve(errListener{}, nil) + if err == nil { + t.Fatal("Serve returned nil error, want accept error") + } + if err.Error() != "accept failed" { + t.Fatalf("Serve error = %q, want %q", err.Error(), "accept failed") + } +} + +func TestPublicAPISymbols(t *testing.T) { + if fcgi.ErrConnClosed == nil { + t.Fatal("ErrConnClosed is nil") + } + if fcgi.ErrRequestAborted == nil { + t.Fatal("ErrRequestAborted is nil") + } + + _ = fcgi.ProcessEnv + _ = fcgi.Serve +} diff --git a/test/std/net/http/go126_symbols_test.go b/test/std/net/http/go126_symbols_test.go new file mode 100644 index 0000000000..7e9b3297d3 --- /dev/null +++ b/test/std/net/http/go126_symbols_test.go @@ -0,0 +1,116 @@ +//go:build go1.26 + +package http_test + +import ( + "io" + "net/http" + "net/http/httptest" + "strings" + "sync/atomic" + "testing" +) + +func TestClientConn(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) { + io.WriteString(w, request.Host+request.URL.Path) + })) + defer server.Close() + + transport := new(http.Transport) + address := strings.TrimPrefix(server.URL, "http://") + connection, err := transport.NewClientConn(t.Context(), "http", address) + if err != nil { + t.Fatal(err) + } + defer connection.Close() + + if connection.Err() != nil || connection.Available() != 1 || connection.InFlight() != 0 { + t.Fatalf("initial state: available=%d in-flight=%d err=%v", connection.Available(), connection.InFlight(), connection.Err()) + } + if err := connection.Reserve(); err != nil { + t.Fatal(err) + } + if connection.Available() != 0 || connection.InFlight() != 1 { + t.Fatalf("reserved state: available=%d in-flight=%d", connection.Available(), connection.InFlight()) + } + connection.Release() + if connection.Available() != 1 || connection.InFlight() != 0 { + t.Fatalf("released state: available=%d in-flight=%d", connection.Available(), connection.InFlight()) + } + + var stateChanges atomic.Int32 + connection.SetStateHook(func(*http.ClientConn) { + stateChanges.Add(1) + }) + request, err := http.NewRequest(http.MethodGet, "http://example.test/resource", nil) + if err != nil { + t.Fatal(err) + } + response, err := connection.RoundTrip(request) + if err != nil { + t.Fatal(err) + } + body, err := io.ReadAll(response.Body) + response.Body.Close() + if err != nil { + t.Fatal(err) + } + if got := string(body); got != "example.test/resource" { + t.Fatalf("response body = %q, want %q", got, "example.test/resource") + } + if stateChanges.Load() == 0 { + t.Fatal("state hook was not called after the request completed") + } + + if err := connection.Close(); err != nil { + t.Fatal(err) + } + if connection.Err() == nil || connection.Available() != 0 || connection.InFlight() != 0 { + t.Fatalf("closed state: available=%d in-flight=%d err=%v", connection.Available(), connection.InFlight(), connection.Err()) + } +} + +func TestCrossOriginProtection(t *testing.T) { + protection := http.NewCrossOriginProtection() + if err := protection.AddTrustedOrigin("https://trusted.example"); err != nil { + t.Fatal(err) + } + protection.AddInsecureBypassPattern("POST /health") + + request := httptest.NewRequest(http.MethodPost, "http://service.example/write", nil) + request.Header.Set("Sec-Fetch-Site", "cross-site") + request.Header.Set("Origin", "https://evil.example") + if err := protection.Check(request); err == nil { + t.Fatal("Check accepted a cross-origin POST") + } + request.Header.Set("Origin", "https://trusted.example") + if err := protection.Check(request); err != nil { + t.Fatalf("Check rejected a trusted origin: %v", err) + } + + bypass := httptest.NewRequest(http.MethodPost, "http://service.example/health", nil) + bypass.Header.Set("Sec-Fetch-Site", "cross-site") + if err := protection.Check(bypass); err != nil { + t.Fatalf("Check rejected a bypass pattern: %v", err) + } + + protection.SetDenyHandler(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusTeapot) + })) + recorder := httptest.NewRecorder() + protection.Handler(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + })).ServeHTTP(recorder, httptest.NewRequest(http.MethodPost, "http://service.example/write", nil)) + if recorder.Code != http.StatusNoContent { + t.Fatalf("same-origin handler status = %d, want %d", recorder.Code, http.StatusNoContent) + } + + recorder = httptest.NewRecorder() + rejected := httptest.NewRequest(http.MethodPost, "http://service.example/write", nil) + rejected.Header.Set("Sec-Fetch-Site", "cross-site") + protection.Handler(http.NotFoundHandler()).ServeHTTP(recorder, rejected) + if recorder.Code != http.StatusTeapot { + t.Fatalf("deny handler status = %d, want %d", recorder.Code, http.StatusTeapot) + } +} diff --git a/test/std/net/http/http_coverage_test.go b/test/std/net/http/http_coverage_test.go new file mode 100644 index 0000000000..db48310b48 --- /dev/null +++ b/test/std/net/http/http_coverage_test.go @@ -0,0 +1,364 @@ +package http_test + +import ( + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + "time" +) + +// Test all status code constants +func TestAllStatusCodes(t *testing.T) { + codes := []int{ + http.StatusContinue, + http.StatusSwitchingProtocols, + http.StatusProcessing, + http.StatusEarlyHints, + http.StatusOK, + http.StatusCreated, + http.StatusAccepted, + http.StatusNonAuthoritativeInfo, + http.StatusNoContent, + http.StatusResetContent, + http.StatusPartialContent, + http.StatusMultiStatus, + http.StatusAlreadyReported, + http.StatusIMUsed, + http.StatusMultipleChoices, + http.StatusMovedPermanently, + http.StatusFound, + http.StatusSeeOther, + http.StatusNotModified, + http.StatusUseProxy, + http.StatusTemporaryRedirect, + http.StatusPermanentRedirect, + http.StatusBadRequest, + http.StatusUnauthorized, + http.StatusPaymentRequired, + http.StatusForbidden, + http.StatusNotFound, + http.StatusMethodNotAllowed, + http.StatusNotAcceptable, + http.StatusProxyAuthRequired, + http.StatusRequestTimeout, + http.StatusConflict, + http.StatusGone, + http.StatusLengthRequired, + http.StatusPreconditionFailed, + http.StatusRequestEntityTooLarge, + http.StatusRequestURITooLong, + http.StatusUnsupportedMediaType, + http.StatusRequestedRangeNotSatisfiable, + http.StatusExpectationFailed, + http.StatusTeapot, + http.StatusMisdirectedRequest, + http.StatusUnprocessableEntity, + http.StatusLocked, + http.StatusFailedDependency, + http.StatusTooEarly, + http.StatusUpgradeRequired, + http.StatusPreconditionRequired, + http.StatusTooManyRequests, + http.StatusRequestHeaderFieldsTooLarge, + http.StatusUnavailableForLegalReasons, + http.StatusInternalServerError, + http.StatusNotImplemented, + http.StatusBadGateway, + http.StatusServiceUnavailable, + http.StatusGatewayTimeout, + http.StatusHTTPVersionNotSupported, + http.StatusVariantAlsoNegotiates, + http.StatusInsufficientStorage, + http.StatusLoopDetected, + http.StatusNotExtended, + http.StatusNetworkAuthenticationRequired, + } + + for _, code := range codes { + if code < 100 || code >= 600 { + t.Errorf("Status code %d is out of valid range", code) + } + } +} + +// Test additional error variables +func TestAdditionalErrors(t *testing.T) { + errs := []error{ + http.ErrContentLength, + http.ErrHeaderTooLong, + http.ErrHijacked, + http.ErrMissingBoundary, + http.ErrMissingContentLength, + http.ErrNotMultipart, + http.ErrShortBody, + http.ErrUnexpectedTrailer, + http.ErrWriteAfterFlush, + } + + for _, err := range errs { + if err == nil { + t.Error("Error variable is nil") + } + if err.Error() == "" { + t.Error("Error message is empty") + } + } +} + +// Test Client methods +func TestClientMethods(t *testing.T) { + // Create test server + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) + })) + defer ts.Close() + + client := &http.Client{ + Timeout: 5 * time.Second, + } + + // Test Get + resp, err := client.Get(ts.URL) + if err != nil { + t.Fatalf("Client.Get failed: %v", err) + } + resp.Body.Close() + + // Test Head + resp, err = client.Head(ts.URL) + if err != nil { + t.Fatalf("Client.Head failed: %v", err) + } + resp.Body.Close() + + // Test Post + resp, err = client.Post(ts.URL, "text/plain", strings.NewReader("test")) + if err != nil { + t.Fatalf("Client.Post failed: %v", err) + } + resp.Body.Close() + + // Test PostForm + resp, err = client.PostForm(ts.URL, url.Values{"key": {"value"}}) + if err != nil { + t.Fatalf("Client.PostForm failed: %v", err) + } + resp.Body.Close() + + // Test Do + req, _ := http.NewRequest("GET", ts.URL, nil) + resp, err = client.Do(req) + if err != nil { + t.Fatalf("Client.Do failed: %v", err) + } + resp.Body.Close() +} + +// Test package-level convenience functions +func TestConvenienceFunctions(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) + })) + defer ts.Close() + + // Test Get + resp, err := http.Get(ts.URL) + if err != nil { + t.Fatalf("Get failed: %v", err) + } + resp.Body.Close() + + // Test Head + resp, err = http.Head(ts.URL) + if err != nil { + t.Fatalf("Head failed: %v", err) + } + resp.Body.Close() + + // Test Post + resp, err = http.Post(ts.URL, "text/plain", strings.NewReader("test")) + if err != nil { + t.Fatalf("Post failed: %v", err) + } + resp.Body.Close() + + // Test PostForm + resp, err = http.PostForm(ts.URL, url.Values{"key": {"value"}}) + if err != nil { + t.Fatalf("PostForm failed: %v", err) + } + resp.Body.Close() +} + +// Test Handle and HandleFunc +func TestHandleAndHandleFunc(t *testing.T) { + oldMux := http.DefaultServeMux + http.DefaultServeMux = http.NewServeMux() + defer func() { http.DefaultServeMux = oldMux }() + + http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + http.Handle("/other", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusCreated) + })) + + req := httptest.NewRequest("GET", "http://example.com/test", nil) + w := httptest.NewRecorder() + http.DefaultServeMux.ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Errorf("Status code = %d, want %d", w.Code, http.StatusOK) + } +} + +// Test Server methods +func TestServerMethods(t *testing.T) { + srv := &http.Server{ + Addr: ":0", + Handler: http.DefaultServeMux, + } + + // These methods exist even if we can't fully test them without starting server + _ = srv.ListenAndServe + _ = srv.ListenAndServeTLS + _ = srv.Serve + _ = srv.ServeTLS + _ = srv.Shutdown + + // Can test Close + srv.Close() +} + +// Test package-level server functions +func TestServerFunctions(t *testing.T) { + _ = http.ListenAndServe + _ = http.ListenAndServeTLS + _ = http.Serve + _ = http.ServeTLS +} + +// Test Request additional methods +func TestRequestAdditionalMethods(t *testing.T) { + req, _ := http.NewRequest("POST", "http://example.com", nil) + + // Test SetPathValue and PathValue + req.SetPathValue("key", "value") + if req.PathValue("key") != "value" { + t.Error("PathValue did not return set value") + } + + // Test CookiesNamed + req.AddCookie(&http.Cookie{Name: "test", Value: "val1"}) + req.AddCookie(&http.Cookie{Name: "test", Value: "val2"}) + cookies := req.CookiesNamed("test") + if len(cookies) != 2 { + t.Errorf("CookiesNamed returned %d cookies, want 2", len(cookies)) + } + + // Test FormFile (requires multipart form) + _ = req.FormFile +} + +// Test ResponseController additional methods +func TestResponseControllerAdditionalMethods(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + rc := http.NewResponseController(w) + + // Test Flush + err := rc.Flush() + if err != nil { + t.Logf("Flush error (expected for ResponseRecorder): %v", err) + } + + // Test Hijack (will fail with ResponseRecorder, but method exists) + _, _, err = rc.Hijack() + if err != nil { + t.Logf("Hijack error (expected for ResponseRecorder): %v", err) + } + }) + + req := httptest.NewRequest("GET", "http://example.com", nil) + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) +} + +// Test interfaces +func TestInterfaces(t *testing.T) { + // CloseNotifier interface + var _ http.CloseNotifier + + // Flusher interface + var _ http.Flusher + + // Hijacker interface + var _ http.Hijacker + + // Pusher interface + var _ http.Pusher + + // CookieJar interface + var _ http.CookieJar + + // RoundTripper interface + var _ http.RoundTripper +} + +// Test Transport additional methods +func TestTransportAdditionalMethods(t *testing.T) { + tr := &http.Transport{} + + // Test Clone + tr2 := tr.Clone() + if tr2 == nil { + t.Error("Clone returned nil") + } + + // Test RegisterProtocol + tr.RegisterProtocol("test", http.NewFileTransport(http.Dir("."))) + + // Test RoundTrip (requires full setup, just verify method exists) + _ = tr.RoundTrip +} + +// Test Protocols additional methods +func TestProtocolsAdditionalMethods(t *testing.T) { + p := &http.Protocols{} + + // Test HTTP1 + if p.HTTP1() { + t.Log("HTTP1 enabled by default") + } + + // Test SetHTTP1 + p.SetHTTP1(true) + + // Test UnencryptedHTTP2 + if p.UnencryptedHTTP2() { + t.Log("UnencryptedHTTP2 enabled") + } + + // Test SetUnencryptedHTTP2 + p.SetUnencryptedHTTP2(false) + + // Test String + s := p.String() + if s == "" { + t.Log("Protocols.String() returned empty string") + } +} + +// Test ProtocolError.Is +func TestProtocolErrorIs(t *testing.T) { + pe := &http.ProtocolError{ErrorString: "test"} + + // ProtocolError.Is is used with errors.Is, test with a target error + target := http.ErrNotSupported + result := pe.Is(target) + _ = result // Result depends on error comparison logic +} diff --git a/test/std/net/http/http_test.go b/test/std/net/http/http_test.go new file mode 100644 index 0000000000..a0d28a0ed7 --- /dev/null +++ b/test/std/net/http/http_test.go @@ -0,0 +1,1203 @@ +package http_test + +import ( + "bufio" + "bytes" + "context" + "errors" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "strings" + "testing" + "time" +) + +// Test constants +func TestConstants(t *testing.T) { + methods := []string{ + http.MethodGet, http.MethodHead, http.MethodPost, + http.MethodPut, http.MethodPatch, http.MethodDelete, + http.MethodConnect, http.MethodOptions, http.MethodTrace, + } + if len(methods) != 9 { + t.Errorf("Expected 9 HTTP methods, got %d", len(methods)) + } + + statusCodes := []int{ + http.StatusContinue, http.StatusOK, http.StatusNotFound, + http.StatusInternalServerError, http.StatusBadRequest, + } + if len(statusCodes) != 5 { + t.Errorf("Expected status codes, got %d", len(statusCodes)) + } + + if http.DefaultMaxHeaderBytes != 1<<20 { + t.Errorf("DefaultMaxHeaderBytes = %d, want %d", http.DefaultMaxHeaderBytes, 1<<20) + } + + if http.DefaultMaxIdleConnsPerHost != 2 { + t.Errorf("DefaultMaxIdleConnsPerHost = %d, want 2", http.DefaultMaxIdleConnsPerHost) + } + + if http.TimeFormat != "Mon, 02 Jan 2006 15:04:05 GMT" { + t.Errorf("TimeFormat = %q, want RFC1123", http.TimeFormat) + } + + if http.TrailerPrefix != "Trailer:" { + t.Errorf("TrailerPrefix = %q", http.TrailerPrefix) + } +} + +// Test error variables +func TestErrors(t *testing.T) { + errs := []error{ + http.ErrBodyNotAllowed, + http.ErrAbortHandler, + http.ErrBodyReadAfterClose, + http.ErrHandlerTimeout, + http.ErrLineTooLong, + http.ErrMissingFile, + http.ErrNoCookie, + http.ErrNoLocation, + http.ErrSchemeMismatch, + http.ErrServerClosed, + http.ErrSkipAltProtocol, + http.ErrUseLastResponse, + http.ErrNotSupported, + } + + for _, err := range errs { + if err == nil { + t.Error("Error variable is nil") + } + if err.Error() == "" { + t.Error("Error message is empty") + } + } +} + +// Test CanonicalHeaderKey +func TestCanonicalHeaderKey(t *testing.T) { + tests := []struct { + input string + want string + }{ + {"content-type", "Content-Type"}, + {"CONTENT-TYPE", "Content-Type"}, + {"Content-Type", "Content-Type"}, + {"x-custom-header", "X-Custom-Header"}, + } + + for _, tt := range tests { + got := http.CanonicalHeaderKey(tt.input) + if got != tt.want { + t.Errorf("CanonicalHeaderKey(%q) = %q, want %q", tt.input, got, tt.want) + } + } +} + +// Test DetectContentType +func TestDetectContentType(t *testing.T) { + tests := []struct { + data []byte + want string + }{ + {[]byte(""), "text/html; charset=utf-8"}, + {[]byte("= 0", cc.Pending()) + } + resp1, err := cc.Read(req1) + if err != nil { + t.Fatalf("ClientConn.Read: %v", err) + } + body1, _ := io.ReadAll(resp1.Body) + if err := resp1.Body.Close(); err != nil { + t.Fatalf("resp1.Body.Close: %v", err) + } + if string(body1) != "ok" { + t.Fatalf("Read body = %q, want %q", body1, "ok") + } + + req2, _ := http.NewRequest(http.MethodGet, "http://example.com/two", nil) + resp2, err := cc.Do(req2) + if err != nil { + t.Fatalf("ClientConn.Do: %v", err) + } + body2, _ := io.ReadAll(resp2.Body) + if err := resp2.Body.Close(); err != nil { + t.Fatalf("resp2.Body.Close: %v", err) + } + if string(body2) != "pong" { + t.Fatalf("Do body = %q, want %q", body2, "pong") + } + + hijackedConn, hijackedReader := cc.Hijack() + if hijackedConn == nil || hijackedReader == nil { + t.Fatal("ClientConn.Hijack returned nil") + } + if err := hijackedConn.Close(); err != nil { + t.Fatalf("hijackedConn.Close: %v", err) + } + <-done +} + +func TestClientConnCloseAndProxyConn(t *testing.T) { + sconn1, cconn1 := net.Pipe() + cc1 := httputil.NewClientConn(cconn1, nil) + if err := cc1.Close(); err != nil { + t.Fatalf("ClientConn.Close: %v", err) + } + if err := sconn1.Close(); err != nil { + t.Fatalf("sconn1.Close: %v", err) + } + + sconn2, cconn2 := net.Pipe() + cc2 := httputil.NewProxyClientConn(cconn2, nil) + if err := cc2.Close(); err != nil { + t.Fatalf("ProxyClientConn.Close: %v", err) + } + if err := sconn2.Close(); err != nil { + t.Fatalf("sconn2.Close: %v", err) + } +} + +func TestServerConnMethods(t *testing.T) { + sconn, cconn := net.Pipe() + defer sconn.Close() + defer cconn.Close() + + sc := httputil.NewServerConn(sconn, nil) + done := make(chan struct{}) + go func() { + defer close(done) + reqText := "GET /x HTTP/1.1\r\nHost: example.com\r\n\r\n" + if n, err := io.WriteString(cconn, reqText); err != nil || n != len(reqText) { + t.Errorf("client WriteString request = (%d, %v), want (%d, nil)", n, err, len(reqText)) + return + } + br := bufio.NewReader(cconn) + resp, err := http.ReadResponse(br, nil) + if err != nil { + t.Errorf("client ReadResponse: %v", err) + return + } + body, _ := io.ReadAll(resp.Body) + if err := resp.Body.Close(); err != nil { + t.Errorf("resp.Body.Close: %v", err) + return + } + if string(body) != "srv" { + t.Errorf("response body = %q, want %q", body, "srv") + return + } + }() + + req, err := sc.Read() + if err != nil { + t.Fatalf("ServerConn.Read: %v", err) + } + if req.URL.Path != "/x" { + t.Fatalf("request path = %q, want /x", req.URL.Path) + } + if sc.Pending() < 0 { + t.Fatalf("ServerConn.Pending = %d, want >= 0", sc.Pending()) + } + + resp := &http.Response{ + StatusCode: 200, + Status: "200 OK", + ProtoMajor: 1, + ProtoMinor: 1, + ContentLength: 3, + Header: make(http.Header), + Body: io.NopCloser(strings.NewReader("srv")), + } + resp.Header.Set("Content-Length", "3") + resp.Header.Set("Content-Type", "text/plain") + if err := sc.Write(req, resp); err != nil { + t.Fatalf("ServerConn.Write: %v", err) + } + <-done + + hc, hr := sc.Hijack() + if hc == nil || hr == nil { + t.Fatal("ServerConn.Hijack returned nil") + } + if err := hc.Close(); err != nil { + t.Fatalf("hijacked server conn close: %v", err) + } +} + +func TestServerConnClose(t *testing.T) { + sconn, cconn := net.Pipe() + sc := httputil.NewServerConn(sconn, nil) + if err := sc.Close(); err != nil { + t.Fatalf("ServerConn.Close: %v", err) + } + if err := cconn.Close(); err != nil { + t.Fatalf("cconn.Close: %v", err) + } +} diff --git a/test/std/net/http/pprof/pprof_test.go b/test/std/net/http/pprof/pprof_test.go new file mode 100644 index 0000000000..8f5377f7a5 --- /dev/null +++ b/test/std/net/http/pprof/pprof_test.go @@ -0,0 +1,51 @@ +package pprof_test + +import ( + "net/http" + "net/http/httptest" + "net/http/pprof" + "strings" + "testing" +) + +func TestIndexRoot(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/debug/pprof/", nil) + rec := httptest.NewRecorder() + pprof.Index(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK) + } + if !strings.Contains(rec.Body.String(), "profile") { + t.Fatalf("unexpected Index response: %q", rec.Body.String()) + } +} + +func TestCmdline(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/debug/pprof/cmdline", nil) + rec := httptest.NewRecorder() + pprof.Cmdline(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK) + } + if rec.Body.Len() == 0 { + t.Fatal("cmdline response is empty") + } +} + +func TestHandlerFactory(t *testing.T) { + h := pprof.Handler("goroutine") + if h == nil { + t.Fatal("Handler(goroutine) returned nil") + } +} + +func TestPublicAPISymbols(t *testing.T) { + _ = pprof.Cmdline + _ = pprof.Handler + _ = pprof.Index + _ = pprof.Profile + _ = pprof.Symbol + _ = pprof.Trace +} diff --git a/test/std/net/ip_methods_test.go b/test/std/net/ip_methods_test.go new file mode 100644 index 0000000000..4e5e7f547a --- /dev/null +++ b/test/std/net/ip_methods_test.go @@ -0,0 +1,324 @@ +package net_test + +import ( + "errors" + "net" + "syscall" + "testing" + "time" +) + +func TestDialIP(t *testing.T) { + addr := &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)} + conn, err := net.DialIP("ip4:icmp", nil, addr) + + if err != nil { + // On most systems, ICMP requires privileges + var opErr *net.OpError + if !errors.As(err, &opErr) { + t.Fatalf("DialIP error should be *net.OpError, got %T", err) + } + if opErr.Op != "dial" { + t.Errorf("OpError.Op = %q, want %q", opErr.Op, "dial") + } + return + } + + defer conn.Close() + + // Verify connection properties + if conn.LocalAddr() == nil { + t.Error("DialIP: LocalAddr should not be nil") + } + if conn.RemoteAddr() == nil { + t.Error("DialIP: RemoteAddr should not be nil") + } + + remoteAddr, ok := conn.RemoteAddr().(*net.IPAddr) + if !ok { + t.Fatalf("RemoteAddr type = %T, want *net.IPAddr", conn.RemoteAddr()) + } + if !remoteAddr.IP.Equal(addr.IP) { + t.Errorf("RemoteAddr IP = %v, want %v", remoteAddr.IP, addr.IP) + } +} + +func TestListenIP(t *testing.T) { + addr := &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)} + listener, err := net.ListenIP("ip4:icmp", addr) + + if err != nil { + // On most systems, ICMP requires privileges + var opErr *net.OpError + if !errors.As(err, &opErr) { + t.Fatalf("ListenIP error should be *net.OpError, got %T", err) + } + if opErr.Op != "listen" { + t.Errorf("OpError.Op = %q, want %q", opErr.Op, "listen") + } + return + } + + defer listener.Close() + + // Verify listener properties + if listener.LocalAddr() == nil { + t.Error("ListenIP: LocalAddr should not be nil") + } + + localAddr, ok := listener.LocalAddr().(*net.IPAddr) + if !ok { + t.Fatalf("LocalAddr type = %T, want *net.IPAddr", listener.LocalAddr()) + } + if !localAddr.IP.Equal(addr.IP) { + t.Errorf("LocalAddr IP = %v, want %v", localAddr.IP, addr.IP) + } +} + +func TestListenMulticastUDP(t *testing.T) { + multicastAddr := &net.UDPAddr{IP: net.IPv4(224, 0, 0, 1), Port: 9999} + conn, err := net.ListenMulticastUDP("udp4", nil, multicastAddr) + + if err != nil { + var opErr *net.OpError + if !errors.As(err, &opErr) { + t.Fatalf("ListenMulticastUDP error should be *net.OpError, got %T", err) + } + if opErr.Op != "listen" { + t.Errorf("OpError.Op = %q, want %q", opErr.Op, "listen") + } + return + } + + defer conn.Close() + + // Verify connection properties + if conn.LocalAddr() == nil { + t.Error("ListenMulticastUDP: LocalAddr should not be nil") + } + + localAddr, ok := conn.LocalAddr().(*net.UDPAddr) + if !ok { + t.Fatalf("LocalAddr type = %T, want *net.UDPAddr", conn.LocalAddr()) + } + if localAddr.Port != multicastAddr.Port { + t.Errorf("LocalAddr Port = %d, want %d", localAddr.Port, multicastAddr.Port) + } +} + +func TestOpErrorInterface(t *testing.T) { + var _ net.Error = &net.OpError{} + + opErr := &net.OpError{ + Op: "test", + Net: "tcp", + Err: errors.New("test error"), + } + + if !opErr.Timeout() { + // Should be false for non-timeout errors + } + + if !opErr.Temporary() { + // Should be false for non-temporary errors + } + + errStr := opErr.Error() + if errStr == "" { + t.Error("OpError.Error() should not be empty") + } +} + +func TestIPConnZeroValue(t *testing.T) { + var ipConn net.IPConn + + // Test Close + if err := ipConn.Close(); !errors.Is(err, syscall.EINVAL) { + t.Errorf("Close() = %v, want EINVAL", err) + } + + // Test LocalAddr + if addr := ipConn.LocalAddr(); addr != nil { + t.Errorf("LocalAddr() = %v, want nil", addr) + } + + // Test RemoteAddr + if addr := ipConn.RemoteAddr(); addr != nil { + t.Errorf("RemoteAddr() = %v, want nil", addr) + } +} + +func TestIPConnDeadlines(t *testing.T) { + var ipConn net.IPConn + deadline := time.Now().Add(time.Second) + + // Test SetDeadline + if err := ipConn.SetDeadline(deadline); !errors.Is(err, syscall.EINVAL) { + t.Errorf("SetDeadline() = %v, want EINVAL", err) + } + + // Test SetReadDeadline + if err := ipConn.SetReadDeadline(deadline); !errors.Is(err, syscall.EINVAL) { + t.Errorf("SetReadDeadline() = %v, want EINVAL", err) + } + + // Test SetWriteDeadline + if err := ipConn.SetWriteDeadline(deadline); !errors.Is(err, syscall.EINVAL) { + t.Errorf("SetWriteDeadline() = %v, want EINVAL", err) + } +} + +func TestIPConnBuffers(t *testing.T) { + var ipConn net.IPConn + + // Test SetReadBuffer + if err := ipConn.SetReadBuffer(1024); !errors.Is(err, syscall.EINVAL) { + t.Errorf("SetReadBuffer() = %v, want EINVAL", err) + } + + // Test SetWriteBuffer + if err := ipConn.SetWriteBuffer(1024); !errors.Is(err, syscall.EINVAL) { + t.Errorf("SetWriteBuffer() = %v, want EINVAL", err) + } +} + +func TestIPConnSyscallConn(t *testing.T) { + var ipConn net.IPConn + + raw, err := ipConn.SyscallConn() + if raw != nil { + t.Errorf("SyscallConn() raw = %v, want nil", raw) + } + if !errors.Is(err, syscall.EINVAL) { + t.Errorf("SyscallConn() error = %v, want EINVAL", err) + } +} + +func TestIPConnReadWrite(t *testing.T) { + var ipConn net.IPConn + buf := make([]byte, 10) + + // Test Read + n, err := ipConn.Read(buf) + if n != 0 { + t.Errorf("Read() n = %d, want 0", n) + } + if !errors.Is(err, syscall.EINVAL) { + t.Errorf("Read() error = %v, want EINVAL", err) + } + + // Test Write + n, err = ipConn.Write(buf) + if n != 0 { + t.Errorf("Write() n = %d, want 0", n) + } + if !errors.Is(err, syscall.EINVAL) { + t.Errorf("Write() error = %v, want EINVAL", err) + } +} + +func TestIPConnReadFrom(t *testing.T) { + var ipConn net.IPConn + buf := make([]byte, 10) + + // Test ReadFrom + n, addr, err := ipConn.ReadFrom(buf) + if n != 0 { + t.Errorf("ReadFrom() n = %d, want 0", n) + } + if addr != nil { + t.Errorf("ReadFrom() addr = %v, want nil", addr) + } + if !errors.Is(err, syscall.EINVAL) { + t.Errorf("ReadFrom() error = %v, want EINVAL", err) + } + + // Test ReadFromIP + n, ipAddr, err := ipConn.ReadFromIP(buf) + if n != 0 { + t.Errorf("ReadFromIP() n = %d, want 0", n) + } + if ipAddr != nil { + t.Errorf("ReadFromIP() addr = %v, want nil", ipAddr) + } + if !errors.Is(err, syscall.EINVAL) { + t.Errorf("ReadFromIP() error = %v, want EINVAL", err) + } +} + +func TestIPConnReadMsgIP(t *testing.T) { + var ipConn net.IPConn + buf := make([]byte, 10) + oob := make([]byte, 10) + + n, oobn, flags, addr, err := ipConn.ReadMsgIP(buf, oob) + if n != 0 { + t.Errorf("ReadMsgIP() n = %d, want 0", n) + } + if oobn != 0 { + t.Errorf("ReadMsgIP() oobn = %d, want 0", oobn) + } + if flags != 0 { + t.Errorf("ReadMsgIP() flags = %d, want 0", flags) + } + if addr != nil { + t.Errorf("ReadMsgIP() addr = %v, want nil", addr) + } + if !errors.Is(err, syscall.EINVAL) { + t.Errorf("ReadMsgIP() error = %v, want EINVAL", err) + } +} + +func TestIPConnWriteTo(t *testing.T) { + var ipConn net.IPConn + buf := make([]byte, 10) + addr := &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)} + + // Test WriteTo + n, err := ipConn.WriteTo(buf, addr) + if n != 0 { + t.Errorf("WriteTo() n = %d, want 0", n) + } + if !errors.Is(err, syscall.EINVAL) { + t.Errorf("WriteTo() error = %v, want EINVAL", err) + } + + // Test WriteToIP + n, err = ipConn.WriteToIP(buf, addr) + if n != 0 { + t.Errorf("WriteToIP() n = %d, want 0", n) + } + if !errors.Is(err, syscall.EINVAL) { + t.Errorf("WriteToIP() error = %v, want EINVAL", err) + } +} + +func TestIPConnWriteMsgIP(t *testing.T) { + var ipConn net.IPConn + buf := make([]byte, 10) + oob := make([]byte, 10) + addr := &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)} + + n, oobn, err := ipConn.WriteMsgIP(buf, oob, addr) + if n != 0 { + t.Errorf("WriteMsgIP() n = %d, want 0", n) + } + if oobn != 0 { + t.Errorf("WriteMsgIP() oobn = %d, want 0", oobn) + } + if !errors.Is(err, syscall.EINVAL) { + t.Errorf("WriteMsgIP() error = %v, want EINVAL", err) + } +} + +func TestIPConnFile(t *testing.T) { + var ipConn net.IPConn + + defer func() { + if r := recover(); r == nil { + t.Error("File() should panic on zero value IPConn") + } + }() + + ipConn.File() +} diff --git a/test/std/net/mail/mail_test.go b/test/std/net/mail/mail_test.go new file mode 100644 index 0000000000..205185d4c2 --- /dev/null +++ b/test/std/net/mail/mail_test.go @@ -0,0 +1,98 @@ +package mail_test + +import ( + "io" + "net/mail" + "strings" + "testing" + "time" +) + +func TestAddressParsingAndFormatting(t *testing.T) { + a, err := mail.ParseAddress("Alice ") + if err != nil { + t.Fatalf("ParseAddress failed: %v", err) + } + if a.Name != "Alice" || a.Address != "alice@example.com" { + t.Fatalf("ParseAddress returned %+v", a) + } + if got := a.String(); !strings.Contains(got, "alice@example.com") { + t.Fatalf("Address.String unexpected: %q", got) + } + + list, err := mail.ParseAddressList("Bob , carol@example.com") + if err != nil { + t.Fatalf("ParseAddressList failed: %v", err) + } + if len(list) != 2 { + t.Fatalf("ParseAddressList len=%d, want 2", len(list)) + } +} + +func TestAddressParserAndHeader(t *testing.T) { + p := &mail.AddressParser{} + a, err := p.Parse("Dan ") + if err != nil { + t.Fatalf("AddressParser.Parse failed: %v", err) + } + if a.Address != "dan@example.com" { + t.Fatalf("AddressParser.Parse address=%q", a.Address) + } + if _, err := p.ParseList("Eve , frank@example.com"); err != nil { + t.Fatalf("AddressParser.ParseList failed: %v", err) + } + + h := mail.Header{ + "From": []string{"Grace "}, + "To": []string{"Heidi , ivan@example.com"}, + "Date": []string{"Mon, 02 Jan 2006 15:04:05 -0700"}, + } + if got := h.Get("from"); !strings.Contains(got, "grace@example.com") { + t.Fatalf("Header.Get returned %q", got) + } + toList, err := h.AddressList("To") + if err != nil { + t.Fatalf("Header.AddressList failed: %v", err) + } + if len(toList) != 2 { + t.Fatalf("Header.AddressList len=%d, want 2", len(toList)) + } + d, err := h.Date() + if err != nil { + t.Fatalf("Header.Date failed: %v", err) + } + if d.Year() != 2006 { + t.Fatalf("Header.Date year=%d, want 2006", d.Year()) + } + if _, err := (mail.Header{}).Date(); err != mail.ErrHeaderNotPresent { + t.Fatalf("empty Header.Date error=%v, want ErrHeaderNotPresent", err) + } +} + +func TestReadMessageAndParseDate(t *testing.T) { + raw := "From: A \r\n" + + "Date: Mon, 02 Jan 2006 15:04:05 -0700\r\n" + + "\r\nhello body" + msg, err := mail.ReadMessage(strings.NewReader(raw)) + if err != nil { + t.Fatalf("ReadMessage failed: %v", err) + } + // Reference exported Message type explicitly. + var _ *mail.Message = msg + + body, err := io.ReadAll(msg.Body) + if err != nil { + t.Fatalf("ReadAll body failed: %v", err) + } + if got := string(body); got != "hello body" { + t.Fatalf("message body=%q, want %q", got, "hello body") + } + + d, err := mail.ParseDate("Mon, 02 Jan 2006 15:04:05 -0700") + if err != nil { + t.Fatalf("ParseDate failed: %v", err) + } + if d.Location() == time.UTC && d.Hour() != 15 { + t.Fatalf("ParseDate result unexpected: %v", d) + } +} diff --git a/test/std/net/net_test.go b/test/std/net/net_test.go new file mode 100644 index 0000000000..1f3dd76aa5 --- /dev/null +++ b/test/std/net/net_test.go @@ -0,0 +1,1680 @@ +package net_test + +import ( + "context" + "io" + "net" + "testing" + "time" +) + +func TestParseIP(t *testing.T) { + tests := []struct { + input string + want bool + }{ + {"192.168.1.1", true}, + {"::1", true}, + {"2001:db8::1", true}, + {"invalid", false}, + } + for _, tt := range tests { + ip := net.ParseIP(tt.input) + if (ip != nil) != tt.want { + t.Errorf("ParseIP(%q) valid = %v, want %v", tt.input, ip != nil, tt.want) + } + } +} + +func TestIPv4(t *testing.T) { + ip := net.IPv4(192, 168, 1, 1) + if ip == nil { + t.Fatal("IPv4 returned nil") + } + if ip.String() != "192.168.1.1" { + t.Errorf("IPv4().String() = %q, want %q", ip.String(), "192.168.1.1") + } +} + +func TestIPString(t *testing.T) { + ip := net.ParseIP("192.168.1.1") + if ip.String() != "192.168.1.1" { + t.Errorf("IP.String() = %q, want %q", ip.String(), "192.168.1.1") + } +} + +func TestIPEqual(t *testing.T) { + ip1 := net.ParseIP("192.168.1.1") + ip2 := net.ParseIP("192.168.1.1") + ip3 := net.ParseIP("192.168.1.2") + if !ip1.Equal(ip2) { + t.Error("Equal IPs not equal") + } + if ip1.Equal(ip3) { + t.Error("Different IPs equal") + } +} + +func TestIPTo4(t *testing.T) { + ip := net.ParseIP("192.168.1.1") + ip4 := ip.To4() + if ip4 == nil { + t.Error("To4() returned nil for IPv4 address") + } +} + +func TestIPTo16(t *testing.T) { + ip := net.ParseIP("192.168.1.1") + ip16 := ip.To16() + if ip16 == nil { + t.Error("To16() returned nil") + } +} + +func TestIPDefaultMask(t *testing.T) { + ip := net.IPv4(192, 168, 1, 1) + mask := ip.DefaultMask() + if mask == nil { + t.Error("DefaultMask() returned nil") + } +} + +func TestIPMask(t *testing.T) { + ip := net.IPv4(192, 168, 1, 1) + mask := net.CIDRMask(24, 32) + masked := ip.Mask(mask) + want := "192.168.1.0" + if masked.String() != want { + t.Errorf("Mask() = %q, want %q", masked.String(), want) + } +} + +func TestIPIsLoopback(t *testing.T) { + tests := []struct { + ip string + want bool + }{ + {"127.0.0.1", true}, + {"::1", true}, + {"192.168.1.1", false}, + } + for _, tt := range tests { + ip := net.ParseIP(tt.ip) + if got := ip.IsLoopback(); got != tt.want { + t.Errorf("IP(%q).IsLoopback() = %v, want %v", tt.ip, got, tt.want) + } + } +} + +func TestIPIsMulticast(t *testing.T) { + tests := []struct { + ip string + want bool + }{ + {"224.0.0.1", true}, + {"ff02::1", true}, + {"192.168.1.1", false}, + } + for _, tt := range tests { + ip := net.ParseIP(tt.ip) + if got := ip.IsMulticast(); got != tt.want { + t.Errorf("IP(%q).IsMulticast() = %v, want %v", tt.ip, got, tt.want) + } + } +} + +func TestIPIsPrivate(t *testing.T) { + tests := []struct { + ip string + want bool + }{ + {"192.168.1.1", true}, + {"10.0.0.1", true}, + {"8.8.8.8", false}, + } + for _, tt := range tests { + ip := net.ParseIP(tt.ip) + if got := ip.IsPrivate(); got != tt.want { + t.Errorf("IP(%q).IsPrivate() = %v, want %v", tt.ip, got, tt.want) + } + } +} + +func TestIPIsUnspecified(t *testing.T) { + ip := net.ParseIP("0.0.0.0") + if !ip.IsUnspecified() { + t.Error("0.0.0.0 IsUnspecified() = false") + } +} + +func TestIPIsGlobalUnicast(t *testing.T) { + ip := net.ParseIP("8.8.8.8") + if !ip.IsGlobalUnicast() { + t.Error("8.8.8.8 IsGlobalUnicast() = false") + } +} + +func TestIPIsLinkLocalUnicast(t *testing.T) { + ip := net.ParseIP("169.254.1.1") + if !ip.IsLinkLocalUnicast() { + t.Error("169.254.1.1 IsLinkLocalUnicast() = false") + } +} + +func TestIPIsLinkLocalMulticast(t *testing.T) { + ip := net.ParseIP("224.0.0.1") + if !ip.IsLinkLocalMulticast() { + t.Error("224.0.0.1 IsLinkLocalMulticast() = false") + } +} + +func TestIPIsInterfaceLocalMulticast(t *testing.T) { + ip := net.ParseIP("ff01::1") + if !ip.IsInterfaceLocalMulticast() { + t.Error("ff01::1 IsInterfaceLocalMulticast() = false") + } +} + +func TestIPMarshalText(t *testing.T) { + ip := net.ParseIP("192.168.1.1") + text, err := ip.MarshalText() + if err != nil { + t.Fatalf("MarshalText() error = %v", err) + } + if string(text) != "192.168.1.1" { + t.Errorf("MarshalText() = %q, want %q", string(text), "192.168.1.1") + } +} + +func TestIPUnmarshalText(t *testing.T) { + var ip net.IP + err := ip.UnmarshalText([]byte("192.168.1.1")) + if err != nil { + t.Fatalf("UnmarshalText() error = %v", err) + } + if ip.String() != "192.168.1.1" { + t.Errorf("UnmarshalText() ip = %q, want %q", ip.String(), "192.168.1.1") + } +} + +func TestIPAppendText(t *testing.T) { + ip := net.ParseIP("192.168.1.1") + b := []byte("prefix:") + result, err := ip.AppendText(b) + if err != nil { + t.Fatalf("AppendText() error = %v", err) + } + if len(result) <= len(b) { + t.Error("AppendText() did not append data") + } +} + +func TestParseCIDR(t *testing.T) { + tests := []struct { + input string + wantErr bool + }{ + {"192.168.1.0/24", false}, + {"2001:db8::/32", false}, + {"invalid", true}, + } + for _, tt := range tests { + ip, ipnet, err := net.ParseCIDR(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("ParseCIDR(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr) + continue + } + if !tt.wantErr && (ip == nil || ipnet == nil) { + t.Errorf("ParseCIDR(%q) returned nil", tt.input) + } + } +} + +func TestIPNetContains(t *testing.T) { + _, ipnet, err := net.ParseCIDR("192.168.1.0/24") + if err != nil { + t.Fatalf("ParseCIDR error: %v", err) + } + ip1 := net.ParseIP("192.168.1.1") + ip2 := net.ParseIP("192.168.2.1") + if !ipnet.Contains(ip1) { + t.Error("Contains() = false for IP in network") + } + if ipnet.Contains(ip2) { + t.Error("Contains() = true for IP outside network") + } +} + +func TestIPNetString(t *testing.T) { + _, ipnet, err := net.ParseCIDR("192.168.1.0/24") + if err != nil { + t.Fatalf("ParseCIDR error: %v", err) + } + got := ipnet.String() + want := "192.168.1.0/24" + if got != want { + t.Errorf("IPNet.String() = %q, want %q", got, want) + } +} + +func TestIPNetNetwork(t *testing.T) { + _, ipnet, err := net.ParseCIDR("192.168.1.0/24") + if err != nil { + t.Fatalf("ParseCIDR error: %v", err) + } + network := ipnet.Network() + if network == "" { + t.Error("IPNet.Network() returned empty string") + } +} + +func TestParseMAC(t *testing.T) { + tests := []struct { + input string + wantErr bool + }{ + {"01:23:45:67:89:ab", false}, + {"01-23-45-67-89-ab", false}, + {"0123.4567.89ab", false}, + {"invalid", true}, + } + for _, tt := range tests { + mac, err := net.ParseMAC(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("ParseMAC(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr) + continue + } + if !tt.wantErr && mac == nil { + t.Errorf("ParseMAC(%q) returned nil", tt.input) + } + } +} + +func TestHardwareAddrString(t *testing.T) { + mac, err := net.ParseMAC("01:23:45:67:89:ab") + if err != nil { + t.Fatalf("ParseMAC error: %v", err) + } + got := mac.String() + want := "01:23:45:67:89:ab" + if got != want { + t.Errorf("HardwareAddr.String() = %q, want %q", got, want) + } +} + +func TestJoinHostPort(t *testing.T) { + tests := []struct { + host string + port string + want string + }{ + {"example.com", "80", "example.com:80"}, + {"192.168.1.1", "8080", "192.168.1.1:8080"}, + {"::1", "80", "[::1]:80"}, + } + for _, tt := range tests { + got := net.JoinHostPort(tt.host, tt.port) + if got != tt.want { + t.Errorf("JoinHostPort(%q, %q) = %q, want %q", tt.host, tt.port, got, tt.want) + } + } +} + +func TestSplitHostPort(t *testing.T) { + tests := []struct { + hostport string + wantHost string + wantPort string + wantErr bool + }{ + {"example.com:80", "example.com", "80", false}, + {"192.168.1.1:8080", "192.168.1.1", "8080", false}, + {"[::1]:80", "::1", "80", false}, + {"invalid", "", "", true}, + } + for _, tt := range tests { + host, port, err := net.SplitHostPort(tt.hostport) + if (err != nil) != tt.wantErr { + t.Errorf("SplitHostPort(%q) error = %v, wantErr %v", tt.hostport, err, tt.wantErr) + continue + } + if !tt.wantErr && (host != tt.wantHost || port != tt.wantPort) { + t.Errorf("SplitHostPort(%q) = (%q, %q), want (%q, %q)", tt.hostport, host, port, tt.wantHost, tt.wantPort) + } + } +} + +func TestResolveTCPAddr(t *testing.T) { + addr, err := net.ResolveTCPAddr("tcp", "localhost:8080") + if err != nil { + t.Fatalf("ResolveTCPAddr error: %v", err) + } + if addr == nil { + t.Error("ResolveTCPAddr returned nil") + } +} + +func TestTCPAddrString(t *testing.T) { + addr := &net.TCPAddr{ + IP: net.ParseIP("192.168.1.1"), + Port: 8080, + } + got := addr.String() + if got != "192.168.1.1:8080" { + t.Errorf("TCPAddr.String() = %q, want %q", got, "192.168.1.1:8080") + } +} + +func TestTCPAddrNetwork(t *testing.T) { + addr := &net.TCPAddr{ + IP: net.ParseIP("192.168.1.1"), + Port: 8080, + } + network := addr.Network() + if network != "tcp" { + t.Errorf("TCPAddr.Network() = %q, want %q", network, "tcp") + } +} + +func TestResolveUDPAddr(t *testing.T) { + addr, err := net.ResolveUDPAddr("udp", "localhost:8080") + if err != nil { + t.Fatalf("ResolveUDPAddr error: %v", err) + } + if addr == nil { + t.Error("ResolveUDPAddr returned nil") + } +} + +func TestUDPAddrString(t *testing.T) { + addr := &net.UDPAddr{ + IP: net.ParseIP("192.168.1.1"), + Port: 8080, + } + got := addr.String() + if got != "192.168.1.1:8080" { + t.Errorf("UDPAddr.String() = %q, want %q", got, "192.168.1.1:8080") + } +} + +func TestUDPAddrNetwork(t *testing.T) { + addr := &net.UDPAddr{ + IP: net.ParseIP("192.168.1.1"), + Port: 8080, + } + network := addr.Network() + if network != "udp" { + t.Errorf("UDPAddr.Network() = %q, want %q", network, "udp") + } +} + +func TestResolveIPAddr(t *testing.T) { + addr, err := net.ResolveIPAddr("ip", "localhost") + if err != nil { + t.Fatalf("ResolveIPAddr error: %v", err) + } + if addr == nil { + t.Error("ResolveIPAddr returned nil") + } +} + +func TestIPAddrString(t *testing.T) { + addr := &net.IPAddr{ + IP: net.ParseIP("192.168.1.1"), + } + got := addr.String() + if got != "192.168.1.1" { + t.Errorf("IPAddr.String() = %q, want %q", got, "192.168.1.1") + } +} + +func TestIPAddrNetwork(t *testing.T) { + addr := &net.IPAddr{ + IP: net.ParseIP("192.168.1.1"), + } + network := addr.Network() + if network != "ip" { + t.Errorf("IPAddr.Network() = %q, want %q", network, "ip") + } +} + +func TestResolveUnixAddr(t *testing.T) { + addr, err := net.ResolveUnixAddr("unix", "/tmp/test.sock") + if err != nil { + t.Fatalf("ResolveUnixAddr error: %v", err) + } + if addr == nil { + t.Error("ResolveUnixAddr returned nil") + } +} + +func TestUnixAddrString(t *testing.T) { + addr := &net.UnixAddr{ + Name: "/tmp/test.sock", + Net: "unix", + } + got := addr.String() + if got != "/tmp/test.sock" { + t.Errorf("UnixAddr.String() = %q, want %q", got, "/tmp/test.sock") + } +} + +func TestUnixAddrNetwork(t *testing.T) { + addr := &net.UnixAddr{ + Name: "/tmp/test.sock", + Net: "unix", + } + network := addr.Network() + if network != "unix" { + t.Errorf("UnixAddr.Network() = %q, want %q", network, "unix") + } +} + +func TestPipe(t *testing.T) { + c1, c2 := net.Pipe() + if c1 == nil || c2 == nil { + t.Fatal("Pipe returned nil") + } + defer c1.Close() + defer c2.Close() + + go func() { + c1.Write([]byte("hello")) + }() + + buf := make([]byte, 5) + n, err := c2.Read(buf) + if err != nil { + t.Fatalf("Read error: %v", err) + } + if n != 5 || string(buf) != "hello" { + t.Errorf("Read = %q, want %q", string(buf), "hello") + } +} + +func TestListenTCP(t *testing.T) { + addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("ResolveTCPAddr error: %v", err) + } + ln, err := net.ListenTCP("tcp", addr) + if err != nil { + t.Fatalf("ListenTCP error: %v", err) + } + defer ln.Close() + if ln.Addr() == nil { + t.Error("ListenTCP returned nil address") + } +} + +func TestTCPListenerAccept(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + go func() { + net.Dial("tcp", ln.Addr().String()) + }() + + conn, err := ln.Accept() + if err != nil { + t.Fatalf("Accept error: %v", err) + } + defer conn.Close() +} + +func TestTCPListenerClose(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + err = ln.Close() + if err != nil { + t.Errorf("Close error: %v", err) + } +} + +func TestTCPListenerAddr(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + addr := ln.Addr() + if addr == nil { + t.Error("Addr() returned nil") + } +} + +func TestDialTCP(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + go func() { + ln.Accept() + }() + + addr := ln.Addr().(*net.TCPAddr) + conn, err := net.DialTCP("tcp", nil, addr) + if err != nil { + t.Fatalf("DialTCP error: %v", err) + } + defer conn.Close() +} + +func TestDial(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + go func() { + ln.Accept() + }() + + conn, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial error: %v", err) + } + defer conn.Close() +} + +func TestDialTimeout(t *testing.T) { + conn, err := net.DialTimeout("tcp", "192.0.2.1:80", 100*time.Millisecond) + if err == nil { + conn.Close() + } +} + +func TestTCPConnClose(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + go func() { + ln.Accept() + }() + + conn, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial error: %v", err) + } + err = conn.Close() + if err != nil { + t.Errorf("Close error: %v", err) + } +} + +func TestTCPConnRead(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + go func() { + conn, err := ln.Accept() + if err != nil { + t.Errorf("Accept error: %v", err) + return + } + defer conn.Close() + if _, err := conn.Write([]byte("hello")); err != nil { + t.Errorf("server Write error: %v", err) + } + }() + + conn, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial error: %v", err) + } + defer conn.Close() + + buf := make([]byte, 5) + n, err := conn.Read(buf) + if err != nil { + t.Fatalf("Read error: %v", err) + } + if n != 5 || string(buf) != "hello" { + t.Errorf("Read = %q, want %q", string(buf), "hello") + } +} + +func TestTCPConnWrite(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + done := make(chan error, 1) + go func() { + conn, err := ln.Accept() + if err != nil { + done <- err + return + } + defer conn.Close() + buf := make([]byte, 5) + _, err = io.ReadFull(conn, buf) + done <- err + }() + + conn, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial error: %v", err) + } + defer conn.Close() + + n, err := conn.Write([]byte("hello")) + if err != nil { + t.Fatalf("Write error: %v", err) + } + if n != 5 { + t.Errorf("Write = %d, want 5", n) + } + + if err := <-done; err != nil { + t.Fatalf("server error: %v", err) + } +} + +func TestTCPConnLocalAddr(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + go func() { + ln.Accept() + }() + + conn, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial error: %v", err) + } + defer conn.Close() + + addr := conn.LocalAddr() + if addr == nil { + t.Error("LocalAddr() returned nil") + } +} + +func TestTCPConnRemoteAddr(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + go func() { + ln.Accept() + }() + + conn, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial error: %v", err) + } + defer conn.Close() + + addr := conn.RemoteAddr() + if addr == nil { + t.Error("RemoteAddr() returned nil") + } +} + +func TestTCPConnSetDeadline(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + go func() { + ln.Accept() + }() + + conn, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial error: %v", err) + } + defer conn.Close() + + err = conn.SetDeadline(time.Now().Add(time.Second)) + if err != nil { + t.Errorf("SetDeadline error: %v", err) + } +} + +func TestTCPConnSetReadDeadline(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + go func() { + ln.Accept() + }() + + conn, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial error: %v", err) + } + defer conn.Close() + + err = conn.SetReadDeadline(time.Now().Add(time.Second)) + if err != nil { + t.Errorf("SetReadDeadline error: %v", err) + } +} + +func TestTCPConnSetWriteDeadline(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + go func() { + ln.Accept() + }() + + conn, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial error: %v", err) + } + defer conn.Close() + + err = conn.SetWriteDeadline(time.Now().Add(time.Second)) + if err != nil { + t.Errorf("SetWriteDeadline error: %v", err) + } +} + +func TestListenUDP(t *testing.T) { + addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") + if err != nil { + t.Fatalf("ResolveUDPAddr error: %v", err) + } + conn, err := net.ListenUDP("udp", addr) + if err != nil { + t.Fatalf("ListenUDP error: %v", err) + } + defer conn.Close() + if conn.LocalAddr() == nil { + t.Error("ListenUDP returned nil address") + } +} + +func TestDialUDP(t *testing.T) { + addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") + if err != nil { + t.Fatalf("ResolveUDPAddr error: %v", err) + } + server, err := net.ListenUDP("udp", addr) + if err != nil { + t.Fatalf("ListenUDP error: %v", err) + } + defer server.Close() + serverAddr, ok := server.LocalAddr().(*net.UDPAddr) + if !ok { + t.Fatalf("LocalAddr type = %T, want *net.UDPAddr", server.LocalAddr()) + } + + conn, err := net.DialUDP("udp", nil, serverAddr) + if err != nil { + t.Fatalf("DialUDP error: %v", err) + } + defer conn.Close() +} + +func TestUDPConnClose(t *testing.T) { + addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") + if err != nil { + t.Fatalf("ResolveUDPAddr error: %v", err) + } + conn, err := net.ListenUDP("udp", addr) + if err != nil { + t.Fatalf("ListenUDP error: %v", err) + } + err = conn.Close() + if err != nil { + t.Errorf("Close error: %v", err) + } +} + +func TestUDPConnLocalAddr(t *testing.T) { + addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") + if err != nil { + t.Fatalf("ResolveUDPAddr error: %v", err) + } + conn, err := net.ListenUDP("udp", addr) + if err != nil { + t.Fatalf("ListenUDP error: %v", err) + } + defer conn.Close() + if conn.LocalAddr() == nil { + t.Error("LocalAddr() returned nil") + } +} + +func TestListen(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + if ln.Addr() == nil { + t.Error("Listen returned nil address") + } +} + +func TestListenPacket(t *testing.T) { + pc, err := net.ListenPacket("udp", "127.0.0.1:0") + if err != nil { + t.Fatalf("ListenPacket error: %v", err) + } + defer pc.Close() + if pc.LocalAddr() == nil { + t.Error("ListenPacket returned nil address") + } +} + +func TestInterfaces(t *testing.T) { + ifaces, err := net.Interfaces() + if err != nil { + t.Fatalf("Interfaces error: %v", err) + } + if len(ifaces) == 0 { + t.Skip("No interfaces found") + } +} + +func TestInterfaceByName(t *testing.T) { + ifaces, err := net.Interfaces() + if err != nil || len(ifaces) == 0 { + t.Skip("No interfaces found") + } + iface, err := net.InterfaceByName(ifaces[0].Name) + if err != nil { + t.Fatalf("InterfaceByName error: %v", err) + } + if iface == nil { + t.Error("InterfaceByName returned nil") + } +} + +func TestInterfaceByIndex(t *testing.T) { + ifaces, err := net.Interfaces() + if err != nil || len(ifaces) == 0 { + t.Skip("No interfaces found") + } + iface, err := net.InterfaceByIndex(ifaces[0].Index) + if err != nil { + t.Fatalf("InterfaceByIndex error: %v", err) + } + if iface == nil { + t.Error("InterfaceByIndex returned nil") + } +} + +func TestInterfaceAddrs(t *testing.T) { + addrs, err := net.InterfaceAddrs() + if err != nil { + t.Fatalf("InterfaceAddrs error: %v", err) + } + if len(addrs) == 0 { + t.Skip("No interface addresses found") + } +} + +func TestInterfaceAddrsMethod(t *testing.T) { + ifaces, err := net.Interfaces() + if err != nil || len(ifaces) == 0 { + t.Skip("No interfaces found") + } + addrs, err := ifaces[0].Addrs() + if err != nil { + t.Fatalf("Interface.Addrs error: %v", err) + } + _ = addrs +} + +func TestInterfaceMulticastAddrs(t *testing.T) { + ifaces, err := net.Interfaces() + if err != nil || len(ifaces) == 0 { + t.Skip("No interfaces found") + } + addrs, err := ifaces[0].MulticastAddrs() + if err != nil { + t.Fatalf("Interface.MulticastAddrs error: %v", err) + } + _ = addrs +} + +func TestFlagsString(t *testing.T) { + flags := net.FlagUp | net.FlagLoopback + s := flags.String() + if s == "" { + t.Error("Flags.String() returned empty string") + } +} + +func TestDialer(t *testing.T) { + d := &net.Dialer{ + Timeout: time.Second, + } + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + go func() { + ln.Accept() + }() + + conn, err := d.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dialer.Dial error: %v", err) + } + defer conn.Close() +} + +func TestDialerDialContext(t *testing.T) { + d := &net.Dialer{} + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + go func() { + ln.Accept() + }() + + ctx := context.Background() + conn, err := d.DialContext(ctx, "tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dialer.DialContext error: %v", err) + } + defer conn.Close() +} + +func TestDialerMultipathTCP(t *testing.T) { + d := &net.Dialer{} + d.SetMultipathTCP(true) + if !d.MultipathTCP() { + t.Error("Dialer.MultipathTCP() = false after SetMultipathTCP(true)") + } +} + +func TestBuffersRead(t *testing.T) { + buffers := net.Buffers{ + []byte("hello"), + []byte("world"), + } + buf := make([]byte, 10) + n, err := buffers.Read(buf) + if err != nil && err != io.EOF { + t.Fatalf("Buffers.Read error: %v", err) + } + if n > 10 { + t.Errorf("Read too many bytes: %d", n) + } +} + +func TestBuffersWriteTo(t *testing.T) { + buffers := net.Buffers{ + []byte("hello"), + []byte("world"), + } + c1, c2 := net.Pipe() + defer c1.Close() + defer c2.Close() + + go func() { + buffers.WriteTo(c1) + }() + + buf := make([]byte, 10) + n, _ := c2.Read(buf) + if n == 0 { + t.Error("WriteTo wrote no data") + } +} + +func TestAddrError(t *testing.T) { + e := &net.AddrError{ + Err: "test error", + Addr: "127.0.0.1", + } + got := e.Error() + if got == "" { + t.Error("AddrError.Error() returned empty string") + } + if e.Timeout() { + t.Error("AddrError.Timeout() = true") + } + if e.Temporary() { + t.Error("AddrError.Temporary() = true") + } +} + +func TestDNSError(t *testing.T) { + e := &net.DNSError{ + Err: "test error", + Name: "example.com", + } + got := e.Error() + if got == "" { + t.Error("DNSError.Error() returned empty string") + } +} + +func TestDNSConfigError(t *testing.T) { + e := &net.DNSConfigError{ + Err: &net.DNSError{Err: "test"}, + } + got := e.Error() + if got == "" { + t.Error("DNSConfigError.Error() returned empty string") + } +} + +func TestDefaultResolver(t *testing.T) { + if net.DefaultResolver == nil { + t.Error("DefaultResolver is nil") + } +} + +func TestResolverLookupHost(t *testing.T) { + r := net.DefaultResolver + addrs, err := r.LookupHost(context.Background(), "localhost") + if err != nil { + t.Fatalf("LookupHost error: %v", err) + } + if len(addrs) == 0 { + t.Error("LookupHost returned no addresses") + } +} + +func TestResolverLookupAddr(t *testing.T) { + r := net.DefaultResolver + names, err := r.LookupAddr(context.Background(), "127.0.0.1") + if err != nil { + t.Skip("LookupAddr failed (may not have reverse DNS)") + } + _ = names +} + +func TestResolverLookupCNAME(t *testing.T) { + r := net.DefaultResolver + cname, err := r.LookupCNAME(context.Background(), "localhost") + if err != nil { + t.Skipf("LookupCNAME error (DNS configuration issue): %v", err) + } + if cname == "" { + t.Error("LookupCNAME returned empty string") + } +} + +func TestLookupHost(t *testing.T) { + addrs, err := net.LookupHost("localhost") + if err != nil { + t.Fatalf("LookupHost error: %v", err) + } + if len(addrs) == 0 { + t.Error("LookupHost returned no addresses") + } +} + +func TestLookupIP(t *testing.T) { + ips, err := net.LookupIP("localhost") + if err != nil { + t.Fatalf("LookupIP error: %v", err) + } + if len(ips) == 0 { + t.Error("LookupIP returned no IPs") + } +} + +func TestLookupAddr(t *testing.T) { + names, err := net.LookupAddr("127.0.0.1") + if err != nil { + t.Skip("LookupAddr failed (may not have reverse DNS)") + } + _ = names +} + +func TestLookupCNAME(t *testing.T) { + cname, err := net.LookupCNAME("localhost") + if err != nil { + t.Skipf("LookupCNAME error (DNS configuration issue): %v", err) + } + if cname == "" { + t.Error("LookupCNAME returned empty string") + } +} + +func TestLookupPort(t *testing.T) { + port, err := net.LookupPort("tcp", "http") + if err != nil { + t.Fatalf("LookupPort error: %v", err) + } + if port != 80 { + t.Errorf("LookupPort() = %d, want 80", port) + } +} + +func TestLookupTXT(t *testing.T) { + records, err := net.LookupTXT("localhost") + if err != nil { + t.Skip("LookupTXT failed") + } + _ = records +} + +func TestLookupMX(t *testing.T) { + records, err := net.LookupMX("localhost") + if err != nil { + t.Skip("LookupMX failed") + } + _ = records +} + +func TestLookupNS(t *testing.T) { + records, err := net.LookupNS("localhost") + if err != nil { + t.Skip("LookupNS failed") + } + _ = records +} + +func TestLookupSRV(t *testing.T) { + cname, records, err := net.LookupSRV("xmpp-server", "tcp", "localhost") + if err != nil { + t.Skip("LookupSRV failed") + } + _, _ = cname, records +} + +func TestIPMaskSize(t *testing.T) { + mask := net.CIDRMask(24, 32) + ones, bits := mask.Size() + if ones != 24 || bits != 32 { + t.Errorf("Size() = (%d, %d), want (24, 32)", ones, bits) + } +} + +func TestIPMaskString(t *testing.T) { + mask := net.CIDRMask(24, 32) + s := mask.String() + if s == "" { + t.Error("IPMask.String() returned empty string") + } +} + +func TestCIDRMask(t *testing.T) { + mask := net.CIDRMask(24, 32) + if mask == nil { + t.Error("CIDRMask returned nil") + } +} + +func TestIPv4Mask(t *testing.T) { + mask := net.IPv4Mask(255, 255, 255, 0) + if mask == nil { + t.Error("IPv4Mask returned nil") + } +} + +func TestErrClosed(t *testing.T) { + if net.ErrClosed == nil { + t.Error("ErrClosed is nil") + } +} + +func TestIPv4Constants(t *testing.T) { + if net.IPv4len != 4 { + t.Errorf("IPv4len = %d, want 4", net.IPv4len) + } + if net.IPv6len != 16 { + t.Errorf("IPv6len = %d, want 16", net.IPv6len) + } + if net.IPv4zero == nil { + t.Error("IPv4zero is nil") + } + if net.IPv4bcast == nil { + t.Error("IPv4bcast is nil") + } + if net.IPv4allsys == nil { + t.Error("IPv4allsys is nil") + } + if net.IPv4allrouter == nil { + t.Error("IPv4allrouter is nil") + } + if net.IPv6zero == nil { + t.Error("IPv6zero is nil") + } + if net.IPv6unspecified == nil { + t.Error("IPv6unspecified is nil") + } + if net.IPv6loopback == nil { + t.Error("IPv6loopback is nil") + } + if net.IPv6interfacelocalallnodes == nil { + t.Error("IPv6interfacelocalallnodes is nil") + } + if net.IPv6linklocalallnodes == nil { + t.Error("IPv6linklocalallnodes is nil") + } + if net.IPv6linklocalallrouters == nil { + t.Error("IPv6linklocalallrouters is nil") + } +} + +func TestTCPConnMethods(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + go func() { + conn, _ := ln.Accept() + if conn != nil { + conn.Close() + } + }() + + conn, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial error: %v", err) + } + tcpConn := conn.(*net.TCPConn) + defer tcpConn.Close() + + if err := tcpConn.SetKeepAlive(true); err != nil { + t.Errorf("SetKeepAlive error: %v", err) + } + + if err := tcpConn.SetKeepAlivePeriod(time.Second); err != nil { + t.Errorf("SetKeepAlivePeriod error: %v", err) + } + + if err := tcpConn.SetLinger(0); err != nil { + t.Errorf("SetLinger error: %v", err) + } + + if err := tcpConn.SetNoDelay(true); err != nil { + t.Errorf("SetNoDelay error: %v", err) + } + + if err := tcpConn.SetReadBuffer(4096); err != nil { + t.Errorf("SetReadBuffer error: %v", err) + } + + if err := tcpConn.SetWriteBuffer(4096); err != nil { + t.Errorf("SetWriteBuffer error: %v", err) + } + + // CloseRead/CloseWrite may fail on some platforms if connection is not established + if err := tcpConn.CloseRead(); err != nil { + t.Logf("CloseRead error (may be platform-specific): %v", err) + } + + if err := tcpConn.CloseWrite(); err != nil { + t.Logf("CloseWrite error (may be platform-specific): %v", err) + } +} + +func TestTCPAddrAddrPort(t *testing.T) { + addr := &net.TCPAddr{ + IP: net.ParseIP("192.168.1.1"), + Port: 8080, + } + ap := addr.AddrPort() + if !ap.IsValid() { + t.Error("TCPAddr.AddrPort() returned invalid AddrPort") + } +} + +func TestUDPAddrAddrPort(t *testing.T) { + addr := &net.UDPAddr{ + IP: net.ParseIP("192.168.1.1"), + Port: 8080, + } + ap := addr.AddrPort() + if !ap.IsValid() { + t.Error("UDPAddr.AddrPort() returned invalid AddrPort") + } +} + +func TestUDPConnMethods(t *testing.T) { + conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}) + if err != nil { + t.Fatalf("ListenUDP error: %v", err) + } + defer conn.Close() + + if err := conn.SetReadBuffer(4096); err != nil { + t.Errorf("SetReadBuffer error: %v", err) + } + + if err := conn.SetWriteBuffer(4096); err != nil { + t.Errorf("SetWriteBuffer error: %v", err) + } +} + +func TestTCPListenerMethods(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + tcpLn := ln.(*net.TCPListener) + + if err := tcpLn.SetDeadline(time.Now().Add(time.Second)); err != nil { + t.Errorf("SetDeadline error: %v", err) + } +} + +func TestMXType(t *testing.T) { + mx := &net.MX{ + Host: "mail.example.com", + Pref: 10, + } + if mx.Host != "mail.example.com" { + t.Error("MX.Host not set correctly") + } + if mx.Pref != 10 { + t.Error("MX.Pref not set correctly") + } +} + +func TestNSType(t *testing.T) { + ns := &net.NS{ + Host: "ns1.example.com", + } + if ns.Host != "ns1.example.com" { + t.Error("NS.Host not set correctly") + } +} + +func TestSRVType(t *testing.T) { + srv := &net.SRV{ + Target: "target.example.com", + Port: 8080, + Priority: 10, + Weight: 20, + } + if srv.Target != "target.example.com" { + t.Error("SRV.Target not set correctly") + } +} + +func TestResolverLookupMethods(t *testing.T) { + r := net.DefaultResolver + ctx := context.Background() + + _, err := r.LookupIPAddr(ctx, "localhost") + if err != nil { + t.Errorf("LookupIPAddr error: %v", err) + } + + _, err = r.LookupNetIP(ctx, "ip", "localhost") + if err != nil { + t.Errorf("LookupNetIP error: %v", err) + } + + _, err = r.LookupPort(ctx, "tcp", "http") + if err != nil { + t.Errorf("LookupPort error: %v", err) + } + + _, err = r.LookupMX(ctx, "localhost") + if err != nil { + t.Skip("LookupMX failed") + } + + _, err = r.LookupNS(ctx, "localhost") + if err != nil { + t.Skip("LookupNS failed") + } + + _, err = r.LookupTXT(ctx, "localhost") + if err != nil { + t.Skip("LookupTXT failed") + } + + _, _, err = r.LookupSRV(ctx, "xmpp-server", "tcp", "localhost") + if err != nil { + t.Skip("LookupSRV failed") + } + + _, err = r.LookupIP(ctx, "ip4", "localhost") + if err != nil { + t.Errorf("LookupIP error: %v", err) + } +} + +func TestInvalidAddrError(t *testing.T) { + var e net.InvalidAddrError = "invalid" + if e.Error() == "" { + t.Error("InvalidAddrError.Error() returned empty string") + } + if e.Timeout() { + t.Error("InvalidAddrError.Timeout() = true") + } + if e.Temporary() { + t.Error("InvalidAddrError.Temporary() = true") + } +} + +func TestUnknownNetworkError(t *testing.T) { + var e net.UnknownNetworkError = "unknown" + if e.Error() == "" { + t.Error("UnknownNetworkError.Error() returned empty string") + } + if e.Timeout() { + t.Error("UnknownNetworkError.Timeout() = true") + } + if e.Temporary() { + t.Error("UnknownNetworkError.Temporary() = true") + } +} + +func TestParseError(t *testing.T) { + e := &net.ParseError{ + Type: "IP address", + Text: "invalid", + } + if e.Error() == "" { + t.Error("ParseError.Error() returned empty string") + } + if e.Timeout() { + t.Error("ParseError.Timeout() = true") + } + if e.Temporary() { + t.Error("ParseError.Temporary() = true") + } +} + +func TestOpError(t *testing.T) { + e := &net.OpError{ + Op: "dial", + Net: "tcp", + Addr: &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 8080}, + Err: io.EOF, + } + if e.Error() == "" { + t.Error("OpError.Error() returned empty string") + } + if e.Timeout() { + t.Error("OpError.Timeout() = true for non-timeout error") + } + if e.Temporary() { + t.Error("OpError.Temporary() = true for non-temporary error") + } + if e.Unwrap() == nil { + t.Error("OpError.Unwrap() returned nil") + } +} + +func TestDNSErrorMethods(t *testing.T) { + e := &net.DNSError{ + Err: "test error", + Name: "example.com", + } + if e.Timeout() { + t.Error("DNSError.Timeout() = true for non-timeout error") + } + if e.Temporary() { + t.Error("DNSError.Temporary() = true for non-temporary error") + } + if e.Unwrap() != nil { + t.Error("DNSError.Unwrap() should be nil for plain DNSError") + } +} + +func TestDNSConfigErrorMethods(t *testing.T) { + e := &net.DNSConfigError{ + Err: &net.DNSError{Err: "test"}, + } + if e.Timeout() { + t.Error("DNSConfigError.Timeout() = true for non-timeout error") + } + if e.Temporary() { + t.Error("DNSConfigError.Temporary() = true for non-temporary error") + } + if e.Unwrap() == nil { + t.Error("DNSConfigError.Unwrap() returned nil") + } +} + +func TestListenConfig(t *testing.T) { + lc := &net.ListenConfig{} + lc.SetMultipathTCP(true) + if !lc.MultipathTCP() { + t.Error("MultipathTCP() = false after SetMultipathTCP(true)") + } + + ln, err := lc.Listen(context.Background(), "tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("ListenConfig.Listen error: %v", err) + } + defer ln.Close() + + pc, err := lc.ListenPacket(context.Background(), "udp", "127.0.0.1:0") + if err != nil { + t.Fatalf("ListenConfig.ListenPacket error: %v", err) + } + defer pc.Close() +} + +func TestErrWriteToConnected(t *testing.T) { + if net.ErrWriteToConnected == nil { + t.Error("ErrWriteToConnected is nil") + } +} + +func TestKeepAliveConfig(t *testing.T) { + kac := net.KeepAliveConfig{ + Enable: true, + Idle: time.Second, + Interval: time.Second, + Count: 3, + } + if !kac.Enable { + t.Error("KeepAliveConfig.Enable = false") + } +} + +func TestTCPConnKeepAliveConfig(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + go func() { + conn, _ := ln.Accept() + if conn != nil { + conn.Close() + } + }() + + conn, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial error: %v", err) + } + tcpConn := conn.(*net.TCPConn) + defer tcpConn.Close() + + kac := net.KeepAliveConfig{ + Enable: true, + Idle: time.Second, + } + if err := tcpConn.SetKeepAliveConfig(kac); err != nil { + t.Errorf("SetKeepAliveConfig error: %v", err) + } +} + +func TestTCPConnMultipathTCP(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + go func() { + conn, _ := ln.Accept() + if conn != nil { + conn.Close() + } + }() + + conn, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial error: %v", err) + } + tcpConn := conn.(*net.TCPConn) + defer tcpConn.Close() + + if _, err := tcpConn.MultipathTCP(); err != nil { + t.Logf("TCPConn.MultipathTCP() error: %v", err) + } +} + +func TestTCPListenerAcceptTCP(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen error: %v", err) + } + defer ln.Close() + + tcpLn := ln.(*net.TCPListener) + + go func() { + net.Dial("tcp", ln.Addr().String()) + }() + + conn, err := tcpLn.AcceptTCP() + if err != nil { + t.Fatalf("AcceptTCP error: %v", err) + } + defer conn.Close() +} diff --git a/test/std/net/netip/go126_symbols_test.go b/test/std/net/netip/go126_symbols_test.go new file mode 100644 index 0000000000..a06ce340e2 --- /dev/null +++ b/test/std/net/netip/go126_symbols_test.go @@ -0,0 +1,26 @@ +//go:build go1.26 + +package netip_test + +import ( + "net/netip" + "testing" +) + +func TestPrefixCompare(t *testing.T) { + short := netip.MustParsePrefix("192.0.2.0/24") + long := netip.MustParsePrefix("192.0.2.0/25") + next := netip.MustParsePrefix("192.0.3.0/24") + if got := short.Compare(long); got >= 0 { + t.Fatalf("short.Compare(long) = %d, want negative", got) + } + if got := long.Compare(short); got <= 0 { + t.Fatalf("long.Compare(short) = %d, want positive", got) + } + if got := short.Compare(short); got != 0 { + t.Fatalf("short.Compare(short) = %d, want zero", got) + } + if got := short.Compare(next); got >= 0 { + t.Fatalf("short.Compare(next) = %d, want negative", got) + } +} diff --git a/test/std/net/netip/netip_test.go b/test/std/net/netip/netip_test.go new file mode 100644 index 0000000000..c4643fd211 --- /dev/null +++ b/test/std/net/netip/netip_test.go @@ -0,0 +1,863 @@ +package netip_test + +import ( + "net/netip" + "testing" +) + +func TestAddrFrom4(t *testing.T) { + addr := netip.AddrFrom4([4]byte{192, 168, 1, 1}) + if !addr.IsValid() { + t.Error("AddrFrom4 returned invalid address") + } + if !addr.Is4() { + t.Error("AddrFrom4 did not return IPv4 address") + } +} + +func TestAddrFrom16(t *testing.T) { + addr := netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) + if !addr.IsValid() { + t.Error("AddrFrom16 returned invalid address") + } + if !addr.Is6() { + t.Error("AddrFrom16 did not return IPv6 address") + } +} + +func TestAddrFromSlice(t *testing.T) { + tests := []struct { + slice []byte + ok bool + }{ + {[]byte{192, 168, 1, 1}, true}, + {[]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, true}, + {[]byte{1, 2, 3}, false}, + {nil, false}, + } + for _, tt := range tests { + addr, ok := netip.AddrFromSlice(tt.slice) + if ok != tt.ok { + t.Errorf("AddrFromSlice(%v) ok = %v, want %v", tt.slice, ok, tt.ok) + } + if ok && !addr.IsValid() { + t.Error("AddrFromSlice returned invalid address when ok=true") + } + } +} + +func TestIPv4Unspecified(t *testing.T) { + addr := netip.IPv4Unspecified() + if !addr.IsValid() { + t.Error("IPv4Unspecified returned invalid address") + } + if !addr.IsUnspecified() { + t.Error("IPv4Unspecified did not return unspecified address") + } +} + +func TestIPv6Unspecified(t *testing.T) { + addr := netip.IPv6Unspecified() + if !addr.IsValid() { + t.Error("IPv6Unspecified returned invalid address") + } + if !addr.IsUnspecified() { + t.Error("IPv6Unspecified did not return unspecified address") + } +} + +func TestIPv6Loopback(t *testing.T) { + addr := netip.IPv6Loopback() + if !addr.IsValid() { + t.Error("IPv6Loopback returned invalid address") + } + if !addr.IsLoopback() { + t.Error("IPv6Loopback did not return loopback address") + } +} + +func TestIPv6LinkLocalAllNodes(t *testing.T) { + addr := netip.IPv6LinkLocalAllNodes() + if !addr.IsValid() { + t.Error("IPv6LinkLocalAllNodes returned invalid address") + } + if !addr.IsMulticast() { + t.Error("IPv6LinkLocalAllNodes did not return multicast address") + } +} + +func TestIPv6LinkLocalAllRouters(t *testing.T) { + addr := netip.IPv6LinkLocalAllRouters() + if !addr.IsValid() { + t.Error("IPv6LinkLocalAllRouters returned invalid address") + } + if !addr.IsMulticast() { + t.Error("IPv6LinkLocalAllRouters did not return multicast address") + } +} + +func TestParseAddr(t *testing.T) { + tests := []struct { + input string + wantErr bool + }{ + {"192.168.1.1", false}, + {"::1", false}, + {"2001:db8::1", false}, + {"invalid", true}, + {"", true}, + } + for _, tt := range tests { + addr, err := netip.ParseAddr(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("ParseAddr(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr) + continue + } + if !tt.wantErr && !addr.IsValid() { + t.Errorf("ParseAddr(%q) returned invalid address", tt.input) + } + } +} + +func TestMustParseAddr(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("MustParseAddr did not panic on invalid input") + } + }() + addr := netip.MustParseAddr("192.168.1.1") + if !addr.IsValid() { + t.Error("MustParseAddr returned invalid address") + } + netip.MustParseAddr("invalid") +} + +func TestAddrIs4(t *testing.T) { + addr4 := netip.MustParseAddr("192.168.1.1") + if !addr4.Is4() { + t.Error("IPv4 address Is4() = false") + } + addr6 := netip.MustParseAddr("::1") + if addr6.Is4() { + t.Error("IPv6 address Is4() = true") + } +} + +func TestAddrIs6(t *testing.T) { + addr6 := netip.MustParseAddr("::1") + if !addr6.Is6() { + t.Error("IPv6 address Is6() = false") + } + addr4 := netip.MustParseAddr("192.168.1.1") + if addr4.Is6() { + t.Error("IPv4 address Is6() = true") + } +} + +func TestAddrIs4In6(t *testing.T) { + addr := netip.MustParseAddr("::ffff:192.168.1.1") + if !addr.Is4In6() { + t.Error("IPv4-in-IPv6 address Is4In6() = false") + } +} + +func TestAddrIsLoopback(t *testing.T) { + tests := []struct { + addr string + want bool + }{ + {"127.0.0.1", true}, + {"::1", true}, + {"192.168.1.1", false}, + } + for _, tt := range tests { + addr := netip.MustParseAddr(tt.addr) + if got := addr.IsLoopback(); got != tt.want { + t.Errorf("Addr(%q).IsLoopback() = %v, want %v", tt.addr, got, tt.want) + } + } +} + +func TestAddrIsMulticast(t *testing.T) { + tests := []struct { + addr string + want bool + }{ + {"224.0.0.1", true}, + {"ff02::1", true}, + {"192.168.1.1", false}, + } + for _, tt := range tests { + addr := netip.MustParseAddr(tt.addr) + if got := addr.IsMulticast(); got != tt.want { + t.Errorf("Addr(%q).IsMulticast() = %v, want %v", tt.addr, got, tt.want) + } + } +} + +func TestAddrIsPrivate(t *testing.T) { + tests := []struct { + addr string + want bool + }{ + {"192.168.1.1", true}, + {"10.0.0.1", true}, + {"172.16.0.1", true}, + {"8.8.8.8", false}, + } + for _, tt := range tests { + addr := netip.MustParseAddr(tt.addr) + if got := addr.IsPrivate(); got != tt.want { + t.Errorf("Addr(%q).IsPrivate() = %v, want %v", tt.addr, got, tt.want) + } + } +} + +func TestAddrIsGlobalUnicast(t *testing.T) { + addr := netip.MustParseAddr("8.8.8.8") + if !addr.IsGlobalUnicast() { + t.Error("Public IPv4 IsGlobalUnicast() = false") + } +} + +func TestAddrIsLinkLocalUnicast(t *testing.T) { + addr := netip.MustParseAddr("169.254.1.1") + if !addr.IsLinkLocalUnicast() { + t.Error("Link-local address IsLinkLocalUnicast() = false") + } +} + +func TestAddrIsLinkLocalMulticast(t *testing.T) { + addr := netip.MustParseAddr("224.0.0.1") + if !addr.IsLinkLocalMulticast() { + t.Error("Link-local multicast IsLinkLocalMulticast() = false") + } +} + +func TestAddrIsInterfaceLocalMulticast(t *testing.T) { + addr := netip.MustParseAddr("ff01::1") + if !addr.IsInterfaceLocalMulticast() { + t.Error("Interface-local multicast IsInterfaceLocalMulticast() = false") + } +} + +func TestAddrIsUnspecified(t *testing.T) { + addr := netip.IPv4Unspecified() + if !addr.IsUnspecified() { + t.Error("Unspecified address IsUnspecified() = false") + } +} + +func TestAddrIsValid(t *testing.T) { + addr := netip.MustParseAddr("192.168.1.1") + if !addr.IsValid() { + t.Error("Valid address IsValid() = false") + } + var zero netip.Addr + if zero.IsValid() { + t.Error("Zero address IsValid() = true") + } +} + +func TestAddrString(t *testing.T) { + tests := []struct { + input string + }{ + {"192.168.1.1"}, + {"::1"}, + {"2001:db8::1"}, + } + for _, tt := range tests { + addr := netip.MustParseAddr(tt.input) + got := addr.String() + if got != tt.input { + t.Errorf("Addr(%q).String() = %q, want %q", tt.input, got, tt.input) + } + } +} + +func TestAddrStringExpanded(t *testing.T) { + addr := netip.MustParseAddr("2001:db8::1") + expanded := addr.StringExpanded() + if expanded == "2001:db8::1" { + t.Error("StringExpanded() returned abbreviated form") + } +} + +func TestAddrAs4(t *testing.T) { + addr := netip.MustParseAddr("192.168.1.1") + a4 := addr.As4() + if a4 != [4]byte{192, 168, 1, 1} { + t.Errorf("As4() = %v, want [192 168 1 1]", a4) + } +} + +func TestAddrAs16(t *testing.T) { + addr := netip.MustParseAddr("::1") + a16 := addr.As16() + if a16[15] != 1 { + t.Error("As16() did not return correct IPv6 bytes") + } +} + +func TestAddrAsSlice(t *testing.T) { + addr := netip.MustParseAddr("192.168.1.1") + slice := addr.AsSlice() + if len(slice) != 4 { + t.Errorf("AsSlice() len = %d, want 4", len(slice)) + } +} + +func TestAddrBitLen(t *testing.T) { + addr4 := netip.MustParseAddr("192.168.1.1") + if addr4.BitLen() != 32 { + t.Errorf("IPv4 BitLen() = %d, want 32", addr4.BitLen()) + } + addr6 := netip.MustParseAddr("::1") + if addr6.BitLen() != 128 { + t.Errorf("IPv6 BitLen() = %d, want 128", addr6.BitLen()) + } +} + +func TestAddrZone(t *testing.T) { + addr := netip.MustParseAddr("fe80::1%eth0") + if addr.Zone() != "eth0" { + t.Errorf("Zone() = %q, want %q", addr.Zone(), "eth0") + } +} + +func TestAddrWithZone(t *testing.T) { + addr := netip.MustParseAddr("fe80::1") + withZone := addr.WithZone("eth0") + if withZone.Zone() != "eth0" { + t.Errorf("WithZone().Zone() = %q, want %q", withZone.Zone(), "eth0") + } +} + +func TestAddrCompare(t *testing.T) { + addr1 := netip.MustParseAddr("192.168.1.1") + addr2 := netip.MustParseAddr("192.168.1.2") + if addr1.Compare(addr2) >= 0 { + t.Error("Compare() did not return negative for smaller address") + } + if addr2.Compare(addr1) <= 0 { + t.Error("Compare() did not return positive for larger address") + } + if addr1.Compare(addr1) != 0 { + t.Error("Compare() did not return 0 for equal addresses") + } +} + +func TestAddrLess(t *testing.T) { + addr1 := netip.MustParseAddr("192.168.1.1") + addr2 := netip.MustParseAddr("192.168.1.2") + if !addr1.Less(addr2) { + t.Error("Less() = false for smaller address") + } + if addr2.Less(addr1) { + t.Error("Less() = true for larger address") + } +} + +func TestAddrNext(t *testing.T) { + addr := netip.MustParseAddr("192.168.1.1") + next := addr.Next() + want := netip.MustParseAddr("192.168.1.2") + if next != want { + t.Errorf("Next() = %v, want %v", next, want) + } +} + +func TestAddrPrev(t *testing.T) { + addr := netip.MustParseAddr("192.168.1.2") + prev := addr.Prev() + want := netip.MustParseAddr("192.168.1.1") + if prev != want { + t.Errorf("Prev() = %v, want %v", prev, want) + } +} + +func TestAddrUnmap(t *testing.T) { + addr := netip.MustParseAddr("::ffff:192.168.1.1") + unmapped := addr.Unmap() + if !unmapped.Is4() { + t.Error("Unmap() did not return IPv4 address") + } +} + +func TestAddrPrefix(t *testing.T) { + addr := netip.MustParseAddr("192.168.1.1") + prefix, err := addr.Prefix(24) + if err != nil { + t.Fatalf("Prefix() error = %v", err) + } + if !prefix.IsValid() { + t.Error("Prefix() returned invalid prefix") + } +} + +func TestAddrMarshalText(t *testing.T) { + addr := netip.MustParseAddr("192.168.1.1") + text, err := addr.MarshalText() + if err != nil { + t.Fatalf("MarshalText() error = %v", err) + } + if string(text) != "192.168.1.1" { + t.Errorf("MarshalText() = %q, want %q", string(text), "192.168.1.1") + } +} + +func TestAddrUnmarshalText(t *testing.T) { + var addr netip.Addr + err := addr.UnmarshalText([]byte("192.168.1.1")) + if err != nil { + t.Fatalf("UnmarshalText() error = %v", err) + } + if !addr.IsValid() { + t.Error("UnmarshalText() returned invalid address") + } +} + +func TestAddrMarshalBinary(t *testing.T) { + addr := netip.MustParseAddr("192.168.1.1") + data, err := addr.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary() error = %v", err) + } + if len(data) == 0 { + t.Error("MarshalBinary() returned empty data") + } +} + +func TestAddrUnmarshalBinary(t *testing.T) { + original := netip.MustParseAddr("192.168.1.1") + data, err := original.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary() error = %v", err) + } + var addr netip.Addr + err = addr.UnmarshalBinary(data) + if err != nil { + t.Fatalf("UnmarshalBinary() error = %v", err) + } + if addr != original { + t.Errorf("UnmarshalBinary() = %v, want %v", addr, original) + } +} + +func TestAddrAppendTo(t *testing.T) { + addr := netip.MustParseAddr("192.168.1.1") + b := []byte("prefix:") + result := addr.AppendTo(b) + if len(result) <= len(b) { + t.Error("AppendTo() did not append data") + } +} + +func TestAddrAppendText(t *testing.T) { + addr := netip.MustParseAddr("192.168.1.1") + b := []byte("prefix:") + result, err := addr.AppendText(b) + if err != nil { + t.Fatalf("AppendText() error = %v", err) + } + if len(result) <= len(b) { + t.Error("AppendText() did not append data") + } +} + +func TestAddrAppendBinary(t *testing.T) { + addr := netip.MustParseAddr("192.168.1.1") + b := []byte("prefix") + result, err := addr.AppendBinary(b) + if err != nil { + t.Fatalf("AppendBinary() error = %v", err) + } + if len(result) <= len(b) { + t.Error("AppendBinary() did not append data") + } +} + +func TestAddrPortFrom(t *testing.T) { + addr := netip.MustParseAddr("192.168.1.1") + ap := netip.AddrPortFrom(addr, 8080) + if !ap.IsValid() { + t.Error("AddrPortFrom returned invalid AddrPort") + } +} + +func TestParseAddrPort(t *testing.T) { + tests := []struct { + input string + wantErr bool + }{ + {"192.168.1.1:8080", false}, + {"[::1]:8080", false}, + {"invalid", true}, + } + for _, tt := range tests { + ap, err := netip.ParseAddrPort(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("ParseAddrPort(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr) + continue + } + if !tt.wantErr && !ap.IsValid() { + t.Errorf("ParseAddrPort(%q) returned invalid AddrPort", tt.input) + } + } +} + +func TestMustParseAddrPort(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("MustParseAddrPort did not panic on invalid input") + } + }() + ap := netip.MustParseAddrPort("192.168.1.1:8080") + if !ap.IsValid() { + t.Error("MustParseAddrPort returned invalid AddrPort") + } + netip.MustParseAddrPort("invalid") +} + +func TestAddrPortAddr(t *testing.T) { + ap := netip.MustParseAddrPort("192.168.1.1:8080") + addr := ap.Addr() + if !addr.IsValid() { + t.Error("AddrPort.Addr() returned invalid address") + } +} + +func TestAddrPortPort(t *testing.T) { + ap := netip.MustParseAddrPort("192.168.1.1:8080") + if ap.Port() != 8080 { + t.Errorf("Port() = %d, want 8080", ap.Port()) + } +} + +func TestAddrPortIsValid(t *testing.T) { + ap := netip.MustParseAddrPort("192.168.1.1:8080") + if !ap.IsValid() { + t.Error("Valid AddrPort IsValid() = false") + } + var zero netip.AddrPort + if zero.IsValid() { + t.Error("Zero AddrPort IsValid() = true") + } +} + +func TestAddrPortString(t *testing.T) { + tests := []struct { + input string + }{ + {"192.168.1.1:8080"}, + {"[::1]:8080"}, + } + for _, tt := range tests { + ap := netip.MustParseAddrPort(tt.input) + got := ap.String() + if got != tt.input { + t.Errorf("AddrPort(%q).String() = %q, want %q", tt.input, got, tt.input) + } + } +} + +func TestAddrPortCompare(t *testing.T) { + ap1 := netip.MustParseAddrPort("192.168.1.1:8080") + ap2 := netip.MustParseAddrPort("192.168.1.1:8081") + if ap1.Compare(ap2) >= 0 { + t.Error("Compare() did not return negative for smaller port") + } +} + +func TestAddrPortMarshalText(t *testing.T) { + ap := netip.MustParseAddrPort("192.168.1.1:8080") + text, err := ap.MarshalText() + if err != nil { + t.Fatalf("MarshalText() error = %v", err) + } + if len(text) == 0 { + t.Error("MarshalText() returned empty data") + } +} + +func TestAddrPortUnmarshalText(t *testing.T) { + var ap netip.AddrPort + err := ap.UnmarshalText([]byte("192.168.1.1:8080")) + if err != nil { + t.Fatalf("UnmarshalText() error = %v", err) + } + if !ap.IsValid() { + t.Error("UnmarshalText() returned invalid AddrPort") + } +} + +func TestAddrPortMarshalBinary(t *testing.T) { + ap := netip.MustParseAddrPort("192.168.1.1:8080") + data, err := ap.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary() error = %v", err) + } + if len(data) == 0 { + t.Error("MarshalBinary() returned empty data") + } +} + +func TestAddrPortUnmarshalBinary(t *testing.T) { + original := netip.MustParseAddrPort("192.168.1.1:8080") + data, err := original.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary() error = %v", err) + } + var ap netip.AddrPort + err = ap.UnmarshalBinary(data) + if err != nil { + t.Fatalf("UnmarshalBinary() error = %v", err) + } + if ap != original { + t.Errorf("UnmarshalBinary() = %v, want %v", ap, original) + } +} + +func TestAddrPortAppendTo(t *testing.T) { + ap := netip.MustParseAddrPort("192.168.1.1:8080") + b := []byte("prefix:") + result := ap.AppendTo(b) + if len(result) <= len(b) { + t.Error("AppendTo() did not append data") + } +} + +func TestAddrPortAppendText(t *testing.T) { + ap := netip.MustParseAddrPort("192.168.1.1:8080") + b := []byte("prefix:") + result, err := ap.AppendText(b) + if err != nil { + t.Fatalf("AppendText() error = %v", err) + } + if len(result) <= len(b) { + t.Error("AppendText() did not append data") + } +} + +func TestAddrPortAppendBinary(t *testing.T) { + ap := netip.MustParseAddrPort("192.168.1.1:8080") + b := []byte("prefix") + result, err := ap.AppendBinary(b) + if err != nil { + t.Fatalf("AppendBinary() error = %v", err) + } + if len(result) <= len(b) { + t.Error("AppendBinary() did not append data") + } +} + +func TestPrefixFrom(t *testing.T) { + addr := netip.MustParseAddr("192.168.1.0") + prefix := netip.PrefixFrom(addr, 24) + if !prefix.IsValid() { + t.Error("PrefixFrom returned invalid Prefix") + } +} + +func TestParsePrefix(t *testing.T) { + tests := []struct { + input string + wantErr bool + }{ + {"192.168.1.0/24", false}, + {"2001:db8::/32", false}, + {"invalid", true}, + } + for _, tt := range tests { + prefix, err := netip.ParsePrefix(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("ParsePrefix(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr) + continue + } + if !tt.wantErr && !prefix.IsValid() { + t.Errorf("ParsePrefix(%q) returned invalid Prefix", tt.input) + } + } +} + +func TestMustParsePrefix(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("MustParsePrefix did not panic on invalid input") + } + }() + prefix := netip.MustParsePrefix("192.168.1.0/24") + if !prefix.IsValid() { + t.Error("MustParsePrefix returned invalid Prefix") + } + netip.MustParsePrefix("invalid") +} + +func TestPrefixAddr(t *testing.T) { + prefix := netip.MustParsePrefix("192.168.1.0/24") + addr := prefix.Addr() + if !addr.IsValid() { + t.Error("Prefix.Addr() returned invalid address") + } +} + +func TestPrefixBits(t *testing.T) { + prefix := netip.MustParsePrefix("192.168.1.0/24") + if prefix.Bits() != 24 { + t.Errorf("Bits() = %d, want 24", prefix.Bits()) + } +} + +func TestPrefixIsValid(t *testing.T) { + prefix := netip.MustParsePrefix("192.168.1.0/24") + if !prefix.IsValid() { + t.Error("Valid Prefix IsValid() = false") + } + var zero netip.Prefix + if zero.IsValid() { + t.Error("Zero Prefix IsValid() = true") + } +} + +func TestPrefixString(t *testing.T) { + tests := []struct { + input string + }{ + {"192.168.1.0/24"}, + {"2001:db8::/32"}, + } + for _, tt := range tests { + prefix := netip.MustParsePrefix(tt.input) + got := prefix.String() + if got != tt.input { + t.Errorf("Prefix(%q).String() = %q, want %q", tt.input, got, tt.input) + } + } +} + +func TestPrefixContains(t *testing.T) { + prefix := netip.MustParsePrefix("192.168.1.0/24") + addr1 := netip.MustParseAddr("192.168.1.1") + addr2 := netip.MustParseAddr("192.168.2.1") + if !prefix.Contains(addr1) { + t.Error("Contains() = false for address in prefix") + } + if prefix.Contains(addr2) { + t.Error("Contains() = true for address outside prefix") + } +} + +func TestPrefixOverlaps(t *testing.T) { + prefix1 := netip.MustParsePrefix("192.168.1.0/24") + prefix2 := netip.MustParsePrefix("192.168.1.128/25") + prefix3 := netip.MustParsePrefix("192.168.2.0/24") + if !prefix1.Overlaps(prefix2) { + t.Error("Overlaps() = false for overlapping prefixes") + } + if prefix1.Overlaps(prefix3) { + t.Error("Overlaps() = true for non-overlapping prefixes") + } +} + +func TestPrefixMasked(t *testing.T) { + prefix := netip.MustParsePrefix("192.168.1.1/24") + masked := prefix.Masked() + want := netip.MustParsePrefix("192.168.1.0/24") + if masked != want { + t.Errorf("Masked() = %v, want %v", masked, want) + } +} + +func TestPrefixIsSingleIP(t *testing.T) { + prefix32 := netip.MustParsePrefix("192.168.1.1/32") + if !prefix32.IsSingleIP() { + t.Error("/32 prefix IsSingleIP() = false") + } + prefix24 := netip.MustParsePrefix("192.168.1.0/24") + if prefix24.IsSingleIP() { + t.Error("/24 prefix IsSingleIP() = true") + } +} + +func TestPrefixMarshalText(t *testing.T) { + prefix := netip.MustParsePrefix("192.168.1.0/24") + text, err := prefix.MarshalText() + if err != nil { + t.Fatalf("MarshalText() error = %v", err) + } + if string(text) != "192.168.1.0/24" { + t.Errorf("MarshalText() = %q, want %q", string(text), "192.168.1.0/24") + } +} + +func TestPrefixUnmarshalText(t *testing.T) { + var prefix netip.Prefix + err := prefix.UnmarshalText([]byte("192.168.1.0/24")) + if err != nil { + t.Fatalf("UnmarshalText() error = %v", err) + } + if !prefix.IsValid() { + t.Error("UnmarshalText() returned invalid Prefix") + } +} + +func TestPrefixMarshalBinary(t *testing.T) { + prefix := netip.MustParsePrefix("192.168.1.0/24") + data, err := prefix.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary() error = %v", err) + } + if len(data) == 0 { + t.Error("MarshalBinary() returned empty data") + } +} + +func TestPrefixUnmarshalBinary(t *testing.T) { + original := netip.MustParsePrefix("192.168.1.0/24") + data, err := original.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary() error = %v", err) + } + var prefix netip.Prefix + err = prefix.UnmarshalBinary(data) + if err != nil { + t.Fatalf("UnmarshalBinary() error = %v", err) + } + if prefix != original { + t.Errorf("UnmarshalBinary() = %v, want %v", prefix, original) + } +} + +func TestPrefixAppendTo(t *testing.T) { + prefix := netip.MustParsePrefix("192.168.1.0/24") + b := []byte("prefix:") + result := prefix.AppendTo(b) + if len(result) <= len(b) { + t.Error("AppendTo() did not append data") + } +} + +func TestPrefixAppendText(t *testing.T) { + prefix := netip.MustParsePrefix("192.168.1.0/24") + b := []byte("prefix:") + result, err := prefix.AppendText(b) + if err != nil { + t.Fatalf("AppendText() error = %v", err) + } + if len(result) <= len(b) { + t.Error("AppendText() did not append data") + } +} + +func TestPrefixAppendBinary(t *testing.T) { + prefix := netip.MustParsePrefix("192.168.1.0/24") + b := []byte("prefix") + result, err := prefix.AppendBinary(b) + if err != nil { + t.Fatalf("AppendBinary() error = %v", err) + } + if len(result) <= len(b) { + t.Error("AppendBinary() did not append data") + } +} diff --git a/test/std/net/rpc/jsonrpc/jsonrpc_test.go b/test/std/net/rpc/jsonrpc/jsonrpc_test.go new file mode 100644 index 0000000000..73b4a25147 --- /dev/null +++ b/test/std/net/rpc/jsonrpc/jsonrpc_test.go @@ -0,0 +1,103 @@ +package jsonrpc_test + +import ( + "fmt" + "net" + "net/rpc" + "net/rpc/jsonrpc" + "sync/atomic" + "testing" +) + +type Args struct { + A, B int +} + +type Arith struct{} + +func (Arith) Mul(args *Args, reply *int) error { + *reply = args.A * args.B + return nil +} + +var serviceID uint64 + +func registerService(t *testing.T) string { + t.Helper() + name := fmt.Sprintf("ArithJSONRPC%d", atomic.AddUint64(&serviceID, 1)) + if err := rpc.RegisterName(name, new(Arith)); err != nil { + t.Fatalf("RegisterName failed: %v", err) + } + return name +} + +func TestServeConnAndNewClient(t *testing.T) { + svc := registerService(t) + serverConn, clientConn := net.Pipe() + defer clientConn.Close() + + go jsonrpc.ServeConn(serverConn) + + client := jsonrpc.NewClient(clientConn) + defer client.Close() + + var reply int + if err := client.Call(svc+".Mul", &Args{A: 6, B: 7}, &reply); err != nil { + t.Fatalf("Call failed: %v", err) + } + if reply != 42 { + t.Fatalf("reply = %d, want 42", reply) + } +} + +func TestCodecHelpers(t *testing.T) { + svc := registerService(t) + serverConn, clientConn := net.Pipe() + defer clientConn.Close() + + serverCodec := jsonrpc.NewServerCodec(serverConn) + go rpc.ServeCodec(serverCodec) + + clientCodec := jsonrpc.NewClientCodec(clientConn) + client := rpc.NewClientWithCodec(clientCodec) + defer client.Close() + + var reply int + if err := client.Call(svc+".Mul", &Args{A: 3, B: 5}, &reply); err != nil { + t.Fatalf("Call with codec failed: %v", err) + } + if reply != 15 { + t.Fatalf("reply = %d, want 15", reply) + } +} + +func TestDial(t *testing.T) { + svc := registerService(t) + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen failed: %v", err) + } + defer ln.Close() + + go func() { + conn, err := ln.Accept() + if err != nil { + return + } + jsonrpc.ServeConn(conn) + }() + + client, err := jsonrpc.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial failed: %v", err) + } + defer client.Close() + + var reply int + if err := client.Call(svc+".Mul", &Args{A: 8, B: 9}, &reply); err != nil { + t.Fatalf("Dialed Call failed: %v", err) + } + if reply != 72 { + t.Fatalf("reply = %d, want 72", reply) + } +} diff --git a/test/std/net/rpc/rpc_test.go b/test/std/net/rpc/rpc_test.go new file mode 100644 index 0000000000..acda844ec9 --- /dev/null +++ b/test/std/net/rpc/rpc_test.go @@ -0,0 +1,258 @@ +package rpc_test + +import ( + "io" + "net" + "net/http" + "net/http/httptest" + "net/rpc" + "strings" + "testing" +) + +type Arith int + +type Args struct { + A int + B int +} + +func (a *Arith) Add(args *Args, reply *int) error { + *reply = args.A + args.B + return nil +} + +func TestPipeRPC(t *testing.T) { + srv := rpc.NewServer() + if err := srv.RegisterName("Arith", new(Arith)); err != nil { + t.Fatalf("RegisterName: %v", err) + } + + sconn, cconn := net.Pipe() + defer sconn.Close() + defer cconn.Close() + + go srv.ServeConn(sconn) + + client := rpc.NewClient(cconn) + defer client.Close() + + var reply int + if err := client.Call("Arith.Add", &Args{A: 2, B: 3}, &reply); err != nil { + t.Fatalf("Call: %v", err) + } + if reply != 5 { + t.Fatalf("reply = %d, want 5", reply) + } + + var reply2 int + call := client.Go("Arith.Add", &Args{A: 40, B: 2}, &reply2, make(chan *rpc.Call, 1)) + result := <-call.Done + if result.Error != nil { + t.Fatalf("Go error: %v", result.Error) + } + if reply2 != 42 { + t.Fatalf("reply2 = %d, want 42", reply2) + } + + if err := client.Close(); err != nil && err != rpc.ErrShutdown { + t.Fatalf("second Close: %v", err) + } +} + +func TestPublicAPISymbols(t *testing.T) { + if rpc.DefaultServer == nil { + t.Fatal("DefaultServer is nil") + } + if rpc.DefaultRPCPath == "" || rpc.DefaultDebugPath == "" { + t.Fatal("default paths must not be empty") + } + if rpc.ErrShutdown == nil { + t.Fatal("ErrShutdown is nil") + } + + var req rpc.Request + req.ServiceMethod = "Arith.Add" + req.Seq = 1 + var resp rpc.Response + resp.ServiceMethod = req.ServiceMethod + resp.Seq = req.Seq + if resp.ServiceMethod != "Arith.Add" || resp.Seq != 1 { + t.Fatalf("unexpected request/response values: %+v %+v", req, resp) + } + + var se rpc.ServerError = "boom" + if se.Error() != "boom" { + t.Fatalf("ServerError.Error() = %q, want %q", se.Error(), "boom") + } + + // Function and method references for exported API surface coverage. + _ = rpc.Accept + _ = rpc.HandleHTTP + _ = rpc.Register + _ = rpc.RegisterName + _ = rpc.ServeCodec + _ = rpc.ServeConn + _ = rpc.ServeRequest + _ = rpc.Dial + _ = rpc.DialHTTP + _ = rpc.DialHTTPPath + _ = rpc.NewClient + _ = rpc.NewClientWithCodec + + var _ rpc.ClientCodec + var _ rpc.ServerCodec +} + +func TestServerServeRequestAndServeCodec(t *testing.T) { + srv := rpc.NewServer() + if err := srv.RegisterName("Arith", new(Arith)); err != nil { + t.Fatalf("RegisterName: %v", err) + } + + codecReq := newOneShotCodec("Arith.Add", &Args{A: 20, B: 22}) + if err := srv.ServeRequest(codecReq); err != nil { + t.Fatalf("ServeRequest: %v", err) + } + if codecReq.reply != 42 { + t.Fatalf("ServeRequest reply = %d, want 42", codecReq.reply) + } + + codecLoop := newOneShotCodec("Arith.Add", &Args{A: 4, B: 7}) + srv.ServeCodec(codecLoop) + if codecLoop.reply != 11 { + t.Fatalf("ServeCodec reply = %d, want 11", codecLoop.reply) + } +} + +func TestServerAccept(t *testing.T) { + srv := rpc.NewServer() + if err := srv.Register(new(Arith)); err != nil { + t.Fatalf("Register: %v", err) + } + + sconn, cconn := net.Pipe() + defer sconn.Close() + defer cconn.Close() + + done := make(chan struct{}) + go func() { + srv.Accept(&oneConnListener{conn: sconn}) + close(done) + }() + + client := rpc.NewClient(cconn) + defer client.Close() + var reply int + if err := client.Call("Arith.Add", &Args{A: 5, B: 6}, &reply); err != nil { + t.Fatalf("Call via Accept: %v", err) + } + if reply != 11 { + t.Fatalf("reply = %d, want 11", reply) + } + if err := client.Close(); err != nil && err != rpc.ErrShutdown { + t.Fatalf("second Close: %v", err) + } + <-done +} + +func TestServerServeHTTPAndHandleHTTP(t *testing.T) { + srv := rpc.NewServer() + if err := srv.RegisterName("Arith", new(Arith)); err != nil { + t.Fatalf("RegisterName: %v", err) + } + + rr := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "http://example.com/_rpc", nil) + srv.ServeHTTP(rr, req) + if rr.Code != http.StatusMethodNotAllowed { + t.Fatalf("ServeHTTP GET status = %d, want %d", rr.Code, http.StatusMethodNotAllowed) + } + + rpcPath := "/_llgo_rpc_" + strings.ReplaceAll(t.Name(), "/", "_") + debugPath := "/_llgo_rpc_debug_" + strings.ReplaceAll(t.Name(), "/", "_") + srv.HandleHTTP(rpcPath, debugPath) + + rrRPC := httptest.NewRecorder() + reqRPC := httptest.NewRequest(http.MethodGet, "http://example.com"+rpcPath, nil) + http.DefaultServeMux.ServeHTTP(rrRPC, reqRPC) + if rrRPC.Code != http.StatusMethodNotAllowed { + t.Fatalf("default mux rpc path status = %d, want %d", rrRPC.Code, http.StatusMethodNotAllowed) + } + + rrDebug := httptest.NewRecorder() + reqDebug := httptest.NewRequest(http.MethodGet, "http://example.com"+debugPath, nil) + http.DefaultServeMux.ServeHTTP(rrDebug, reqDebug) + if rrDebug.Code != http.StatusOK { + t.Fatalf("default mux debug path status = %d, want %d", rrDebug.Code, http.StatusOK) + } +} + +type oneShotCodec struct { + method string + args *Args + + served bool + reply int +} + +func newOneShotCodec(method string, args *Args) *oneShotCodec { + return &oneShotCodec{method: method, args: args} +} + +func (c *oneShotCodec) ReadRequestHeader(r *rpc.Request) error { + if c.served { + return io.EOF + } + c.served = true + r.ServiceMethod = c.method + r.Seq = 1 + return nil +} + +func (c *oneShotCodec) ReadRequestBody(v any) error { + if v == nil { + return nil + } + p, ok := v.(*Args) + if !ok { + return nil + } + *p = *c.args + return nil +} + +func (c *oneShotCodec) WriteResponse(_ *rpc.Response, v any) error { + if v == nil { + c.reply = 0 + return nil + } + if p, ok := v.(*int); ok { + c.reply = *p + } + return nil +} + +func (c *oneShotCodec) Close() error { return nil } + +type oneConnListener struct { + conn net.Conn + used bool +} + +func (l *oneConnListener) Accept() (net.Conn, error) { + if l.used { + return nil, io.EOF + } + l.used = true + return l.conn, nil +} + +func (l *oneConnListener) Close() error { return nil } + +func (l *oneConnListener) Addr() net.Addr { return dummyAddr("rpc-test") } + +type dummyAddr string + +func (a dummyAddr) Network() string { return "test" } +func (a dummyAddr) String() string { return string(a) } diff --git a/test/std/net/smtp/smtp_test.go b/test/std/net/smtp/smtp_test.go new file mode 100644 index 0000000000..df49a794ad --- /dev/null +++ b/test/std/net/smtp/smtp_test.go @@ -0,0 +1,290 @@ +package smtp_test + +import ( + "bufio" + "bytes" + "crypto/tls" + "net" + "net/smtp" + "strings" + "testing" +) + +func TestPlainAuth(t *testing.T) { + a := smtp.PlainAuth("", "user", "pass", "smtp.example.com") + + if _, _, err := a.Start(&smtp.ServerInfo{Name: "smtp.example.com", TLS: false, Auth: []string{"PLAIN"}}); err == nil { + t.Fatal("expected PlainAuth to reject non-TLS remote server") + } + + proto, resp, err := a.Start(&smtp.ServerInfo{Name: "smtp.example.com", TLS: true, Auth: []string{"PLAIN"}}) + if err != nil { + t.Fatalf("Start (TLS): %v", err) + } + if proto != "PLAIN" { + t.Fatalf("proto = %q, want %q", proto, "PLAIN") + } + wantResp := []byte("\x00user\x00pass") + if !bytes.Equal(resp, wantResp) { + t.Fatalf("resp = %q, want %q", resp, wantResp) + } + + next, err := a.Next(nil, false) + if err != nil { + t.Fatalf("Next: %v", err) + } + if next != nil { + t.Fatalf("Next returned %q, want nil", next) + } +} + +func TestCRAMMD5Auth(t *testing.T) { + a := smtp.CRAMMD5Auth("user", "secret") + + proto, initResp, err := a.Start(&smtp.ServerInfo{Name: "smtp.example.com", TLS: true, Auth: []string{"CRAM-MD5"}}) + if err != nil { + t.Fatalf("Start: %v", err) + } + if proto != "CRAM-MD5" { + t.Fatalf("proto = %q, want %q", proto, "CRAM-MD5") + } + if initResp != nil { + t.Fatalf("initial response = %q, want nil", initResp) + } + + challenge := []byte("<12345.67890@localhost>") + resp, err := a.Next(challenge, true) + if err != nil { + t.Fatalf("Next challenge: %v", err) + } + if !bytes.HasPrefix(resp, []byte("user ")) { + t.Fatalf("response = %q, want prefix %q", resp, "user ") + } + + finalResp, err := a.Next(nil, false) + if err != nil { + t.Fatalf("Next final: %v", err) + } + if finalResp != nil { + t.Fatalf("final response = %q, want nil", finalResp) + } +} + +func TestPublicAPISymbols(t *testing.T) { + _ = smtp.SendMail + _ = smtp.Dial + _ = smtp.NewClient + _ = smtp.CRAMMD5Auth + _ = smtp.PlainAuth + + var _ smtp.Auth + var _ smtp.ServerInfo +} + +func TestClientMethodFlow(t *testing.T) { + client, done := newSMTPClient(t, func(conn net.Conn) { + r := bufio.NewReader(conn) + w := bufio.NewWriter(conn) + writeSMTPLine(t, w, "220 localhost ESMTP") + + expectSMTPPrefix(t, r, "EHLO ") + writeSMTPLine(t, w, "250-localhost") + writeSMTPLine(t, w, "250-AUTH PLAIN") + writeSMTPLine(t, w, "250 HELP") + + expectSMTPPrefix(t, r, "AUTH PLAIN ") + writeSMTPLine(t, w, "235 2.7.0 auth ok") + + expectSMTPPrefix(t, r, "MAIL FROM:") + writeSMTPLine(t, w, "250 2.1.0 ok") + + expectSMTPPrefix(t, r, "RCPT TO:") + writeSMTPLine(t, w, "250 2.1.5 ok") + + expectSMTPLine(t, r, "DATA") + writeSMTPLine(t, w, "354 end with .") + readSMTPData(t, r) + writeSMTPLine(t, w, "250 2.0.0 queued") + + expectSMTPLine(t, r, "NOOP") + writeSMTPLine(t, w, "250 2.0.0 ok") + + expectSMTPLine(t, r, "RSET") + writeSMTPLine(t, w, "250 2.0.0 reset") + + expectSMTPPrefix(t, r, "VRFY ") + writeSMTPLine(t, w, "250 user verified") + + expectSMTPLine(t, r, "QUIT") + writeSMTPLine(t, w, "221 2.0.0 bye") + }) + defer waitSMTPDone(t, done) + + ok, param := client.Extension("AUTH") + if !ok || !strings.Contains(param, "PLAIN") { + t.Fatalf("Extension(AUTH) = (%v,%q), want true with PLAIN", ok, param) + } + + auth := smtp.PlainAuth("", "user", "pass", "localhost") + if err := client.Auth(auth); err != nil { + t.Fatalf("Auth: %v", err) + } + if err := client.Mail("sender@example.com"); err != nil { + t.Fatalf("Mail: %v", err) + } + if err := client.Rcpt("rcpt@example.com"); err != nil { + t.Fatalf("Rcpt: %v", err) + } + wc, err := client.Data() + if err != nil { + t.Fatalf("Data: %v", err) + } + if _, err := wc.Write([]byte("Subject: test\r\n\r\nhello\r\n")); err != nil { + t.Fatalf("Data.Write: %v", err) + } + if err := wc.Close(); err != nil { + t.Fatalf("Data.Close: %v", err) + } + if err := client.Noop(); err != nil { + t.Fatalf("Noop: %v", err) + } + if err := client.Reset(); err != nil { + t.Fatalf("Reset: %v", err) + } + if err := client.Verify("user"); err != nil { + t.Fatalf("Verify: %v", err) + } + if err := client.Quit(); err != nil { + t.Fatalf("Quit: %v", err) + } +} + +func TestClientHelloCloseAndStartTLSError(t *testing.T) { + client, done := newSMTPClient(t, func(conn net.Conn) { + r := bufio.NewReader(conn) + w := bufio.NewWriter(conn) + writeSMTPLine(t, w, "220 localhost ESMTP") + + expectSMTPLine(t, r, "EHLO llgo.local") + writeSMTPLine(t, w, "250 localhost") + }) + if err := client.Hello("llgo.local"); err != nil { + t.Fatalf("Hello: %v", err) + } + if _, ok := client.TLSConnectionState(); ok { + t.Fatal("TLSConnectionState ok=true before STARTTLS") + } + if err := client.StartTLS(&tls.Config{InsecureSkipVerify: true}); err == nil { + t.Fatal("StartTLS should fail without STARTTLS extension") + } + if err := client.Close(); err != nil { + t.Fatalf("Close: %v", err) + } + waitSMTPDone(t, done) +} + +func TestDial(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen: %v", err) + } + defer ln.Close() + + serverDone := make(chan error, 1) + go func() { + conn, err := ln.Accept() + if err != nil { + serverDone <- err + return + } + defer conn.Close() + w := bufio.NewWriter(conn) + writeSMTPLine(t, w, "220 localhost ESMTP") + serverDone <- nil + }() + + c, err := smtp.Dial(ln.Addr().String()) + if err != nil { + t.Fatalf("Dial: %v", err) + } + if err := c.Close(); err != nil { + t.Fatalf("Close after Dial: %v", err) + } + if err := <-serverDone; err != nil { + t.Fatalf("server accept: %v", err) + } +} + +func newSMTPClient(t *testing.T, serve func(conn net.Conn)) (*smtp.Client, chan error) { + t.Helper() + sconn, cconn := net.Pipe() + done := make(chan error, 1) + go func() { + defer sconn.Close() + serve(sconn) + done <- nil + }() + + client, err := smtp.NewClient(cconn, "localhost") + if err != nil { + if cerr := cconn.Close(); cerr != nil { + t.Logf("close cconn after NewClient failure: %v", cerr) + } + t.Fatalf("NewClient: %v", err) + } + return client, done +} + +func expectSMTPLine(t *testing.T, r *bufio.Reader, want string) { + t.Helper() + line, err := r.ReadString('\n') + if err != nil { + t.Fatalf("ReadString: %v", err) + } + got := strings.TrimRight(line, "\r\n") + if got != want { + t.Fatalf("SMTP line = %q, want %q", got, want) + } +} + +func expectSMTPPrefix(t *testing.T, r *bufio.Reader, prefix string) { + t.Helper() + line, err := r.ReadString('\n') + if err != nil { + t.Fatalf("ReadString: %v", err) + } + got := strings.TrimRight(line, "\r\n") + if !strings.HasPrefix(got, prefix) { + t.Fatalf("SMTP line = %q, want prefix %q", got, prefix) + } +} + +func writeSMTPLine(t *testing.T, w *bufio.Writer, line string) { + t.Helper() + if _, err := w.WriteString(line + "\r\n"); err != nil { + t.Fatalf("WriteString: %v", err) + } + if err := w.Flush(); err != nil { + t.Fatalf("Flush: %v", err) + } +} + +func readSMTPData(t *testing.T, r *bufio.Reader) { + t.Helper() + for { + line, err := r.ReadString('\n') + if err != nil { + t.Fatalf("Read data: %v", err) + } + if strings.TrimRight(line, "\r\n") == "." { + return + } + } +} + +func waitSMTPDone(t *testing.T, done chan error) { + t.Helper() + if err := <-done; err != nil { + t.Fatalf("smtp server goroutine: %v", err) + } +} diff --git a/test/std/net/tcp_methods_test.go b/test/std/net/tcp_methods_test.go new file mode 100644 index 0000000000..adbba38a9d --- /dev/null +++ b/test/std/net/tcp_methods_test.go @@ -0,0 +1,199 @@ +package net_test + +import ( + "errors" + "io" + "net" + "os" + "strings" + "testing" + "time" +) + +func TestTCPConnMethodCoverage(t *testing.T) { + ln, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}) + if err != nil { + t.Fatalf("ListenTCP error: %v", err) + } + t.Cleanup(func() { + if err := ln.Close(); err != nil { + t.Errorf("ln.Close: %v", err) + } + }) + + if lf, err := ln.File(); err == nil { + defer lf.Close() + if fl, err := net.FileListener(lf); err == nil { + fl.Close() + } else { + t.Logf("FileListener: %v", err) + } + } else { + t.Logf("TCPListener.File unavailable: %v", err) + } + + if raw, err := ln.SyscallConn(); err == nil { + done := make(chan struct{}) + if err := raw.Control(func(uintptr) {}); err != nil { + t.Logf("TCPListener.SyscallConn control: %v", err) + } + close(done) + } else { + t.Logf("TCPListener.SyscallConn unavailable: %v", err) + } + + serverDone := make(chan struct{}) + go func() { + defer close(serverDone) + conn, err := ln.Accept() + if err != nil { + t.Errorf("Accept error: %v", err) + return + } + tcpConn, ok := conn.(*net.TCPConn) + if !ok { + t.Errorf("connection type %T, want *net.TCPConn", conn) + conn.Close() + return + } + defer tcpConn.Close() + + if err := (*net.TCPConn).SetReadBuffer(tcpConn, 4096); err != nil { + t.Errorf("SetReadBuffer: %v", err) + } + if err := (*net.TCPConn).SetWriteBuffer(tcpConn, 4096); err != nil { + t.Errorf("SetWriteBuffer: %v", err) + } + if err := (*net.TCPConn).SetDeadline(tcpConn, time.Now().Add(500*time.Millisecond)); err != nil { + t.Errorf("SetDeadline: %v", err) + } + if err := (*net.TCPConn).SetReadDeadline(tcpConn, time.Now().Add(500*time.Millisecond)); err != nil { + t.Errorf("SetReadDeadline: %v", err) + } + if err := (*net.TCPConn).SetWriteDeadline(tcpConn, time.Now().Add(500*time.Millisecond)); err != nil { + t.Errorf("SetWriteDeadline: %v", err) + } + + if (*net.TCPConn).LocalAddr(tcpConn) == nil { + t.Error("LocalAddr returned nil") + } + if (*net.TCPConn).RemoteAddr(tcpConn) == nil { + t.Error("RemoteAddr returned nil") + } + + buf := make([]byte, 5) + tcpConn.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) + if _, err := (*net.TCPConn).Read(tcpConn, buf); err != nil { + t.Errorf("server ReadFull: %v", err) + } + if _, err := (*net.TCPConn).Write(tcpConn, []byte("pong")); err != nil { + t.Errorf("server Write: %v", err) + } + + if _, err := tcpConn.ReadFrom(strings.NewReader("from-server")); err != nil { + t.Errorf("ReadFrom: %v", err) + } + + if raw, err := tcpConn.SyscallConn(); err == nil { + if err := raw.Control(func(uintptr) {}); err != nil { + t.Logf("server SyscallConn control: %v", err) + } + } else { + t.Logf("server SyscallConn unavailable: %v", err) + } + + if f, err := tcpConn.File(); err == nil { + f.Close() + } else { + t.Logf("server File unavailable: %v", err) + } + + writeToDone := make(chan struct{}) + go func() { + defer close(writeToDone) + tcpConn.SetReadDeadline(time.Now().Add(200 * time.Millisecond)) + if _, err := tcpConn.WriteTo(io.Discard); err != nil && !errors.Is(err, os.ErrDeadlineExceeded) { + t.Logf("WriteTo: %v", err) + } + }() + <-writeToDone + }() + + client, err := net.DialTCP("tcp", nil, ln.Addr().(*net.TCPAddr)) + if err != nil { + t.Fatalf("DialTCP error: %v", err) + } + t.Cleanup(func() { + if err := (*net.TCPConn).Close(client); err != nil { + t.Errorf("client.Close: %v", err) + } + }) + + if err := (*net.TCPConn).SetReadBuffer(client, 4096); err != nil { + t.Errorf("client SetReadBuffer: %v", err) + } + if err := (*net.TCPConn).SetWriteBuffer(client, 4096); err != nil { + t.Errorf("client SetWriteBuffer: %v", err) + } + if err := (*net.TCPConn).SetDeadline(client, time.Now().Add(time.Second)); err != nil { + t.Errorf("client SetDeadline: %v", err) + } + if err := (*net.TCPConn).SetReadDeadline(client, time.Now().Add(time.Second)); err != nil { + t.Errorf("client SetReadDeadline: %v", err) + } + if err := (*net.TCPConn).SetWriteDeadline(client, time.Now().Add(time.Second)); err != nil { + t.Errorf("client SetWriteDeadline: %v", err) + } + + if (*net.TCPConn).LocalAddr(client) == nil { + t.Fatal("client LocalAddr returned nil") + } + if (*net.TCPConn).RemoteAddr(client) == nil { + t.Fatal("client RemoteAddr returned nil") + } + + if raw, err := client.SyscallConn(); err == nil { + if err := raw.Control(func(uintptr) {}); err != nil { + t.Logf("client SyscallConn control: %v", err) + } + } else { + t.Logf("client SyscallConn unavailable: %v", err) + } + + if f, err := client.File(); err == nil { + defer f.Close() + if dup, err := net.FileConn(f); err == nil { + dup.Close() + } else { + t.Logf("FileConn: %v", err) + } + } else { + t.Logf("client File unavailable: %v", err) + } + + if _, err := (*net.TCPConn).Write(client, []byte("hello")); err != nil { + t.Fatalf("client Write: %v", err) + } + + reply := make([]byte, 4) + if _, err := (*net.TCPConn).Read(client, reply); err != nil { + t.Fatalf("client Read pong: %v", err) + } + + extra := make([]byte, len("from-server")) + if _, err := (*net.TCPConn).Read(client, extra); err != nil { + t.Fatalf("client Read from ReadFrom: %v", err) + } + + if _, err := client.ReadFrom(strings.NewReader("from-client")); err != nil { + t.Fatalf("client ReadFrom: %v", err) + } + + client.CloseWrite() + client.SetReadDeadline(time.Now().Add(200 * time.Millisecond)) + if _, err := client.WriteTo(io.Discard); err != nil && !errors.Is(err, os.ErrDeadlineExceeded) { + t.Logf("client WriteTo: %v", err) + } + + <-serverDone +} diff --git a/test/std/net/textproto/textproto_test.go b/test/std/net/textproto/textproto_test.go new file mode 100644 index 0000000000..a559154571 --- /dev/null +++ b/test/std/net/textproto/textproto_test.go @@ -0,0 +1,389 @@ +package textproto_test + +import ( + "bufio" + "bytes" + "io" + "net/textproto" + "reflect" + "strings" + "testing" +) + +func TestCanonicalMIMEHeaderKey(t *testing.T) { + tests := []struct { + input string + want string + }{ + {"content-type", "Content-Type"}, + {"CONTENT-TYPE", "Content-Type"}, + {"accept-encoding", "Accept-Encoding"}, + {"foo-bar-baz", "Foo-Bar-Baz"}, + } + for _, tt := range tests { + got := textproto.CanonicalMIMEHeaderKey(tt.input) + if got != tt.want { + t.Errorf("CanonicalMIMEHeaderKey(%q) = %q, want %q", tt.input, got, tt.want) + } + } +} + +func TestTrimString(t *testing.T) { + tests := []struct { + input string + want string + }{ + {" hello ", "hello"}, + {"hello", "hello"}, + {"\t\n\rhello\t\n\r", "hello"}, + {"", ""}, + } + for _, tt := range tests { + got := textproto.TrimString(tt.input) + if got != tt.want { + t.Errorf("TrimString(%q) = %q, want %q", tt.input, got, tt.want) + } + } +} + +func TestTrimBytes(t *testing.T) { + tests := []struct { + input []byte + want []byte + }{ + {[]byte(" hello "), []byte("hello")}, + {[]byte("hello"), []byte("hello")}, + {[]byte("\t\n\rhello\t\n\r"), []byte("hello")}, + {[]byte(""), []byte("")}, + } + for _, tt := range tests { + got := textproto.TrimBytes(tt.input) + if !bytes.Equal(got, tt.want) { + t.Errorf("TrimBytes(%q) = %q, want %q", tt.input, got, tt.want) + } + } +} + +func TestMIMEHeaderAdd(t *testing.T) { + h := make(textproto.MIMEHeader) + h.Add("Content-Type", "text/html") + h.Add("Content-Type", "text/plain") + vals := h["Content-Type"] + if len(vals) != 2 { + t.Errorf("After Add, got %d values, want 2", len(vals)) + } +} + +func TestMIMEHeaderSet(t *testing.T) { + h := make(textproto.MIMEHeader) + h.Set("Content-Type", "text/html") + h.Set("Content-Type", "text/plain") + vals := h["Content-Type"] + if len(vals) != 1 || vals[0] != "text/plain" { + t.Errorf("After Set, got %v, want [text/plain]", vals) + } +} + +func TestMIMEHeaderGet(t *testing.T) { + h := make(textproto.MIMEHeader) + h.Set("Content-Type", "text/html") + got := h.Get("Content-Type") + if got != "text/html" { + t.Errorf("Get() = %q, want %q", got, "text/html") + } + got = h.Get("Missing") + if got != "" { + t.Errorf("Get(missing) = %q, want %q", got, "") + } +} + +func TestMIMEHeaderDel(t *testing.T) { + h := make(textproto.MIMEHeader) + h.Set("Content-Type", "text/html") + h.Del("Content-Type") + if h.Get("Content-Type") != "" { + t.Error("After Del, key still exists") + } +} + +func TestMIMEHeaderValues(t *testing.T) { + h := make(textproto.MIMEHeader) + h.Add("Accept", "text/html") + h.Add("Accept", "text/plain") + vals := h.Values("Accept") + if len(vals) != 2 || vals[0] != "text/html" || vals[1] != "text/plain" { + t.Errorf("Values() = %v, want [text/html text/plain]", vals) + } +} + +func TestNewReader(t *testing.T) { + br := bufio.NewReader(strings.NewReader("test")) + r := textproto.NewReader(br) + if r == nil { + t.Error("NewReader returned nil") + } +} + +func TestReaderReadLine(t *testing.T) { + br := bufio.NewReader(strings.NewReader("line1\nline2\n")) + r := textproto.NewReader(br) + line, err := r.ReadLine() + if err != nil { + t.Fatalf("ReadLine() error = %v", err) + } + if line != "line1" { + t.Errorf("ReadLine() = %q, want %q", line, "line1") + } +} + +func TestReaderReadLineBytes(t *testing.T) { + br := bufio.NewReader(strings.NewReader("line1\nline2\n")) + r := textproto.NewReader(br) + line, err := r.ReadLineBytes() + if err != nil { + t.Fatalf("ReadLineBytes() error = %v", err) + } + if !bytes.Equal(line, []byte("line1")) { + t.Errorf("ReadLineBytes() = %q, want %q", line, "line1") + } +} + +func TestReaderReadContinuedLine(t *testing.T) { + br := bufio.NewReader(strings.NewReader("line1\r\n line2\r\nline3\r\n")) + r := textproto.NewReader(br) + line, err := r.ReadContinuedLine() + if err != nil { + t.Fatalf("ReadContinuedLine() error = %v", err) + } + if line != "line1 line2" { + t.Errorf("ReadContinuedLine() = %q, want %q", line, "line1 line2") + } +} + +func TestReaderReadContinuedLineBytes(t *testing.T) { + br := bufio.NewReader(strings.NewReader("line1\r\n line2\r\nline3\r\n")) + r := textproto.NewReader(br) + line, err := r.ReadContinuedLineBytes() + if err != nil { + t.Fatalf("ReadContinuedLineBytes() error = %v", err) + } + if !bytes.Equal(line, []byte("line1 line2")) { + t.Errorf("ReadContinuedLineBytes() = %q, want %q", line, "line1 line2") + } +} + +func TestReaderReadMIMEHeader(t *testing.T) { + input := "Content-Type: text/html\r\nContent-Length: 123\r\n\r\n" + br := bufio.NewReader(strings.NewReader(input)) + r := textproto.NewReader(br) + h, err := r.ReadMIMEHeader() + if err != nil { + t.Fatalf("ReadMIMEHeader() error = %v", err) + } + if h.Get("Content-Type") != "text/html" { + t.Errorf("Get(Content-Type) = %q, want %q", h.Get("Content-Type"), "text/html") + } + if h.Get("Content-Length") != "123" { + t.Errorf("Get(Content-Length) = %q, want %q", h.Get("Content-Length"), "123") + } +} + +func TestReaderReadCodeLine(t *testing.T) { + br := bufio.NewReader(strings.NewReader("200 OK\r\n")) + r := textproto.NewReader(br) + code, msg, err := r.ReadCodeLine(200) + if err != nil { + t.Fatalf("ReadCodeLine() error = %v", err) + } + if code != 200 { + t.Errorf("ReadCodeLine() code = %d, want 200", code) + } + if msg != "OK" { + t.Errorf("ReadCodeLine() message = %q, want %q", msg, "OK") + } +} + +func TestReaderReadResponse(t *testing.T) { + br := bufio.NewReader(strings.NewReader("200 OK\r\n")) + r := textproto.NewReader(br) + code, msg, err := r.ReadResponse(200) + if err != nil { + t.Fatalf("ReadResponse() error = %v", err) + } + if code != 200 { + t.Errorf("ReadResponse() code = %d, want 200", code) + } + if msg != "OK" { + t.Errorf("ReadResponse() message = %q, want %q", msg, "OK") + } +} + +func TestReaderDotReader(t *testing.T) { + input := "line1\r\nline2\r\n.\r\n" + br := bufio.NewReader(strings.NewReader(input)) + r := textproto.NewReader(br) + dr := r.DotReader() + data, err := io.ReadAll(dr) + if err != nil { + t.Fatalf("DotReader ReadAll error = %v", err) + } + want := "line1\nline2\n" + if string(data) != want { + t.Errorf("DotReader data = %q, want %q", string(data), want) + } +} + +func TestReaderReadDotBytes(t *testing.T) { + input := "line1\r\nline2\r\n.\r\n" + br := bufio.NewReader(strings.NewReader(input)) + r := textproto.NewReader(br) + data, err := r.ReadDotBytes() + if err != nil { + t.Fatalf("ReadDotBytes() error = %v", err) + } + want := []byte("line1\nline2\n") + if !bytes.Equal(data, want) { + t.Errorf("ReadDotBytes() = %q, want %q", data, want) + } +} + +func TestReaderReadDotLines(t *testing.T) { + input := "line1\r\nline2\r\n.\r\n" + br := bufio.NewReader(strings.NewReader(input)) + r := textproto.NewReader(br) + lines, err := r.ReadDotLines() + if err != nil { + t.Fatalf("ReadDotLines() error = %v", err) + } + want := []string{"line1", "line2"} + if !reflect.DeepEqual(lines, want) { + t.Errorf("ReadDotLines() = %v, want %v", lines, want) + } +} + +func TestNewWriter(t *testing.T) { + var buf bytes.Buffer + bw := bufio.NewWriter(&buf) + w := textproto.NewWriter(bw) + if w == nil { + t.Error("NewWriter returned nil") + } +} + +func TestWriterPrintfLine(t *testing.T) { + var buf bytes.Buffer + bw := bufio.NewWriter(&buf) + w := textproto.NewWriter(bw) + err := w.PrintfLine("Hello %s", "World") + if err != nil { + t.Fatalf("PrintfLine() error = %v", err) + } + bw.Flush() + got := buf.String() + want := "Hello World\r\n" + if got != want { + t.Errorf("PrintfLine() wrote %q, want %q", got, want) + } +} + +func TestWriterDotWriter(t *testing.T) { + var buf bytes.Buffer + bw := bufio.NewWriter(&buf) + w := textproto.NewWriter(bw) + dw := w.DotWriter() + _, err := dw.Write([]byte("line1\r\nline2\r\n")) + if err != nil { + t.Fatalf("DotWriter Write error = %v", err) + } + err = dw.Close() + if err != nil { + t.Fatalf("DotWriter Close error = %v", err) + } + bw.Flush() + got := buf.String() + want := "line1\r\nline2\r\n.\r\n" + if got != want { + t.Errorf("DotWriter wrote %q, want %q", got, want) + } +} + +func TestProtocolError(t *testing.T) { + var e textproto.ProtocolError = "test error" + got := e.Error() + if got != "test error" { + t.Errorf("ProtocolError.Error() = %q, want %q", got, "test error") + } +} + +func TestErrorType(t *testing.T) { + e := &textproto.Error{ + Code: 500, + Msg: "Internal Server Error", + } + got := e.Error() + if got == "" { + t.Error("Error.Error() returned empty string") + } +} + +func TestPipelineNext(t *testing.T) { + var p textproto.Pipeline + id1 := p.Next() + id2 := p.Next() + if id2 <= id1 { + t.Errorf("Pipeline.Next() not incrementing: %d, %d", id1, id2) + } +} + +func TestPipelineStartEndRequest(t *testing.T) { + var p textproto.Pipeline + id := p.Next() + p.StartRequest(id) + p.EndRequest(id) +} + +func TestPipelineStartEndResponse(t *testing.T) { + var p textproto.Pipeline + id := p.Next() + p.StartRequest(id) + p.EndRequest(id) + p.StartResponse(id) + p.EndResponse(id) +} + +func TestNewConn(t *testing.T) { + var buf bytes.Buffer + rwc := &readWriteCloser{&buf} + conn := textproto.NewConn(rwc) + if conn == nil { + t.Error("NewConn returned nil") + } +} + +func TestConnClose(t *testing.T) { + var buf bytes.Buffer + rwc := &readWriteCloser{&buf} + conn := textproto.NewConn(rwc) + err := conn.Close() + if err != nil { + t.Errorf("Close() error = %v", err) + } +} + +func TestConnCmd(t *testing.T) { + var buf bytes.Buffer + rwc := &readWriteCloser{&buf} + conn := textproto.NewConn(rwc) + _, err := conn.Cmd("HELLO %s", "World") + if err != nil { + t.Fatalf("Cmd() error = %v", err) + } +} + +type readWriteCloser struct { + *bytes.Buffer +} + +func (rwc *readWriteCloser) Close() error { + return nil +} diff --git a/test/std/net/udp_methods_test.go b/test/std/net/udp_methods_test.go new file mode 100644 index 0000000000..8c5cc09964 --- /dev/null +++ b/test/std/net/udp_methods_test.go @@ -0,0 +1,197 @@ +package net_test + +import ( + "errors" + "net" + "net/netip" + "os" + "testing" + "time" +) + +func TestUDPConnMethodCoverage(t *testing.T) { + server, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}) + if err != nil { + t.Fatalf("ListenUDP error: %v", err) + } + t.Cleanup(func() { + if err := server.Close(); err != nil { + t.Errorf("server.Close: %v", err) + } + }) + + if err := server.SetReadBuffer(4096); err != nil { + t.Errorf("server SetReadBuffer: %v", err) + } + if err := server.SetWriteBuffer(4096); err != nil { + t.Errorf("server SetWriteBuffer: %v", err) + } + if err := server.SetDeadline(time.Now().Add(time.Second)); err != nil { + t.Errorf("server SetDeadline: %v", err) + } + if err := server.SetReadDeadline(time.Now().Add(time.Second)); err != nil { + t.Errorf("server SetReadDeadline: %v", err) + } + if err := server.SetWriteDeadline(time.Now().Add(time.Second)); err != nil { + t.Errorf("server SetWriteDeadline: %v", err) + } + if server.LocalAddr() == nil { + t.Fatal("server LocalAddr returned nil") + } + if raw, err := server.SyscallConn(); err == nil { + if err := raw.Control(func(uintptr) {}); err != nil { + t.Logf("server SyscallConn control: %v", err) + } + } else { + t.Logf("server SyscallConn unavailable: %v", err) + } + if f, err := server.File(); err == nil { + defer f.Close() + if pc, err := net.FilePacketConn(f); err == nil { + pc.Close() + } else { + t.Logf("FilePacketConn: %v", err) + } + } else { + t.Logf("server File unavailable: %v", err) + } + + serverAddr := server.LocalAddr().(*net.UDPAddr) + client, err := net.DialUDP("udp", nil, serverAddr) + if err != nil { + t.Fatalf("DialUDP error: %v", err) + } + defer client.Close() + + if err := client.SetReadBuffer(4096); err != nil { + t.Errorf("client SetReadBuffer: %v", err) + } + if err := client.SetWriteBuffer(4096); err != nil { + t.Errorf("client SetWriteBuffer: %v", err) + } + if err := client.SetDeadline(time.Now().Add(time.Second)); err != nil { + t.Errorf("client SetDeadline: %v", err) + } + if err := client.SetReadDeadline(time.Now().Add(time.Second)); err != nil { + t.Errorf("client SetReadDeadline: %v", err) + } + if err := client.SetWriteDeadline(time.Now().Add(time.Second)); err != nil { + t.Errorf("client SetWriteDeadline: %v", err) + } + if client.LocalAddr() == nil { + t.Fatal("client LocalAddr returned nil") + } + if client.RemoteAddr() == nil { + t.Fatal("client RemoteAddr returned nil") + } + if raw, err := client.SyscallConn(); err == nil { + if err := raw.Control(func(uintptr) {}); err != nil { + t.Logf("client SyscallConn control: %v", err) + } + } else { + t.Logf("client SyscallConn unavailable: %v", err) + } + if f, err := client.File(); err == nil { + defer f.Close() + if pc, err := net.FilePacketConn(f); err == nil { + pc.Close() + } else { + t.Logf("client FilePacketConn: %v", err) + } + } else { + t.Logf("client File unavailable: %v", err) + } + + if _, err := client.Write([]byte("hello")); err != nil { + t.Fatalf("client Write: %v", err) + } + + buf := make([]byte, 64) + n, addr, err := server.ReadFrom(buf) + if err != nil { + t.Fatalf("server ReadFrom: %v", err) + } + if n == 0 || addr.String() == "" { + t.Error("server ReadFrom returned no data") + } + + if _, err := client.Write([]byte("world")); err != nil { + t.Fatalf("client Write second: %v", err) + } + buf = buf[:cap(buf)] + n, udpAddr, err := server.ReadFromUDP(buf) + if err != nil { + t.Fatalf("server ReadFromUDP: %v", err) + } + if n == 0 || udpAddr == nil { + t.Error("server ReadFromUDP missing data") + } + + if _, err := client.Write([]byte("addrport")); err != nil { + t.Fatalf("client Write third: %v", err) + } + buf = buf[:cap(buf)] + n, ap, err := server.ReadFromUDPAddrPort(buf) + if err != nil { + t.Fatalf("server ReadFromUDPAddrPort: %v", err) + } + if n == 0 || !ap.IsValid() { + t.Error("server ReadFromUDPAddrPort missing data") + } + + oob := make([]byte, 128) + if _, err := client.Write([]byte("msg-readmsg")); err != nil { + t.Fatalf("client Write for ReadMsgUDP: %v", err) + } + buf = buf[:cap(buf)] + if n, _, _, addr, err := server.ReadMsgUDP(buf, oob); err != nil { + t.Fatalf("ReadMsgUDP: %v", err) + } else if n == 0 || addr == nil { + t.Error("ReadMsgUDP missing data") + } + + if _, _, err := server.WriteMsgUDP([]byte("srv-msg"), nil, client.LocalAddr().(*net.UDPAddr)); err != nil { + t.Fatalf("WriteMsgUDP: %v", err) + } + buf = buf[:cap(buf)] + if n, _, _, addr, err := client.ReadMsgUDP(buf, oob); err != nil { + t.Fatalf("client ReadMsgUDP: %v", err) + } else if n == 0 || addr == nil { + t.Error("client ReadMsgUDP missing data") + } + + clientAddr := client.LocalAddr().(*net.UDPAddr) + if _, err := server.WriteTo([]byte("reply"), clientAddr); err != nil { + t.Fatalf("server WriteTo: %v", err) + } + if _, err := server.WriteToUDP([]byte("reply2"), clientAddr); err != nil { + t.Fatalf("server WriteToUDP: %v", err) + } + + if clientAP, ok := netip.AddrFromSlice(clientAddr.IP); ok { + target := netip.AddrPortFrom(clientAP, uint16(clientAddr.Port)) + if _, err := server.WriteToUDPAddrPort([]byte("reply3"), target); err != nil { + t.Fatalf("server WriteToUDPAddrPort: %v", err) + } + if _, _, err := server.WriteMsgUDPAddrPort([]byte("srv-msg-ap"), nil, target); err != nil { + t.Fatalf("WriteMsgUDPAddrPort: %v", err) + } + buf = buf[:cap(buf)] + if n, _, _, from, err := client.ReadMsgUDPAddrPort(buf, oob); err != nil { + t.Fatalf("ReadMsgUDPAddrPort: %v", err) + } else if n == 0 || !from.IsValid() { + t.Error("ReadMsgUDPAddrPort missing data") + } + } + + buf = buf[:cap(buf)] + client.SetReadDeadline(time.Now().Add(200 * time.Millisecond)) + for i := 0; i < 3; i++ { + if _, err := client.Read(buf); err != nil { + if errors.Is(err, os.ErrDeadlineExceeded) { + break + } + t.Fatalf("client Read: %v", err) + } + } +} diff --git a/test/std/net/unix_methods_test.go b/test/std/net/unix_methods_test.go new file mode 100644 index 0000000000..9a803e186f --- /dev/null +++ b/test/std/net/unix_methods_test.go @@ -0,0 +1,273 @@ +package net_test + +import ( + "errors" + "fmt" + "io" + "net" + "os" + "path/filepath" + "runtime" + "testing" + "time" +) + +func TestUnixConnMethodCoverage(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("unix sockets not supported on windows") + } + + dir := t.TempDir() + streamPath := filepath.Join(dir, "stream.sock") + streamAddr := &net.UnixAddr{Name: streamPath, Net: "unix"} + ln, err := net.ListenUnix("unix", streamAddr) + if err != nil { + t.Fatalf("ListenUnix error: %v", err) + } + ln.SetUnlinkOnClose(true) + t.Cleanup(func() { + if err := ln.Close(); err != nil { + t.Errorf("ln.Close: %v", err) + } + }) + if ln.Addr() == nil { + t.Error("UnixListener Addr returned nil") + } + + if err := ln.SetDeadline(time.Now().Add(time.Second)); err != nil { + t.Errorf("UnixListener.SetDeadline: %v", err) + } + if lf, err := ln.File(); err == nil { + defer lf.Close() + if l2, err := net.FileListener(lf); err == nil { + l2.Close() + } else { + t.Logf("Unix FileListener: %v", err) + } + } else { + t.Logf("UnixListener.File unavailable: %v", err) + } + if raw, err := ln.SyscallConn(); err == nil { + if err := raw.Control(func(uintptr) {}); err != nil { + t.Logf("UnixListener.SyscallConn: %v", err) + } + } else { + t.Logf("UnixListener.SyscallConn unavailable: %v", err) + } + + serverDone := make(chan struct{}) + go func() { + defer close(serverDone) + c, err := ln.Accept() + if err != nil { + t.Errorf("AcceptUnix error: %v", err) + return + } + conn, ok := c.(*net.UnixConn) + if !ok { + t.Errorf("UnixListener.Accept returned %T", c) + c.Close() + return + } + defer conn.Close() + + if conn.LocalAddr() == nil || conn.RemoteAddr() == nil { + t.Error("UnixConn stream addresses nil") + } + if err := conn.SetReadBuffer(2048); err != nil { + t.Errorf("SetReadBuffer: %v", err) + } + if err := conn.SetWriteBuffer(2048); err != nil { + t.Errorf("SetWriteBuffer: %v", err) + } + if err := conn.SetDeadline(time.Now().Add(500 * time.Millisecond)); err != nil { + t.Errorf("SetDeadline: %v", err) + } + if err := conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond)); err != nil { + t.Errorf("SetReadDeadline: %v", err) + } + if err := conn.SetWriteDeadline(time.Now().Add(500 * time.Millisecond)); err != nil { + t.Errorf("SetWriteDeadline: %v", err) + } + + buf := make([]byte, 5) + if _, err := io.ReadFull(conn, buf); err != nil { + t.Errorf("Unix stream Read: %v", err) + } + if _, err := conn.Write([]byte("pong")); err != nil { + t.Errorf("Unix stream Write: %v", err) + } + + conn.CloseRead() + conn.CloseWrite() + + if raw, err := conn.SyscallConn(); err == nil { + if err := raw.Control(func(uintptr) {}); err != nil { + t.Logf("Unix stream SyscallConn: %v", err) + } + } else { + t.Logf("Unix stream SyscallConn unavailable: %v", err) + } + if f, err := conn.File(); err == nil { + f.Close() + } else { + t.Logf("Unix stream File unavailable: %v", err) + } + }() + + client, err := net.DialUnix("unix", nil, streamAddr) + if err != nil { + t.Fatalf("DialUnix error: %v", err) + } + defer client.Close() + if err := client.SetDeadline(time.Now().Add(time.Second)); err != nil { + t.Errorf("client SetDeadline: %v", err) + } + if err := client.SetReadDeadline(time.Now().Add(time.Second)); err != nil { + t.Errorf("client SetReadDeadline: %v", err) + } + if err := client.SetWriteDeadline(time.Now().Add(time.Second)); err != nil { + t.Errorf("client SetWriteDeadline: %v", err) + } + if client.LocalAddr() == nil || client.RemoteAddr() == nil { + t.Fatal("client unix stream addresses nil") + } + if raw, err := client.SyscallConn(); err == nil { + if err := raw.Control(func(uintptr) {}); err != nil { + t.Logf("client stream SyscallConn: %v", err) + } + } else { + t.Logf("client stream SyscallConn unavailable: %v", err) + } + if f, err := client.File(); err == nil { + defer f.Close() + if c2, err := net.FileConn(f); err == nil { + c2.Close() + } else { + t.Logf("client stream FileConn: %v", err) + } + } else { + t.Logf("client stream File unavailable: %v", err) + } + + if _, err := client.Write([]byte("hello")); err != nil { + t.Fatalf("client stream Write: %v", err) + } + reply := make([]byte, 4) + if _, err := io.ReadFull(client, reply); err != nil { + t.Fatalf("client stream Read: %v", err) + } + + <-serverDone + + additionalDone := make(chan struct{}) + go func() { + defer close(additionalDone) + spare, err := ln.AcceptUnix() + if err != nil { + t.Logf("AcceptUnix fallback: %v", err) + return + } + spare.Close() + }() + if extraClient, err := net.DialUnix("unix", nil, streamAddr); err == nil { + extraClient.Close() + } + <-additionalDone + + // Datagram coverage + gramPath := filepath.Join(dir, "gram.sock") + gramAddr := &net.UnixAddr{Name: gramPath, Net: "unixgram"} + gram, err := net.ListenUnixgram("unixgram", gramAddr) + if err != nil { + t.Fatalf("ListenUnixgram error: %v", err) + } + defer gram.Close() + if err := gram.SetDeadline(time.Now().Add(time.Second)); err != nil { + t.Errorf("unixgram SetDeadline: %v", err) + } + if err := gram.SetReadBuffer(1024); err != nil { + t.Errorf("unixgram SetReadBuffer: %v", err) + } + if err := gram.SetWriteBuffer(1024); err != nil { + t.Errorf("unixgram SetWriteBuffer: %v", err) + } + + clientGramPath := filepath.Join(os.TempDir(), fmt.Sprintf("llgo-unixgram-%d.sock", time.Now().UnixNano())) + clientGramAddr := &net.UnixAddr{Name: clientGramPath, Net: "unixgram"} + clientGram, err := net.DialUnix("unixgram", clientGramAddr, gramAddr) + if err != nil { + t.Fatalf("DialUnix unixgram error: %v", err) + } + defer clientGram.Close() + t.Cleanup(func() { + if err := os.Remove(clientGramPath); err != nil && !errors.Is(err, os.ErrNotExist) { + t.Errorf("Remove(%q): %v", clientGramPath, err) + } + }) + if err := clientGram.SetDeadline(time.Now().Add(time.Second)); err != nil { + t.Errorf("unixgram client SetDeadline: %v", err) + } + + buf := make([]byte, 64) + if _, err := clientGram.Write([]byte("gram1")); err != nil { + t.Fatalf("unixgram client Write: %v", err) + } + if _, _, err := gram.ReadFrom(buf); err != nil { + t.Fatalf("ReadFrom: %v", err) + } + + if _, err := clientGram.Write([]byte("gram2")); err != nil { + t.Fatalf("unixgram client Write second: %v", err) + } + clientPeer := new(net.UnixAddr) + if n, peer, err := gram.ReadFromUnix(buf); err != nil { + t.Fatalf("ReadFromUnix: %v", err) + } else { + if n == 0 { + t.Fatal("ReadFromUnix returned no data") + } + if peer != nil { + *clientPeer = *peer + } + } + + oob := make([]byte, 128) + if _, _, err := clientGram.WriteMsgUnix([]byte("msg"), nil, nil); err != nil { + t.Logf("client WriteMsgUnix: %v", err) + } else { + if n, oobn, flags, addr, err := gram.ReadMsgUnix(buf, oob); err != nil { + t.Fatalf("ReadMsgUnix: %v", err) + } else if n == 0 || addr == nil || oobn < 0 || flags < 0 { + t.Error("ReadMsgUnix returned unexpected values") + } + } + + if _, err := gram.WriteTo([]byte("reply-generic"), clientPeer); err != nil { + t.Fatalf("gram WriteTo: %v", err) + } + if _, err := gram.WriteToUnix([]byte("reply"), clientPeer); err != nil { + t.Fatalf("gram WriteToUnix reply: %v", err) + } + if _, _, err := gram.WriteMsgUnix([]byte("reply-msg"), nil, clientPeer); err != nil { + t.Fatalf("gram WriteMsgUnix: %v", err) + } + + buf = buf[:cap(buf)] + clientGram.SetReadDeadline(time.Now().Add(200 * time.Millisecond)) + for i := 0; i < 2; i++ { + if _, err := clientGram.Read(buf); err != nil { + if errors.Is(err, os.ErrDeadlineExceeded) { + break + } + t.Fatalf("unixgram client Read: %v", err) + } + } + + if err := clientGram.CloseRead(); err != nil { + t.Logf("unixgram CloseRead: %v", err) + } + if err := clientGram.CloseWrite(); err != nil { + t.Logf("unixgram CloseWrite: %v", err) + } +} diff --git a/test/std/net/url/url_test.go b/test/std/net/url/url_test.go new file mode 100644 index 0000000000..30e2429577 --- /dev/null +++ b/test/std/net/url/url_test.go @@ -0,0 +1,615 @@ +package url_test + +import ( + "net/url" + "reflect" + "testing" +) + +func TestPathEscape(t *testing.T) { + tests := []struct { + input string + want string + }{ + {"", ""}, + {"abc", "abc"}, + {"one two", "one%20two"}, + {"10%", "10%25"}, + {"hello/world", "hello%2Fworld"}, + {" ?&=#+%!<>#\"{}|\\^[]`☺\t:/@$'()*,;", "%20%3F&=%23+%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09:%2F@$%27%28%29%2A%2C%3B"}, + } + for _, tt := range tests { + got := url.PathEscape(tt.input) + if got != tt.want { + t.Errorf("PathEscape(%q) = %q, want %q", tt.input, got, tt.want) + } + } +} + +func TestPathUnescape(t *testing.T) { + tests := []struct { + input string + want string + wantErr bool + }{ + {"", "", false}, + {"abc", "abc", false}, + {"one%20two", "one two", false}, + {"10%25", "10%", false}, + {"%", "", true}, + {"%A", "", true}, + {"%ZZ", "", true}, + } + for _, tt := range tests { + got, err := url.PathUnescape(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("PathUnescape(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr) + continue + } + if !tt.wantErr && got != tt.want { + t.Errorf("PathUnescape(%q) = %q, want %q", tt.input, got, tt.want) + } + } +} + +func TestQueryEscape(t *testing.T) { + tests := []struct { + input string + want string + }{ + {"", ""}, + {"abc", "abc"}, + {"one two", "one+two"}, + {"10%", "10%25"}, + {"a&b=c", "a%26b%3Dc"}, + } + for _, tt := range tests { + got := url.QueryEscape(tt.input) + if got != tt.want { + t.Errorf("QueryEscape(%q) = %q, want %q", tt.input, got, tt.want) + } + } +} + +func TestQueryUnescape(t *testing.T) { + tests := []struct { + input string + want string + wantErr bool + }{ + {"", "", false}, + {"abc", "abc", false}, + {"one+two", "one two", false}, + {"10%25", "10%", false}, + {"a%26b%3Dc", "a&b=c", false}, + {"%", "", true}, + } + for _, tt := range tests { + got, err := url.QueryUnescape(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("QueryUnescape(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr) + continue + } + if !tt.wantErr && got != tt.want { + t.Errorf("QueryUnescape(%q) = %q, want %q", tt.input, got, tt.want) + } + } +} + +func TestParse(t *testing.T) { + tests := []struct { + rawURL string + wantErr bool + }{ + {"http://example.com", false}, + {"https://example.com/path?query=1", false}, + {"http://user:pass@host.com:8080/path", false}, + {"", false}, + {"http://[::1]:8080/", false}, + {":", true}, + } + for _, tt := range tests { + u, err := url.Parse(tt.rawURL) + if (err != nil) != tt.wantErr { + t.Errorf("Parse(%q) error = %v, wantErr %v", tt.rawURL, err, tt.wantErr) + continue + } + if !tt.wantErr && u == nil { + t.Errorf("Parse(%q) returned nil URL", tt.rawURL) + } + } +} + +func TestParseRequestURI(t *testing.T) { + tests := []struct { + rawURL string + wantErr bool + }{ + {"http://example.com", false}, + {"https://example.com/path", false}, + {"/path", false}, + {"//host/path", false}, + } + for _, tt := range tests { + _, err := url.ParseRequestURI(tt.rawURL) + if (err != nil) != tt.wantErr { + t.Errorf("ParseRequestURI(%q) error = %v, wantErr %v", tt.rawURL, err, tt.wantErr) + } + } +} + +func TestURLString(t *testing.T) { + tests := []struct { + url *url.URL + want string + }{ + { + &url.URL{Scheme: "http", Host: "example.com", Path: "/path"}, + "http://example.com/path", + }, + { + &url.URL{Scheme: "https", Host: "example.com", Path: "/path", RawQuery: "q=1"}, + "https://example.com/path?q=1", + }, + { + &url.URL{Scheme: "http", Host: "example.com", Path: "/path", Fragment: "frag"}, + "http://example.com/path#frag", + }, + } + for _, tt := range tests { + got := tt.url.String() + if got != tt.want { + t.Errorf("URL.String() = %q, want %q", got, tt.want) + } + } +} + +func TestURLHostname(t *testing.T) { + tests := []struct { + rawURL string + want string + }{ + {"http://example.com", "example.com"}, + {"http://example.com:8080", "example.com"}, + {"http://[::1]:8080", "::1"}, + {"http://[::1]", "::1"}, + } + for _, tt := range tests { + u, err := url.Parse(tt.rawURL) + if err != nil { + t.Errorf("Parse(%q) error = %v", tt.rawURL, err) + continue + } + got := u.Hostname() + if got != tt.want { + t.Errorf("URL(%q).Hostname() = %q, want %q", tt.rawURL, got, tt.want) + } + } +} + +func TestURLPort(t *testing.T) { + tests := []struct { + rawURL string + want string + }{ + {"http://example.com", ""}, + {"http://example.com:8080", "8080"}, + {"http://[::1]:8080", "8080"}, + {"http://[::1]", ""}, + } + for _, tt := range tests { + u, err := url.Parse(tt.rawURL) + if err != nil { + t.Errorf("Parse(%q) error = %v", tt.rawURL, err) + continue + } + got := u.Port() + if got != tt.want { + t.Errorf("URL(%q).Port() = %q, want %q", tt.rawURL, got, tt.want) + } + } +} + +func TestURLIsAbs(t *testing.T) { + tests := []struct { + rawURL string + want bool + }{ + {"http://example.com", true}, + {"/path", false}, + {"//example.com/path", false}, + {"https://example.com/path", true}, + } + for _, tt := range tests { + u, err := url.Parse(tt.rawURL) + if err != nil { + t.Errorf("Parse(%q) error = %v", tt.rawURL, err) + continue + } + got := u.IsAbs() + if got != tt.want { + t.Errorf("URL(%q).IsAbs() = %v, want %v", tt.rawURL, got, tt.want) + } + } +} + +func TestURLQuery(t *testing.T) { + u, err := url.Parse("http://example.com?a=1&b=2&a=3") + if err != nil { + t.Fatalf("Parse error: %v", err) + } + q := u.Query() + if q.Get("a") != "1" { + t.Errorf("Query().Get(\"a\") = %q, want %q", q.Get("a"), "1") + } + if q.Get("b") != "2" { + t.Errorf("Query().Get(\"b\") = %q, want %q", q.Get("b"), "2") + } + vals := q["a"] + if len(vals) != 2 || vals[0] != "1" || vals[1] != "3" { + t.Errorf("Query()[\"a\"] = %v, want [\"1\", \"3\"]", vals) + } +} + +func TestURLRequestURI(t *testing.T) { + tests := []struct { + rawURL string + want string + }{ + {"http://example.com/path", "/path"}, + {"http://example.com/path?q=1", "/path?q=1"}, + {"http://example.com", "/"}, + } + for _, tt := range tests { + u, err := url.Parse(tt.rawURL) + if err != nil { + t.Errorf("Parse(%q) error = %v", tt.rawURL, err) + continue + } + got := u.RequestURI() + if got != tt.want { + t.Errorf("URL(%q).RequestURI() = %q, want %q", tt.rawURL, got, tt.want) + } + } +} + +func TestURLResolveReference(t *testing.T) { + base, err := url.Parse("http://example.com/a/b") + if err != nil { + t.Fatalf("Parse error: %v", err) + } + ref, err := url.Parse("../c") + if err != nil { + t.Fatalf("Parse error: %v", err) + } + resolved := base.ResolveReference(ref) + want := "http://example.com/c" + if resolved.String() != want { + t.Errorf("ResolveReference() = %q, want %q", resolved.String(), want) + } +} + +func TestURLEscapedPath(t *testing.T) { + u, err := url.Parse("http://example.com/a b") + if err != nil { + t.Fatalf("Parse error: %v", err) + } + got := u.EscapedPath() + want := "/a%20b" + if got != want { + t.Errorf("EscapedPath() = %q, want %q", got, want) + } +} + +func TestURLEscapedFragment(t *testing.T) { + u, err := url.Parse("http://example.com#a b") + if err != nil { + t.Fatalf("Parse error: %v", err) + } + got := u.EscapedFragment() + want := "a%20b" + if got != want { + t.Errorf("EscapedFragment() = %q, want %q", got, want) + } +} + +func TestURLRedacted(t *testing.T) { + tests := []struct { + rawURL string + want string + }{ + {"http://user:pass@example.com/path", "http://user:xxxxx@example.com/path"}, + {"http://example.com/path", "http://example.com/path"}, + {"http://user@example.com/path", "http://user@example.com/path"}, + } + for _, tt := range tests { + u, err := url.Parse(tt.rawURL) + if err != nil { + t.Errorf("Parse(%q) error = %v", tt.rawURL, err) + continue + } + got := u.Redacted() + if got != tt.want { + t.Errorf("URL(%q).Redacted() = %q, want %q", tt.rawURL, got, tt.want) + } + } +} + +func TestURLMarshalBinary(t *testing.T) { + u, err := url.Parse("http://example.com/path") + if err != nil { + t.Fatalf("Parse error: %v", err) + } + data, err := u.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary error: %v", err) + } + if len(data) == 0 { + t.Error("MarshalBinary returned empty data") + } +} + +func TestURLUnmarshalBinary(t *testing.T) { + original, err := url.Parse("http://example.com/path") + if err != nil { + t.Fatalf("Parse error: %v", err) + } + data, err := original.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary error: %v", err) + } + var u url.URL + err = u.UnmarshalBinary(data) + if err != nil { + t.Fatalf("UnmarshalBinary error: %v", err) + } + if u.String() != original.String() { + t.Errorf("UnmarshalBinary result = %q, want %q", u.String(), original.String()) + } +} + +func TestURLAppendBinary(t *testing.T) { + u, err := url.Parse("http://example.com/path") + if err != nil { + t.Fatalf("Parse error: %v", err) + } + b := []byte("prefix") + result, err := u.AppendBinary(b) + if err != nil { + t.Fatalf("AppendBinary error: %v", err) + } + if len(result) <= len(b) { + t.Error("AppendBinary didn't append data") + } + if string(result[:len(b)]) != "prefix" { + t.Error("AppendBinary corrupted prefix") + } +} + +func TestURLParse(t *testing.T) { + base, err := url.Parse("http://example.com/a/b") + if err != nil { + t.Fatalf("Parse error: %v", err) + } + ref, err := base.Parse("../c") + if err != nil { + t.Fatalf("URL.Parse error: %v", err) + } + want := "http://example.com/c" + if ref.String() != want { + t.Errorf("URL.Parse() = %q, want %q", ref.String(), want) + } +} + +func TestURLJoinPath(t *testing.T) { + base, err := url.Parse("http://example.com/a") + if err != nil { + t.Fatalf("Parse error: %v", err) + } + result := base.JoinPath("b", "c") + want := "http://example.com/a/b/c" + if result.String() != want { + t.Errorf("JoinPath() = %q, want %q", result.String(), want) + } +} + +func TestJoinPath(t *testing.T) { + tests := []struct { + base string + elems []string + want string + wantErr bool + }{ + {"http://example.com", []string{"a", "b"}, "http://example.com/a/b", false}, + {"http://example.com/", []string{"a"}, "http://example.com/a", false}, + {"http://example.com/x", []string{"..", "a"}, "http://example.com/a", false}, + } + for _, tt := range tests { + got, err := url.JoinPath(tt.base, tt.elems...) + if (err != nil) != tt.wantErr { + t.Errorf("JoinPath(%q, %v) error = %v, wantErr %v", tt.base, tt.elems, err, tt.wantErr) + continue + } + if !tt.wantErr && got != tt.want { + t.Errorf("JoinPath(%q, %v) = %q, want %q", tt.base, tt.elems, got, tt.want) + } + } +} + +func TestUser(t *testing.T) { + u := url.User("username") + if u == nil { + t.Fatal("User returned nil") + } + if u.Username() != "username" { + t.Errorf("Username() = %q, want %q", u.Username(), "username") + } + if _, ok := u.Password(); ok { + t.Error("Password() returned true, want false") + } +} + +func TestUserPassword(t *testing.T) { + u := url.UserPassword("username", "password") + if u == nil { + t.Fatal("UserPassword returned nil") + } + if u.Username() != "username" { + t.Errorf("Username() = %q, want %q", u.Username(), "username") + } + pass, ok := u.Password() + if !ok { + t.Error("Password() returned false, want true") + } + if pass != "password" { + t.Errorf("Password() = %q, want %q", pass, "password") + } +} + +func TestUserinfoString(t *testing.T) { + u := url.UserPassword("user", "pass") + got := u.String() + want := "user:pass" + if got != want { + t.Errorf("Userinfo.String() = %q, want %q", got, want) + } +} + +func TestParseQuery(t *testing.T) { + tests := []struct { + query string + want url.Values + wantErr bool + }{ + {"a=1&b=2", url.Values{"a": []string{"1"}, "b": []string{"2"}}, false}, + {"a=1&a=2", url.Values{"a": []string{"1", "2"}}, false}, + {"", url.Values{}, false}, + {"%", nil, true}, + } + for _, tt := range tests { + got, err := url.ParseQuery(tt.query) + if (err != nil) != tt.wantErr { + t.Errorf("ParseQuery(%q) error = %v, wantErr %v", tt.query, err, tt.wantErr) + continue + } + if !tt.wantErr && !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseQuery(%q) = %v, want %v", tt.query, got, tt.want) + } + } +} + +func TestValuesGet(t *testing.T) { + v := url.Values{"a": []string{"1", "2"}} + if got := v.Get("a"); got != "1" { + t.Errorf("Get(\"a\") = %q, want %q", got, "1") + } + if got := v.Get("b"); got != "" { + t.Errorf("Get(\"b\") = %q, want %q", got, "") + } +} + +func TestValuesSet(t *testing.T) { + v := url.Values{} + v.Set("a", "1") + if got := v.Get("a"); got != "1" { + t.Errorf("After Set, Get(\"a\") = %q, want %q", got, "1") + } + v.Set("a", "2") + if got := v.Get("a"); got != "2" { + t.Errorf("After second Set, Get(\"a\") = %q, want %q", got, "2") + } + if len(v["a"]) != 1 { + t.Errorf("Set should replace, got %d values", len(v["a"])) + } +} + +func TestValuesAdd(t *testing.T) { + v := url.Values{} + v.Add("a", "1") + v.Add("a", "2") + vals := v["a"] + if len(vals) != 2 || vals[0] != "1" || vals[1] != "2" { + t.Errorf("After Add, got %v, want [\"1\", \"2\"]", vals) + } +} + +func TestValuesDel(t *testing.T) { + v := url.Values{"a": []string{"1"}, "b": []string{"2"}} + v.Del("a") + if v.Get("a") != "" { + t.Error("After Del, key still exists") + } + if v.Get("b") != "2" { + t.Error("Del affected wrong key") + } +} + +func TestValuesHas(t *testing.T) { + v := url.Values{"a": []string{"1"}} + if !v.Has("a") { + t.Error("Has(\"a\") = false, want true") + } + if v.Has("b") { + t.Error("Has(\"b\") = true, want false") + } +} + +func TestValuesEncode(t *testing.T) { + v := url.Values{ + "a": []string{"1", "2"}, + "b": []string{"3"}, + } + encoded := v.Encode() + decoded, err := url.ParseQuery(encoded) + if err != nil { + t.Fatalf("ParseQuery error: %v", err) + } + if !reflect.DeepEqual(decoded, v) { + t.Errorf("Encode/Decode round-trip failed: got %v, want %v", decoded, v) + } +} + +func TestEscapeError(t *testing.T) { + var e url.EscapeError = "test%" + got := e.Error() + if got == "" { + t.Error("EscapeError.Error() returned empty string") + } +} + +func TestInvalidHostError(t *testing.T) { + var e url.InvalidHostError = "[invalid" + got := e.Error() + if got == "" { + t.Error("InvalidHostError.Error() returned empty string") + } +} + +func TestErrorType(t *testing.T) { + e := &url.Error{ + Op: "parse", + URL: "http://[::1:80/", + Err: url.InvalidHostError("[::1:80"), + } + got := e.Error() + if got == "" { + t.Error("Error.Error() returned empty string") + } + if e.Unwrap() == nil { + t.Error("Error.Unwrap() returned nil") + } +} + +func TestErrorTimeout(t *testing.T) { + e := &url.Error{Op: "test"} + if e.Timeout() { + t.Error("Error.Timeout() = true for non-timeout error") + } +} + +func TestErrorTemporary(t *testing.T) { + e := &url.Error{Op: "test"} + if e.Temporary() { + t.Error("Error.Temporary() = true for non-temporary error") + } +} diff --git a/test/std/os/exec/exec_test.go b/test/std/os/exec/exec_test.go new file mode 100644 index 0000000000..61494a1211 --- /dev/null +++ b/test/std/os/exec/exec_test.go @@ -0,0 +1,425 @@ +package exec_test + +import ( + "bytes" + "context" + "io" + "os" + "os/exec" + "runtime" + "strings" + "testing" +) + +func TestCommand(t *testing.T) { + cmd := exec.Command("echo", "test") + if cmd == nil { + t.Fatal("Command returned nil") + } + + if cmd.Path == "" { + t.Error("Command Path is empty") + } +} + +func TestCommandContext(t *testing.T) { + ctx := context.Background() + cmd := exec.CommandContext(ctx, "echo", "test") + if cmd == nil { + t.Fatal("CommandContext returned nil") + } +} + +func TestCmdRun(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + cmd := exec.Command("echo", "hello") + err := cmd.Run() + if err != nil { + t.Fatalf("Run error: %v", err) + } +} + +func TestCmdOutput(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + cmd := exec.Command("echo", "hello") + output, err := cmd.Output() + if err != nil { + t.Fatalf("Output error: %v", err) + } + + result := strings.TrimSpace(string(output)) + if result != "hello" { + t.Errorf("Output = %q, want %q", result, "hello") + } +} + +func TestCmdCombinedOutput(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + cmd := exec.Command("echo", "test") + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("CombinedOutput error: %v", err) + } + + if len(output) == 0 { + t.Error("CombinedOutput returned empty") + } +} + +func TestCmdStdin(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + cmd := exec.Command("cat") + cmd.Stdin = strings.NewReader("test input") + + output, err := cmd.Output() + if err != nil { + t.Fatalf("Output error: %v", err) + } + + result := string(output) + if result != "test input" { + t.Errorf("Output = %q, want %q", result, "test input") + } +} + +func TestCmdStdout(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + var buf bytes.Buffer + cmd := exec.Command("echo", "stdout test") + cmd.Stdout = &buf + + err := cmd.Run() + if err != nil { + t.Fatalf("Run error: %v", err) + } + + output := strings.TrimSpace(buf.String()) + if output != "stdout test" { + t.Errorf("Stdout = %q, want %q", output, "stdout test") + } +} + +func TestCmdStderr(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + var buf bytes.Buffer + cmd := exec.Command("sh", "-c", "echo stderr test >&2") + cmd.Stderr = &buf + + err := cmd.Run() + if err != nil { + t.Fatalf("Run error: %v", err) + } + + output := strings.TrimSpace(buf.String()) + if output != "stderr test" { + t.Errorf("Stderr = %q, want %q", output, "stderr test") + } +} + +func TestCmdStart(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + cmd := exec.Command("sleep", "0.1") + err := cmd.Start() + if err != nil { + t.Fatalf("Start error: %v", err) + } + + if cmd.Process == nil { + t.Error("Process is nil after Start") + } + + err = cmd.Wait() + if err != nil { + t.Errorf("Wait error: %v", err) + } +} + +func TestCmdWait(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + cmd := exec.Command("echo", "test") + err := cmd.Start() + if err != nil { + t.Fatalf("Start error: %v", err) + } + + err = cmd.Wait() + if err != nil { + t.Errorf("Wait error: %v", err) + } + + if cmd.ProcessState == nil { + t.Error("ProcessState is nil after Wait") + } +} + +func TestCmdStdinPipe(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + cmd := exec.Command("cat") + stdin, err := cmd.StdinPipe() + if err != nil { + t.Fatalf("StdinPipe error: %v", err) + } + + if err := cmd.Start(); err != nil { + t.Fatalf("Start error: %v", err) + } + + io.WriteString(stdin, "pipe test") + stdin.Close() + + if err := cmd.Wait(); err != nil { + t.Errorf("Wait error: %v", err) + } +} + +func TestCmdStdoutPipe(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + cmd := exec.Command("echo", "pipe output") + stdout, err := cmd.StdoutPipe() + if err != nil { + t.Fatalf("StdoutPipe error: %v", err) + } + + if err := cmd.Start(); err != nil { + t.Fatalf("Start error: %v", err) + } + + data, err := io.ReadAll(stdout) + if err != nil { + t.Fatalf("ReadAll error: %v", err) + } + + output := strings.TrimSpace(string(data)) + if output != "pipe output" { + t.Errorf("Output = %q, want %q", output, "pipe output") + } + + if err := cmd.Wait(); err != nil { + t.Errorf("Wait error: %v", err) + } +} + +func TestCmdStderrPipe(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + cmd := exec.Command("sh", "-c", "echo pipe error >&2") + stderr, err := cmd.StderrPipe() + if err != nil { + t.Fatalf("StderrPipe error: %v", err) + } + + if err := cmd.Start(); err != nil { + t.Fatalf("Start error: %v", err) + } + + data, err := io.ReadAll(stderr) + if err != nil { + t.Fatalf("ReadAll error: %v", err) + } + + output := strings.TrimSpace(string(data)) + if output != "pipe error" { + t.Errorf("Output = %q, want %q", output, "pipe error") + } + + if err := cmd.Wait(); err != nil { + t.Errorf("Wait error: %v", err) + } +} + +func TestCmdEnv(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + cmd := exec.Command("sh", "-c", "echo $TEST_VAR") + cmd.Env = append(os.Environ(), "TEST_VAR=test_value") + + output, err := cmd.Output() + if err != nil { + t.Fatalf("Output error: %v", err) + } + + result := strings.TrimSpace(string(output)) + if result != "test_value" { + t.Errorf("Output = %q, want %q", result, "test_value") + } +} + +func TestCmdDir(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + tmpDir := strings.TrimSuffix(os.TempDir(), "/") + cmd := exec.Command("pwd") + cmd.Dir = tmpDir + + output, err := cmd.Output() + if err != nil { + t.Fatalf("Output error: %v", err) + } + + result := strings.TrimSpace(string(output)) + if result != tmpDir { + t.Errorf("Output = %q, want %q", result, tmpDir) + } +} + +func TestCmdString(t *testing.T) { + cmd := exec.Command("echo", "test") + str := cmd.String() + if str == "" { + t.Error("String() returned empty") + } +} + +func TestLookPath(t *testing.T) { + path, err := exec.LookPath("echo") + if err != nil { + t.Fatalf("LookPath error: %v", err) + } + + if path == "" { + t.Error("LookPath returned empty path") + } +} + +func TestError(t *testing.T) { + err := &exec.Error{ + Name: "test", + Err: os.ErrNotExist, + } + + errStr := err.Error() + if errStr == "" { + t.Error("Error.Error() returned empty string") + } +} + +func TestExitError(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + cmd := exec.Command("sh", "-c", "exit 1") + err := cmd.Run() + if err == nil { + t.Fatal("Expected error for exit code 1") + } + + exitErr, ok := err.(*exec.ExitError) + if !ok { + t.Fatalf("Error is not ExitError: %T", err) + } + + if exitErr.ExitCode() != 1 { + t.Errorf("ExitCode = %d, want 1", exitErr.ExitCode()) + } +} + +func TestErrNotFound(t *testing.T) { + if exec.ErrNotFound == nil { + t.Error("ErrNotFound should not be nil") + } +} + +func TestErrDot(t *testing.T) { + if exec.ErrDot == nil { + t.Error("ErrDot should not be nil") + } +} + +func TestErrWaitDelay(t *testing.T) { + if exec.ErrWaitDelay == nil { + t.Error("ErrWaitDelay should not be nil") + } +} + +func TestCmdEnviron(t *testing.T) { + cmd := exec.Command("echo", "test") + cmd.Env = []string{"VAR1=value1", "VAR2=value2"} + + environ := cmd.Environ() + if len(environ) == 0 { + t.Error("Environ returned empty slice") + } + + found := false + for _, env := range environ { + if strings.HasPrefix(env, "VAR1=") { + found = true + break + } + } + if !found { + t.Error("Environ doesn't contain VAR1") + } +} + +func TestErrorUnwrap(t *testing.T) { + baseErr := os.ErrNotExist + err := &exec.Error{ + Name: "test", + Err: baseErr, + } + + unwrapped := err.Unwrap() + if unwrapped != baseErr { + t.Errorf("Unwrap = %v, want %v", unwrapped, baseErr) + } +} + +func TestExitErrorError(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + cmd := exec.Command("sh", "-c", "exit 42") + err := cmd.Run() + if err == nil { + t.Fatal("Expected error for exit code 42") + } + + exitErr, ok := err.(*exec.ExitError) + if !ok { + t.Fatalf("Error is not ExitError: %T", err) + } + + errStr := exitErr.Error() + if errStr == "" { + t.Error("ExitError.Error() returned empty string") + } +} diff --git a/test/std/os/go126_symbols_test.go b/test/std/os/go126_symbols_test.go new file mode 100644 index 0000000000..c1962f6cd1 --- /dev/null +++ b/test/std/os/go126_symbols_test.go @@ -0,0 +1,89 @@ +//go:build go1.26 + +package os_test + +import ( + "errors" + "os" + "path/filepath" + "testing" + "time" +) + +func TestRootFileOperations(t *testing.T) { + directory := t.TempDir() + root, err := os.OpenRoot(directory) + if err != nil { + t.Fatal(err) + } + defer root.Close() + + if err := root.MkdirAll("nested/dir", 0755); err != nil { + t.Fatal(err) + } + if err := root.WriteFile("nested/dir/source.txt", []byte("contents"), 0644); err != nil { + t.Fatal(err) + } + data, err := root.ReadFile("nested/dir/source.txt") + if err != nil || string(data) != "contents" { + t.Fatalf("ReadFile = %q, %v; want contents, nil", data, err) + } + if err := root.Chmod("nested/dir/source.txt", 0600); err != nil { + t.Fatal(err) + } + when := time.Unix(123456789, 0) + if err := root.Chtimes("nested/dir/source.txt", when, when); err != nil { + t.Fatal(err) + } + if err := root.Chown("nested/dir/source.txt", -1, -1); err != nil { + t.Fatal(err) + } + if err := root.Link("nested/dir/source.txt", "nested/hardlink.txt"); err != nil { + t.Fatal(err) + } + if err := root.Rename("nested/hardlink.txt", "nested/renamed.txt"); err != nil { + t.Fatal(err) + } + if err := root.Symlink("dir/source.txt", "nested/symlink.txt"); err != nil { + t.Fatal(err) + } + if err := root.Lchown("nested/symlink.txt", -1, -1); err != nil { + t.Fatal(err) + } + target, err := root.Readlink("nested/symlink.txt") + if err != nil || target != "dir/source.txt" { + t.Fatalf("Readlink = %q, %v; want dir/source.txt, nil", target, err) + } + if err := root.RemoveAll("nested"); err != nil { + t.Fatal(err) + } + if _, err := os.Stat(filepath.Join(directory, "nested")); !errors.Is(err, os.ErrNotExist) { + t.Fatalf("RemoveAll left nested directory: %v", err) + } +} + +func TestProcessWithHandle(t *testing.T) { + process, err := os.FindProcess(os.Getpid()) + if err != nil { + t.Fatal(err) + } + called := false + err = process.WithHandle(func(handle uintptr) { + called = true + if handle == 0 { + t.Error("WithHandle supplied a zero handle") + } + }) + if errors.Is(err, os.ErrNoHandle) { + if called { + t.Fatal("WithHandle called its callback while returning ErrNoHandle") + } + return + } + if err != nil { + t.Fatal(err) + } + if !called { + t.Fatal("WithHandle did not call its callback") + } +} diff --git a/test/std/os/os_test.go b/test/std/os/os_test.go new file mode 100644 index 0000000000..bc85c6eead --- /dev/null +++ b/test/std/os/os_test.go @@ -0,0 +1,1571 @@ +package os_test + +import ( + "io" + "os" + "path/filepath" + "runtime" + "strings" + "syscall" + "testing" + "time" +) + +func canonicalPath(p string) string { + resolved, err := filepath.EvalSymlinks(p) + if err != nil { + return p + } + return resolved +} + +func TestChdir(t *testing.T) { + tmpDir := t.TempDir() + origDir, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + defer os.Chdir(origDir) + + if err := os.Chdir(tmpDir); err != nil { + t.Errorf("Chdir(%q) failed: %v", tmpDir, err) + } + + newDir, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + if canonicalPath(newDir) != canonicalPath(tmpDir) { + t.Errorf("After Chdir, Getwd() = %q, want %q", newDir, tmpDir) + } +} + +func TestChmod(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "chmod_test.txt") + if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil { + t.Fatal(err) + } + + if err := os.Chmod(testFile, 0600); err != nil { + t.Errorf("Chmod failed: %v", err) + } + + info, err := os.Stat(testFile) + if err != nil { + t.Fatal(err) + } + if runtime.GOOS != "windows" && info.Mode().Perm() != 0600 { + t.Errorf("After Chmod(0600), mode = %o, want 0600", info.Mode().Perm()) + } +} + +func TestChtimes(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "chtimes_test.txt") + if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil { + t.Fatal(err) + } + + atime := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) + mtime := time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC) + + if err := os.Chtimes(testFile, atime, mtime); err != nil { + t.Errorf("Chtimes failed: %v", err) + } + + info, err := os.Stat(testFile) + if err != nil { + t.Fatal(err) + } + if !info.ModTime().Equal(mtime) { + t.Errorf("After Chtimes, ModTime = %v, want %v", info.ModTime(), mtime) + } +} + +func TestClearenv(t *testing.T) { + os.Setenv("TEST_CLEARENV", "value") + os.Clearenv() + if val := os.Getenv("TEST_CLEARENV"); val != "" { + t.Errorf("After Clearenv, Getenv(TEST_CLEARENV) = %q, want empty", val) + } + os.Setenv("PATH", os.Getenv("PATH")) +} + +func TestEnviron(t *testing.T) { + os.Setenv("TEST_ENVIRON", "test_value") + defer os.Unsetenv("TEST_ENVIRON") + + env := os.Environ() + found := false + for _, e := range env { + if strings.HasPrefix(e, "TEST_ENVIRON=") { + found = true + if e != "TEST_ENVIRON=test_value" { + t.Errorf("Environ contains %q, want TEST_ENVIRON=test_value", e) + } + break + } + } + if !found { + t.Error("TEST_ENVIRON not found in Environ()") + } +} + +func TestExecutable(t *testing.T) { + exe, err := os.Executable() + if err != nil { + t.Errorf("Executable() failed: %v", err) + } + if exe == "" { + t.Error("Executable() returned empty string") + } +} + +func TestExpand(t *testing.T) { + mapper := func(s string) string { + if s == "VAR" { + return "value" + } + return "" + } + + tests := []struct { + input string + want string + }{ + {"$VAR", "value"}, + {"${VAR}", "value"}, + {"prefix ${VAR} suffix", "prefix value suffix"}, + {"$UNKNOWN", ""}, + } + + for _, tt := range tests { + got := os.Expand(tt.input, mapper) + if got != tt.want { + t.Errorf("Expand(%q) = %q, want %q", tt.input, got, tt.want) + } + } +} + +func TestExpandEnv(t *testing.T) { + os.Setenv("TEST_VAR", "test_value") + defer os.Unsetenv("TEST_VAR") + + result := os.ExpandEnv("prefix ${TEST_VAR} suffix") + want := "prefix test_value suffix" + if result != want { + t.Errorf("ExpandEnv = %q, want %q", result, want) + } +} + +func TestGetenv(t *testing.T) { + os.Setenv("TEST_GETENV", "test_value") + defer os.Unsetenv("TEST_GETENV") + + if val := os.Getenv("TEST_GETENV"); val != "test_value" { + t.Errorf("Getenv(TEST_GETENV) = %q, want test_value", val) + } + if val := os.Getenv("NONEXISTENT"); val != "" { + t.Errorf("Getenv(NONEXISTENT) = %q, want empty", val) + } +} + +func TestLookupEnv(t *testing.T) { + os.Setenv("TEST_LOOKUP", "test_value") + defer os.Unsetenv("TEST_LOOKUP") + + val, ok := os.LookupEnv("TEST_LOOKUP") + if !ok || val != "test_value" { + t.Errorf("LookupEnv(TEST_LOOKUP) = (%q, %v), want (test_value, true)", val, ok) + } + + _, ok = os.LookupEnv("NONEXISTENT") + if ok { + t.Error("LookupEnv(NONEXISTENT) returned ok=true, want false") + } +} + +func TestSetenvUnsetenv(t *testing.T) { + if err := os.Setenv("TEST_SETENV", "value"); err != nil { + t.Errorf("Setenv failed: %v", err) + } + if val := os.Getenv("TEST_SETENV"); val != "value" { + t.Errorf("After Setenv, Getenv = %q, want value", val) + } + + if err := os.Unsetenv("TEST_SETENV"); err != nil { + t.Errorf("Unsetenv failed: %v", err) + } + if val := os.Getenv("TEST_SETENV"); val != "" { + t.Errorf("After Unsetenv, Getenv = %q, want empty", val) + } +} + +func TestGetpid(t *testing.T) { + pid := os.Getpid() + if pid <= 0 { + t.Errorf("Getpid() = %d, want > 0", pid) + } +} + +func TestGetppid(t *testing.T) { + ppid := os.Getppid() + if ppid <= 0 { + t.Errorf("Getppid() = %d, want > 0", ppid) + } +} + +func TestGetuid(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Getuid not meaningful on Windows") + } + uid := os.Getuid() + if uid < 0 { + t.Errorf("Getuid() = %d, want >= 0", uid) + } +} + +func TestGeteuid(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Geteuid not meaningful on Windows") + } + euid := os.Geteuid() + if euid < 0 { + t.Errorf("Geteuid() = %d, want >= 0", euid) + } +} + +func TestGetgid(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Getgid not meaningful on Windows") + } + gid := os.Getgid() + if gid < 0 { + t.Errorf("Getgid() = %d, want >= 0", gid) + } +} + +func TestGetegid(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Getegid not meaningful on Windows") + } + egid := os.Getegid() + if egid < 0 { + t.Errorf("Getegid() = %d, want >= 0", egid) + } +} + +func TestGetgroups(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Getgroups not supported on Windows") + } + groups, err := os.Getgroups() + if err != nil { + t.Errorf("Getgroups() failed: %v", err) + } + if len(groups) == 0 { + t.Error("Getgroups() returned empty slice") + } +} + +func TestGetpagesize(t *testing.T) { + pagesize := os.Getpagesize() + if pagesize <= 0 { + t.Errorf("Getpagesize() = %d, want > 0", pagesize) + } +} + +func TestGetwd(t *testing.T) { + wd, err := os.Getwd() + if err != nil { + t.Errorf("Getwd() failed: %v", err) + } + if wd == "" { + t.Error("Getwd() returned empty string") + } +} + +func TestHostname(t *testing.T) { + hostname, err := os.Hostname() + if err != nil { + t.Errorf("Hostname() failed: %v", err) + } + if hostname == "" { + t.Error("Hostname() returned empty string") + } +} + +func TestTempDir(t *testing.T) { + tmpDir := os.TempDir() + if tmpDir == "" { + t.Error("TempDir() returned empty string") + } + info, err := os.Stat(tmpDir) + if err != nil { + t.Errorf("TempDir() returned non-existent directory: %v", err) + } + if !info.IsDir() { + t.Errorf("TempDir() returned non-directory: %q", tmpDir) + } +} + +func TestUserCacheDir(t *testing.T) { + dir, err := os.UserCacheDir() + if err != nil { + t.Skipf("UserCacheDir() failed: %v", err) + } + if dir == "" { + t.Skip("UserCacheDir() returned empty string") + } +} + +func TestUserConfigDir(t *testing.T) { + dir, err := os.UserConfigDir() + if err != nil { + t.Skipf("UserConfigDir() failed: %v", err) + } + if dir == "" { + t.Skip("UserConfigDir() returned empty string") + } +} + +func TestUserHomeDir(t *testing.T) { + dir, err := os.UserHomeDir() + if err != nil { + t.Skipf("UserHomeDir() failed: %v", err) + } + if dir == "" { + t.Skip("UserHomeDir() returned empty string") + } +} + +func TestIsPathSeparator(t *testing.T) { + if runtime.GOOS == "windows" { + if !os.IsPathSeparator('\\') { + t.Error("IsPathSeparator('\\\\') should be true on Windows") + } + } + if !os.IsPathSeparator('/') { + t.Error("IsPathSeparator('/') should be true") + } + if os.IsPathSeparator('a') { + t.Error("IsPathSeparator('a') should be false") + } +} + +func TestMkdir(t *testing.T) { + tmpDir := t.TempDir() + newDir := filepath.Join(tmpDir, "newdir") + + if err := os.Mkdir(newDir, 0755); err != nil { + t.Errorf("Mkdir failed: %v", err) + } + + info, err := os.Stat(newDir) + if err != nil { + t.Errorf("Stat after Mkdir failed: %v", err) + } + if !info.IsDir() { + t.Error("Mkdir did not create a directory") + } +} + +func TestMkdirAll(t *testing.T) { + tmpDir := t.TempDir() + deepDir := filepath.Join(tmpDir, "a", "b", "c") + + if err := os.MkdirAll(deepDir, 0755); err != nil { + t.Errorf("MkdirAll failed: %v", err) + } + + info, err := os.Stat(deepDir) + if err != nil { + t.Errorf("Stat after MkdirAll failed: %v", err) + } + if !info.IsDir() { + t.Error("MkdirAll did not create directories") + } +} + +func TestMkdirTemp(t *testing.T) { + tmpDir := t.TempDir() + tempDir, err := os.MkdirTemp(tmpDir, "pattern*") + if err != nil { + t.Errorf("MkdirTemp failed: %v", err) + } + defer os.RemoveAll(tempDir) + + info, err := os.Stat(tempDir) + if err != nil { + t.Errorf("Stat after MkdirTemp failed: %v", err) + } + if !info.IsDir() { + t.Error("MkdirTemp did not create a directory") + } +} + +func TestRemove(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "remove_test.txt") + if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil { + t.Fatal(err) + } + + if err := os.Remove(testFile); err != nil { + t.Errorf("Remove failed: %v", err) + } + + if _, err := os.Stat(testFile); !os.IsNotExist(err) { + t.Error("File still exists after Remove") + } +} + +func TestRemoveAll(t *testing.T) { + tmpDir := t.TempDir() + deepDir := filepath.Join(tmpDir, "a", "b", "c") + os.MkdirAll(deepDir, 0755) + testFile := filepath.Join(deepDir, "test.txt") + os.WriteFile(testFile, []byte("test"), 0644) + + targetDir := filepath.Join(tmpDir, "a") + if err := os.RemoveAll(targetDir); err != nil { + t.Errorf("RemoveAll failed: %v", err) + } + + if _, err := os.Stat(targetDir); !os.IsNotExist(err) { + t.Error("Directory still exists after RemoveAll") + } +} + +func TestRename(t *testing.T) { + tmpDir := t.TempDir() + oldPath := filepath.Join(tmpDir, "old.txt") + newPath := filepath.Join(tmpDir, "new.txt") + + if err := os.WriteFile(oldPath, []byte("content"), 0644); err != nil { + t.Fatal(err) + } + + if err := os.Rename(oldPath, newPath); err != nil { + t.Errorf("Rename failed: %v", err) + } + + if _, err := os.Stat(oldPath); !os.IsNotExist(err) { + t.Error("Old file still exists after Rename") + } + if _, err := os.Stat(newPath); err != nil { + t.Error("New file does not exist after Rename") + } +} + +func TestTruncate(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "truncate_test.txt") + if err := os.WriteFile(testFile, []byte("long content"), 0644); err != nil { + t.Fatal(err) + } + + if err := os.Truncate(testFile, 4); err != nil { + t.Errorf("Truncate failed: %v", err) + } + + info, err := os.Stat(testFile) + if err != nil { + t.Fatal(err) + } + if info.Size() != 4 { + t.Errorf("After Truncate(4), size = %d, want 4", info.Size()) + } +} + +func TestReadFile(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "read_test.txt") + content := []byte("test content") + if err := os.WriteFile(testFile, content, 0644); err != nil { + t.Fatal(err) + } + + data, err := os.ReadFile(testFile) + if err != nil { + t.Errorf("ReadFile failed: %v", err) + } + if string(data) != string(content) { + t.Errorf("ReadFile = %q, want %q", data, content) + } +} + +func TestWriteFile(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "write_test.txt") + content := []byte("write content") + + if err := os.WriteFile(testFile, content, 0644); err != nil { + t.Errorf("WriteFile failed: %v", err) + } + + data, err := os.ReadFile(testFile) + if err != nil { + t.Fatal(err) + } + if string(data) != string(content) { + t.Errorf("After WriteFile, content = %q, want %q", data, content) + } +} + +func TestReadDir(t *testing.T) { + tmpDir := t.TempDir() + os.WriteFile(filepath.Join(tmpDir, "file1.txt"), []byte("1"), 0644) + os.WriteFile(filepath.Join(tmpDir, "file2.txt"), []byte("2"), 0644) + os.Mkdir(filepath.Join(tmpDir, "dir1"), 0755) + + entries, err := os.ReadDir(tmpDir) + if err != nil { + t.Errorf("ReadDir failed: %v", err) + } + if len(entries) != 3 { + t.Errorf("ReadDir returned %d entries, want 3", len(entries)) + } + + for _, entry := range entries { + if entry.Name() == "" { + t.Error("Entry has empty name") + } + } +} + +func TestStat(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "stat_test.txt") + content := []byte("test") + if err := os.WriteFile(testFile, content, 0644); err != nil { + t.Fatal(err) + } + + info, err := os.Stat(testFile) + if err != nil { + t.Errorf("Stat failed: %v", err) + } + if info.Name() != "stat_test.txt" { + t.Errorf("Stat().Name() = %q, want stat_test.txt", info.Name()) + } + if info.Size() != int64(len(content)) { + t.Errorf("Stat().Size() = %d, want %d", info.Size(), len(content)) + } + if info.IsDir() { + t.Error("Stat().IsDir() = true for file") + } +} + +func TestLstat(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "lstat_test.txt") + if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil { + t.Fatal(err) + } + + info, err := os.Lstat(testFile) + if err != nil { + t.Errorf("Lstat failed: %v", err) + } + if info.Name() != "lstat_test.txt" { + t.Errorf("Lstat().Name() = %q, want lstat_test.txt", info.Name()) + } +} + +func TestSameFile(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "same_test.txt") + if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil { + t.Fatal(err) + } + + info1, err := os.Stat(testFile) + if err != nil { + t.Fatal(err) + } + info2, err := os.Stat(testFile) + if err != nil { + t.Fatal(err) + } + + if !os.SameFile(info1, info2) { + t.Error("SameFile returned false for same file") + } +} + +func TestSymlink(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Symlink requires elevated privileges on Windows") + } + + tmpDir := t.TempDir() + target := filepath.Join(tmpDir, "target.txt") + link := filepath.Join(tmpDir, "link.txt") + + if err := os.WriteFile(target, []byte("content"), 0644); err != nil { + t.Fatal(err) + } + + if err := os.Symlink(target, link); err != nil { + t.Errorf("Symlink failed: %v", err) + } + + linkTarget, err := os.Readlink(link) + if err != nil { + t.Errorf("Readlink failed: %v", err) + } + if linkTarget != target { + t.Errorf("Readlink = %q, want %q", linkTarget, target) + } +} + +func TestLink(t *testing.T) { + tmpDir := t.TempDir() + oldPath := filepath.Join(tmpDir, "old.txt") + newPath := filepath.Join(tmpDir, "new.txt") + + if err := os.WriteFile(oldPath, []byte("content"), 0644); err != nil { + t.Fatal(err) + } + + if err := os.Link(oldPath, newPath); err != nil { + if runtime.GOOS == "windows" { + t.Skip("Link may not be supported") + } + t.Errorf("Link failed: %v", err) + } + + info1, _ := os.Stat(oldPath) + info2, _ := os.Stat(newPath) + if !os.SameFile(info1, info2) { + t.Error("Linked files are not the same") + } +} + +func TestChown(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Chown not supported on Windows") + } + if os.Getuid() != 0 { + t.Skip("Chown requires root privileges") + } + + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "chown_test.txt") + if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil { + t.Fatal(err) + } + + if err := os.Chown(testFile, os.Getuid(), os.Getgid()); err != nil { + t.Errorf("Chown failed: %v", err) + } +} + +func TestLchown(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Lchown not supported on Windows") + } + if os.Getuid() != 0 { + t.Skip("Lchown requires root privileges") + } + + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "lchown_test.txt") + if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil { + t.Fatal(err) + } + + if err := os.Lchown(testFile, os.Getuid(), os.Getgid()); err != nil { + t.Errorf("Lchown failed: %v", err) + } +} + +func TestDirFS(t *testing.T) { + tmpDir := t.TempDir() + testFile := "test.txt" + if err := os.WriteFile(filepath.Join(tmpDir, testFile), []byte("content"), 0644); err != nil { + t.Fatal(err) + } + + fsys := os.DirFS(tmpDir) + f, err := fsys.Open(testFile) + if err != nil { + t.Errorf("DirFS().Open failed: %v", err) + } + defer f.Close() + + data, err := io.ReadAll(f) + if err != nil { + t.Fatal(err) + } + if string(data) != "content" { + t.Errorf("DirFS read %q, want content", data) + } +} + +func TestCopyFS(t *testing.T) { + tmpDir := t.TempDir() + srcDir := filepath.Join(tmpDir, "src") + dstDir := filepath.Join(tmpDir, "dst") + + os.Mkdir(srcDir, 0755) + os.WriteFile(filepath.Join(srcDir, "file.txt"), []byte("content"), 0644) + + fsys := os.DirFS(srcDir) + if err := os.CopyFS(dstDir, fsys); err != nil { + t.Errorf("CopyFS failed: %v", err) + } + + data, err := os.ReadFile(filepath.Join(dstDir, "file.txt")) + if err != nil { + t.Errorf("CopyFS result read failed: %v", err) + } + if string(data) != "content" { + t.Errorf("CopyFS copied %q, want content", data) + } +} + +func TestPipe(t *testing.T) { + r, w, err := os.Pipe() + if err != nil { + t.Errorf("Pipe failed: %v", err) + } + defer r.Close() + defer w.Close() + + testData := []byte("test data") + go func() { + w.Write(testData) + w.Close() + }() + + buf := make([]byte, 100) + n, err := r.Read(buf) + if err != nil && err != io.EOF { + t.Errorf("Read from pipe failed: %v", err) + } + if string(buf[:n]) != string(testData) { + t.Errorf("Pipe read %q, want %q", buf[:n], testData) + } +} + +func TestFileOperations(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "file_ops.txt") + + f, err := os.Create(testFile) + if err != nil { + t.Fatalf("Create failed: %v", err) + } + + if f.Name() != testFile { + t.Errorf("File.Name() = %q, want %q", f.Name(), testFile) + } + + n, err := f.Write([]byte("test")) + if err != nil || n != 4 { + t.Errorf("Write failed: n=%d, err=%v", n, err) + } + + n2, err := f.WriteString(" string") + if err != nil || n2 != 7 { + t.Errorf("WriteString failed: n=%d, err=%v", n2, err) + } + + if err := f.Sync(); err != nil { + t.Errorf("Sync failed: %v", err) + } + + offset, err := f.Seek(0, io.SeekStart) + if err != nil || offset != 0 { + t.Errorf("Seek failed: offset=%d, err=%v", offset, err) + } + + buf := make([]byte, 11) + n, err = f.Read(buf) + if err != nil || string(buf[:n]) != "test string" { + t.Errorf("Read failed: got %q, err=%v", buf[:n], err) + } + + info, err := f.Stat() + if err != nil { + t.Errorf("File.Stat failed: %v", err) + } + if info.Size() != 11 { + t.Errorf("File.Stat().Size() = %d, want 11", info.Size()) + } + + if err := f.Truncate(4); err != nil { + t.Errorf("File.Truncate failed: %v", err) + } + + if fd := f.Fd(); fd == 0 { + t.Error("File.Fd() returned 0") + } + + if err := f.Close(); err != nil { + t.Errorf("Close failed: %v", err) + } +} + +func TestOpen(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "open_test.txt") + content := []byte("test content") + if err := os.WriteFile(testFile, content, 0644); err != nil { + t.Fatal(err) + } + + f, err := os.Open(testFile) + if err != nil { + t.Errorf("Open failed: %v", err) + } + defer f.Close() + + buf := make([]byte, len(content)) + n, err := f.Read(buf) + if err != nil || string(buf[:n]) != string(content) { + t.Errorf("Read after Open got %q, want %q", buf[:n], content) + } +} + +func TestOpenFile(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "openfile_test.txt") + + f, err := os.OpenFile(testFile, os.O_CREATE|os.O_RDWR, 0644) + if err != nil { + t.Errorf("OpenFile failed: %v", err) + } + defer f.Close() + + if _, err := f.WriteString("content"); err != nil { + t.Errorf("WriteString after OpenFile failed: %v", err) + } +} + +func TestCreateTemp(t *testing.T) { + tmpDir := t.TempDir() + f, err := os.CreateTemp(tmpDir, "pattern*") + if err != nil { + t.Errorf("CreateTemp failed: %v", err) + } + defer os.Remove(f.Name()) + defer f.Close() + + if !strings.HasPrefix(filepath.Base(f.Name()), "pattern") { + t.Errorf("CreateTemp name %q doesn't match pattern", f.Name()) + } +} + +func TestFileReadAt(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "readat_test.txt") + content := []byte("0123456789") + if err := os.WriteFile(testFile, content, 0644); err != nil { + t.Fatal(err) + } + + f, err := os.Open(testFile) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + buf := make([]byte, 3) + n, err := f.ReadAt(buf, 5) + if err != nil || string(buf[:n]) != "567" { + t.Errorf("ReadAt(5) = %q, err=%v, want 567", buf[:n], err) + } +} + +func TestFileWriteAt(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "writeat_test.txt") + + f, err := os.Create(testFile) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + f.Write([]byte("0000000000")) + n, err := f.WriteAt([]byte("abc"), 3) + if err != nil || n != 3 { + t.Errorf("WriteAt failed: n=%d, err=%v", n, err) + } + + f.Seek(0, io.SeekStart) + buf := make([]byte, 10) + f.Read(buf) + if string(buf) != "000abc0000" { + t.Errorf("After WriteAt, content = %q, want 000abc0000", buf) + } +} + +func TestFileChdir(t *testing.T) { + tmpDir := t.TempDir() + origDir, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + defer os.Chdir(origDir) + + f, err := os.Open(tmpDir) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + if err := f.Chdir(); err != nil { + t.Errorf("File.Chdir failed: %v", err) + } + + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + if canonicalPath(wd) != canonicalPath(tmpDir) { + t.Errorf("After File.Chdir, Getwd = %q, want %q", wd, tmpDir) + } +} + +func TestFileChmod(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "chmod_file_test.txt") + + f, err := os.Create(testFile) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + if err := f.Chmod(0600); err != nil { + t.Errorf("File.Chmod failed: %v", err) + } + + info, err := f.Stat() + if err != nil { + t.Fatal(err) + } + if runtime.GOOS != "windows" && info.Mode().Perm() != 0600 { + t.Errorf("After File.Chmod, mode = %o, want 0600", info.Mode().Perm()) + } +} + +func TestFileReadDir(t *testing.T) { + tmpDir := t.TempDir() + os.WriteFile(filepath.Join(tmpDir, "file1.txt"), []byte("1"), 0644) + os.WriteFile(filepath.Join(tmpDir, "file2.txt"), []byte("2"), 0644) + + f, err := os.Open(tmpDir) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + entries, err := f.ReadDir(-1) + if err != nil { + t.Errorf("File.ReadDir failed: %v", err) + } + if len(entries) != 2 { + t.Errorf("File.ReadDir returned %d entries, want 2", len(entries)) + } +} + +func TestFileReaddir(t *testing.T) { + tmpDir := t.TempDir() + os.WriteFile(filepath.Join(tmpDir, "file1.txt"), []byte("1"), 0644) + + f, err := os.Open(tmpDir) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + infos, err := f.Readdir(-1) + if err != nil { + t.Errorf("File.Readdir failed: %v", err) + } + if len(infos) != 1 { + t.Errorf("File.Readdir returned %d infos, want 1", len(infos)) + } +} + +func TestFileReaddirnames(t *testing.T) { + tmpDir := t.TempDir() + os.WriteFile(filepath.Join(tmpDir, "file1.txt"), []byte("1"), 0644) + + f, err := os.Open(tmpDir) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + names, err := f.Readdirnames(-1) + if err != nil { + t.Errorf("File.Readdirnames failed: %v", err) + } + if len(names) != 1 || names[0] != "file1.txt" { + t.Errorf("File.Readdirnames = %v, want [file1.txt]", names) + } +} + +func TestErrorFunctions(t *testing.T) { + err := &os.PathError{Op: "open", Path: "/nonexistent", Err: syscall.ENOENT} + if !os.IsNotExist(err) { + t.Error("IsNotExist should return true for ENOENT") + } + + err2 := &os.PathError{Op: "open", Path: "/exists", Err: syscall.EEXIST} + if !os.IsExist(err2) { + t.Error("IsExist should return true for EEXIST") + } + + err3 := &os.PathError{Op: "open", Path: "/denied", Err: syscall.EACCES} + if !os.IsPermission(err3) { + t.Error("IsPermission should return true for EACCES") + } + + if os.IsNotExist(nil) { + t.Error("IsNotExist should return false for nil") + } +} + +func TestNewSyscallError(t *testing.T) { + err := os.NewSyscallError("test_syscall", syscall.EINVAL) + if err == nil { + t.Error("NewSyscallError returned nil") + } + if !strings.Contains(err.Error(), "test_syscall") { + t.Errorf("NewSyscallError().Error() = %q, should contain test_syscall", err.Error()) + } +} + +func TestSyscallError(t *testing.T) { + serr := &os.SyscallError{ + Syscall: "open", + Err: syscall.EINVAL, + } + + errStr := serr.Error() + if !strings.Contains(errStr, "open") { + t.Errorf("SyscallError.Error() = %q, should contain open", errStr) + } + + unwrapped := serr.Unwrap() + if unwrapped != syscall.EINVAL { + t.Errorf("SyscallError.Unwrap() = %v, want EINVAL", unwrapped) + } +} + +func TestLinkError(t *testing.T) { + lerr := &os.LinkError{ + Op: "symlink", + Old: "/old", + New: "/new", + Err: syscall.EEXIST, + } + + errStr := lerr.Error() + if !strings.Contains(errStr, "symlink") || !strings.Contains(errStr, "/old") { + t.Errorf("LinkError.Error() = %q, should contain op and old path", errStr) + } + + unwrapped := lerr.Unwrap() + if unwrapped != syscall.EEXIST { + t.Errorf("LinkError.Unwrap() = %v, want EEXIST", unwrapped) + } +} + +func TestFindProcess(t *testing.T) { + pid := os.Getpid() + proc, err := os.FindProcess(pid) + if err != nil { + t.Errorf("FindProcess(%d) failed: %v", pid, err) + } + if proc == nil { + t.Error("FindProcess returned nil process") + } +} + +func TestConstants(t *testing.T) { + if os.DevNull == "" { + t.Error("DevNull constant is empty") + } + + if os.PathSeparator == 0 { + t.Error("PathSeparator is zero") + } + + if os.PathListSeparator == 0 { + t.Error("PathListSeparator is zero") + } + + openFlags := []int{os.O_RDONLY, os.O_WRONLY, os.O_APPEND, os.O_EXCL, os.O_SYNC, os.O_TRUNC} + for _, flag := range openFlags { + _ = flag + } + + seekModes := []int{os.SEEK_SET, os.SEEK_CUR, os.SEEK_END} + for _, mode := range seekModes { + _ = mode + } +} + +func TestFileMode(t *testing.T) { + modes := []os.FileMode{ + os.ModeDir, + os.ModeAppend, + os.ModeExclusive, + os.ModeTemporary, + os.ModeSymlink, + os.ModeDevice, + os.ModeNamedPipe, + os.ModeSocket, + os.ModeSetuid, + os.ModeSetgid, + os.ModeCharDevice, + os.ModeSticky, + os.ModeIrregular, + os.ModeType, + os.ModePerm, + } + + for _, mode := range modes { + _ = mode + } +} + +func TestErrorConstants(t *testing.T) { + errors := []error{ + os.ErrInvalid, + os.ErrPermission, + os.ErrExist, + os.ErrNotExist, + os.ErrClosed, + os.ErrNoDeadline, + os.ErrDeadlineExceeded, + os.ErrProcessDone, + } + + for _, err := range errors { + if err == nil { + t.Error("Error constant is nil") + } + } +} + +func TestStdFiles(t *testing.T) { + if os.Stdin == nil { + t.Error("Stdin is nil") + } + if os.Stdout == nil { + t.Error("Stdout is nil") + } + if os.Stderr == nil { + t.Error("Stderr is nil") + } + + if os.Stdin.Name() != "/dev/stdin" && os.Stdin.Name() != "stdin" && os.Stdin.Name() != "/dev/fd/0" { + t.Logf("Stdin.Name() = %q", os.Stdin.Name()) + } +} + +func TestArgs(t *testing.T) { + if os.Args == nil { + t.Error("Args is nil") + } + if len(os.Args) == 0 { + t.Error("Args is empty") + } +} + +func TestIsTimeout(t *testing.T) { + if os.IsTimeout(nil) { + t.Error("IsTimeout should return false for nil") + } + + serr := &os.SyscallError{ + Syscall: "test", + Err: syscall.ETIMEDOUT, + } + if !serr.Timeout() { + t.Error("SyscallError.Timeout() should return true for ETIMEDOUT") + } +} + +func TestFileChown(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Chown not supported on Windows") + } + if os.Getuid() != 0 { + t.Skip("File.Chown requires root privileges") + } + + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "chown_file_test.txt") + + f, err := os.Create(testFile) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + if err := f.Chown(os.Getuid(), os.Getgid()); err != nil { + t.Errorf("File.Chown failed: %v", err) + } +} + +func TestFileReadFrom(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "readfrom_test.txt") + + f, err := os.Create(testFile) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + src := strings.NewReader("test data from reader") + n, err := f.ReadFrom(src) + if err != nil { + t.Errorf("File.ReadFrom failed: %v", err) + } + if n != int64(len("test data from reader")) { + t.Errorf("File.ReadFrom wrote %d bytes, want %d", n, len("test data from reader")) + } +} + +func TestFileWriteTo(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "writeto_test.txt") + content := []byte("test data") + + if err := os.WriteFile(testFile, content, 0644); err != nil { + t.Fatal(err) + } + + f, err := os.Open(testFile) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + var buf strings.Builder + n, err := f.WriteTo(&buf) + if err != nil { + t.Errorf("File.WriteTo failed: %v", err) + } + if n != int64(len(content)) || buf.String() != string(content) { + t.Errorf("File.WriteTo wrote %d bytes: %q, want %d: %q", n, buf.String(), len(content), content) + } +} + +func TestFileSetDeadline(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "deadline_test.txt") + + f, err := os.Create(testFile) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + deadline := time.Now().Add(time.Second) + err = f.SetDeadline(deadline) + if err != nil && err != os.ErrNoDeadline { + t.Logf("File.SetDeadline: %v (may not be supported)", err) + } + + err = f.SetReadDeadline(deadline) + if err != nil && err != os.ErrNoDeadline { + t.Logf("File.SetReadDeadline: %v (may not be supported)", err) + } + + err = f.SetWriteDeadline(deadline) + if err != nil && err != os.ErrNoDeadline { + t.Logf("File.SetWriteDeadline: %v (may not be supported)", err) + } +} + +func TestFileSyscallConn(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "sysconn_test.txt") + + f, err := os.Create(testFile) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + conn, err := f.SyscallConn() + if err != nil { + t.Logf("File.SyscallConn: %v (may not be supported)", err) + } else if conn == nil { + t.Error("File.SyscallConn returned nil without error") + } +} + +func TestStartProcess(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("StartProcess test skipped on Windows") + } + + exePath, err := os.Executable() + if err != nil { + t.Fatal(err) + } + + attr := &os.ProcAttr{ + Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}, + } + + proc, err := os.StartProcess("/bin/echo", []string{"echo", "test"}, attr) + if err != nil { + t.Errorf("StartProcess failed: %v", err) + } + + if proc != nil { + state, err := proc.Wait() + if err != nil { + t.Errorf("Process.Wait failed: %v", err) + } + + if state != nil { + if !state.Success() { + t.Error("Process did not exit successfully") + } + if !state.Exited() { + t.Error("Process.Exited() returned false") + } + if pid := state.Pid(); pid <= 0 { + t.Errorf("ProcessState.Pid() = %d, want > 0", pid) + } + if code := state.ExitCode(); code != 0 { + t.Logf("ProcessState.ExitCode() = %d", code) + } + if str := state.String(); str == "" { + t.Error("ProcessState.String() returned empty") + } + if sys := state.SystemTime(); sys < 0 { + t.Errorf("ProcessState.SystemTime() = %v, want >= 0", sys) + } + if user := state.UserTime(); user < 0 { + t.Errorf("ProcessState.UserTime() = %v, want >= 0", user) + } + if state.Sys() == nil { + t.Log("ProcessState.Sys() returned nil") + } + if state.SysUsage() == nil { + t.Log("ProcessState.SysUsage() returned nil") + } + } + + err = proc.Release() + if err != nil { + t.Logf("Process.Release: %v", err) + } + } + + _ = exePath +} + +func TestProcessSignal(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Signal test skipped on Windows") + } + + proc, err := os.FindProcess(os.Getpid()) + if err != nil { + t.Fatal(err) + } + + var sig os.Signal = syscall.Signal(0) + err = proc.Signal(sig) + if err != nil { + t.Logf("Process.Signal(0): %v", err) + } +} + +func TestProcessKill(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Kill test skipped on Windows") + } + + attr := &os.ProcAttr{ + Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}, + } + + proc, err := os.StartProcess("/bin/sleep", []string{"sleep", "60"}, attr) + if err != nil { + t.Skipf("StartProcess failed: %v", err) + } + defer proc.Kill() + + if err := proc.Kill(); err != nil { + t.Errorf("Process.Kill failed: %v", err) + } + + proc.Wait() +} + +func TestRoot(t *testing.T) { + tmpDir := t.TempDir() + + root, err := os.OpenRoot(tmpDir) + if err != nil { + t.Skipf("OpenRoot not supported: %v", err) + } + defer root.Close() + + if name := root.Name(); name != tmpDir { + t.Errorf("Root.Name() = %q, want %q", name, tmpDir) + } + + testFile := "test.txt" + f, err := root.Create(testFile) + if err != nil { + t.Errorf("Root.Create failed: %v", err) + } + if f != nil { + f.WriteString("content") + f.Close() + } + + f, err = root.Open(testFile) + if err != nil { + t.Errorf("Root.Open failed: %v", err) + } + if f != nil { + f.Close() + } + + info, err := root.Stat(testFile) + if err != nil { + t.Errorf("Root.Stat failed: %v", err) + } else if info.Name() != testFile { + t.Errorf("Root.Stat().Name() = %q, want %q", info.Name(), testFile) + } + + info, err = root.Lstat(testFile) + if err != nil { + t.Errorf("Root.Lstat failed: %v", err) + } else if info.Name() != testFile { + t.Errorf("Root.Lstat().Name() = %q, want %q", info.Name(), testFile) + } + + subDir := "subdir" + err = root.Mkdir(subDir, 0755) + if err != nil { + t.Errorf("Root.Mkdir failed: %v", err) + } + + subRoot, err := root.OpenRoot(subDir) + if err != nil { + t.Errorf("Root.OpenRoot failed: %v", err) + } + if subRoot != nil { + subRoot.Close() + } + + f, err = root.OpenFile(testFile, os.O_RDONLY, 0) + if err != nil { + t.Errorf("Root.OpenFile failed: %v", err) + } + if f != nil { + f.Close() + } + + fsys := root.FS() + if fsys == nil { + t.Error("Root.FS() returned nil") + } + + err = root.Remove(testFile) + if err != nil { + t.Errorf("Root.Remove failed: %v", err) + } +} + +func TestNewFile(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "newfile_test.txt") + + f1, err := os.Create(testFile) + if err != nil { + t.Fatal(err) + } + + fd := f1.Fd() + f2 := os.NewFile(fd, testFile) + if f2 == nil { + t.Error("NewFile returned nil") + } + if f2.Name() != testFile { + t.Errorf("NewFile().Name() = %q, want %q", f2.Name(), testFile) + } + + f1.Close() +} + +func TestOpenInRoot(t *testing.T) { + tmpDir := t.TempDir() + testFile := "test.txt" + fullPath := filepath.Join(tmpDir, testFile) + if err := os.WriteFile(fullPath, []byte("content"), 0644); err != nil { + t.Fatal(err) + } + + f, err := os.OpenInRoot(tmpDir, testFile) + if err != nil { + t.Skipf("OpenInRoot not supported: %v", err) + } + if f != nil { + defer f.Close() + buf := make([]byte, 7) + n, _ := f.Read(buf) + if string(buf[:n]) != "content" { + t.Errorf("OpenInRoot read %q, want content", buf[:n]) + } + } +} + +func TestDirEntryAndFileInfo(t *testing.T) { + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "entry_test.txt") + if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil { + t.Fatal(err) + } + + entries, err := os.ReadDir(tmpDir) + if err != nil { + t.Fatal(err) + } + + var entry os.DirEntry + for _, e := range entries { + if e.Name() == "entry_test.txt" { + entry = e + break + } + } + + if entry == nil { + t.Fatal("File not found in ReadDir") + } + + info, err := entry.Info() + if err != nil { + t.Errorf("DirEntry.Info failed: %v", err) + } + + var _ os.FileInfo = info + var _ os.FileMode = info.Mode() +} + +func TestExit(t *testing.T) { + _ = os.Exit +} diff --git a/test/std/os/signal/signal_test.go b/test/std/os/signal/signal_test.go new file mode 100644 index 0000000000..23f413c05c --- /dev/null +++ b/test/std/os/signal/signal_test.go @@ -0,0 +1,280 @@ +package signal_test + +import ( + "context" + "os" + "os/signal" + "runtime" + "syscall" + "testing" + "time" +) + +func TestNotify(t *testing.T) { + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + t.Skip("Skipping on Windows and Plan 9") + } + + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGWINCH) + defer signal.Stop(c) + + proc, err := os.FindProcess(os.Getpid()) + if err != nil { + t.Fatalf("FindProcess error: %v", err) + } + + err = proc.Signal(syscall.SIGWINCH) + if err != nil { + t.Fatalf("Signal error: %v", err) + } + + select { + case sig := <-c: + if sig != syscall.SIGWINCH { + t.Errorf("Received signal %v, want SIGWINCH", sig) + } + case <-time.After(time.Second): + t.Fatal("Timeout waiting for signal") + } +} + +func TestNotifyMultipleSignals(t *testing.T) { + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + t.Skip("Skipping on Windows and Plan 9") + } + + c := make(chan os.Signal, 2) + signal.Notify(c, syscall.SIGWINCH, syscall.SIGCHLD) + defer signal.Stop(c) + + proc, err := os.FindProcess(os.Getpid()) + if err != nil { + t.Fatalf("FindProcess error: %v", err) + } + + err = proc.Signal(syscall.SIGWINCH) + if err != nil { + t.Fatalf("Signal SIGWINCH error: %v", err) + } + + timeout := time.After(time.Second) + for { + select { + case sig := <-c: + // SIGCHLD may arrive first, so wait for the signal sent above. + if sig == syscall.SIGWINCH { + return + } + case <-timeout: + t.Fatal("Timeout waiting for SIGWINCH") + } + } +} + +func TestStop(t *testing.T) { + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + t.Skip("Skipping on Windows and Plan 9") + } + + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGWINCH) + signal.Stop(c) + + proc, err := os.FindProcess(os.Getpid()) + if err != nil { + t.Fatalf("FindProcess error: %v", err) + } + + err = proc.Signal(syscall.SIGWINCH) + if err != nil { + t.Fatalf("Signal error: %v", err) + } + + select { + case sig := <-c: + t.Errorf("Received signal %v after Stop", sig) + case <-time.After(100 * time.Millisecond): + } +} + +func TestReset(t *testing.T) { + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + t.Skip("Skipping on Windows and Plan 9") + } + + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGWINCH) + signal.Reset(syscall.SIGWINCH) + + proc, err := os.FindProcess(os.Getpid()) + if err != nil { + t.Fatalf("FindProcess error: %v", err) + } + + err = proc.Signal(syscall.SIGWINCH) + if err != nil { + t.Fatalf("Signal error: %v", err) + } + + select { + case sig := <-c: + t.Errorf("Received signal %v after Reset", sig) + case <-time.After(100 * time.Millisecond): + } +} + +func TestResetAll(t *testing.T) { + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + t.Skip("Skipping on Windows and Plan 9") + } + + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGWINCH) + signal.Reset() + + proc, err := os.FindProcess(os.Getpid()) + if err != nil { + t.Fatalf("FindProcess error: %v", err) + } + + err = proc.Signal(syscall.SIGWINCH) + if err != nil { + t.Fatalf("Signal error: %v", err) + } + + select { + case sig := <-c: + t.Errorf("Received signal %v after Reset()", sig) + case <-time.After(100 * time.Millisecond): + } +} + +func TestIgnore(t *testing.T) { + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + t.Skip("Skipping on Windows and Plan 9") + } + + signal.Ignore(syscall.SIGWINCH) + defer signal.Reset(syscall.SIGWINCH) + + proc, err := os.FindProcess(os.Getpid()) + if err != nil { + t.Fatalf("FindProcess error: %v", err) + } + + err = proc.Signal(syscall.SIGWINCH) + if err != nil { + t.Fatalf("Signal error: %v", err) + } + + time.Sleep(100 * time.Millisecond) +} + +func TestIgnored(t *testing.T) { + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + t.Skip("Skipping on Windows and Plan 9") + } + + wasIgnored := signal.Ignored(syscall.SIGWINCH) + + signal.Ignore(syscall.SIGWINCH) + defer signal.Reset(syscall.SIGWINCH) + + if !signal.Ignored(syscall.SIGWINCH) { + t.Error("Expected SIGWINCH to be ignored after Ignore()") + } + + signal.Reset(syscall.SIGWINCH) + + afterReset := signal.Ignored(syscall.SIGWINCH) + if afterReset != wasIgnored { + t.Logf("Signal ignored state changed after Reset: was=%v, after=%v", wasIgnored, afterReset) + } +} + +func TestNotifyContext(t *testing.T) { + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + t.Skip("Skipping on Windows and Plan 9") + } + + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGWINCH) + defer stop() + + select { + case <-ctx.Done(): + t.Error("Context should not be done before signal") + case <-time.After(100 * time.Millisecond): + } + + proc, err := os.FindProcess(os.Getpid()) + if err != nil { + t.Fatalf("FindProcess error: %v", err) + } + + err = proc.Signal(syscall.SIGWINCH) + if err != nil { + t.Fatalf("Signal error: %v", err) + } + + select { + case <-ctx.Done(): + case <-time.After(time.Second): + t.Fatal("Timeout waiting for context cancellation") + } +} + +func TestNotifyContextStop(t *testing.T) { + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + t.Skip("Skipping on Windows and Plan 9") + } + + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGWINCH) + + stop() + + select { + case <-ctx.Done(): + case <-time.After(time.Second): + t.Fatal("Context should be cancelled after stop()") + } +} + +func TestMultipleChannels(t *testing.T) { + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + t.Skip("Skipping on Windows and Plan 9") + } + + c1 := make(chan os.Signal, 1) + c2 := make(chan os.Signal, 1) + + signal.Notify(c1, syscall.SIGWINCH) + signal.Notify(c2, syscall.SIGWINCH) + defer signal.Stop(c1) + defer signal.Stop(c2) + + proc, err := os.FindProcess(os.Getpid()) + if err != nil { + t.Fatalf("FindProcess error: %v", err) + } + + err = proc.Signal(syscall.SIGWINCH) + if err != nil { + t.Fatalf("Signal error: %v", err) + } + + receivedC1 := false + receivedC2 := false + + timeout := time.After(time.Second) + for !receivedC1 || !receivedC2 { + select { + case <-c1: + receivedC1 = true + case <-c2: + receivedC2 = true + case <-timeout: + t.Fatal("Timeout waiting for signals on both channels") + } + } +} diff --git a/test/std/os/user/user_test.go b/test/std/os/user/user_test.go new file mode 100644 index 0000000000..65fe233ea5 --- /dev/null +++ b/test/std/os/user/user_test.go @@ -0,0 +1,243 @@ +package user_test + +import ( + "os/user" + "runtime" + "testing" +) + +func TestCurrent(t *testing.T) { + u, err := user.Current() + if err != nil { + t.Fatalf("Current error: %v", err) + } + + if u == nil { + t.Fatal("Current returned nil user") + } + + if u.Uid == "" { + t.Error("User Uid is empty") + } + + if u.Username == "" { + t.Error("User Username is empty") + } +} + +func TestLookup(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + u, err := user.Lookup("root") + if err != nil { + t.Skipf("Lookup(root) error: %v", err) + } + + if u == nil { + t.Fatal("Lookup returned nil user") + } + + if u.Uid != "0" { + t.Errorf("root Uid = %q, want %q", u.Uid, "0") + } + + if u.Username != "root" { + t.Errorf("root Username = %q, want %q", u.Username, "root") + } +} + +func TestLookupNonexistent(t *testing.T) { + _, err := user.Lookup("nonexistent_user_12345") + if err == nil { + t.Error("Expected error for nonexistent user") + } + + _, ok := err.(user.UnknownUserError) + if !ok { + t.Errorf("Error type = %T, want UnknownUserError", err) + } +} + +func TestLookupId(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + u, err := user.LookupId("0") + if err != nil { + t.Skipf("LookupId(0) error: %v", err) + } + + if u == nil { + t.Fatal("LookupId returned nil user") + } + + if u.Uid != "0" { + t.Errorf("User Uid = %q, want %q", u.Uid, "0") + } +} + +func TestLookupIdNonexistent(t *testing.T) { + _, err := user.LookupId("99999999") + if err == nil { + t.Error("Expected error for nonexistent uid") + } + + _, ok := err.(user.UnknownUserIdError) + if !ok { + t.Errorf("Error type = %T, want UnknownUserIdError", err) + } +} + +func TestUserGroupIds(t *testing.T) { + u, err := user.Current() + if err != nil { + t.Fatalf("Current error: %v", err) + } + + gids, err := u.GroupIds() + if err != nil { + t.Skipf("GroupIds error: %v", err) + } + + if len(gids) == 0 { + t.Error("GroupIds returned empty slice") + } +} + +func TestLookupGroup(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + g, err := user.LookupGroup("root") + if err != nil { + t.Skipf("LookupGroup(root) error: %v", err) + } + + if g == nil { + t.Fatal("LookupGroup returned nil group") + } + + if g.Gid == "" { + t.Error("Group Gid is empty") + } + + if g.Name != "root" { + t.Errorf("Group Name = %q, want %q", g.Name, "root") + } +} + +func TestLookupGroupNonexistent(t *testing.T) { + _, err := user.LookupGroup("nonexistent_group_12345") + if err == nil { + t.Error("Expected error for nonexistent group") + } + + _, ok := err.(user.UnknownGroupError) + if !ok { + t.Errorf("Error type = %T, want UnknownGroupError", err) + } +} + +func TestLookupGroupId(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + g, err := user.LookupGroupId("0") + if err != nil { + t.Skipf("LookupGroupId(0) error: %v", err) + } + + if g == nil { + t.Fatal("LookupGroupId returned nil group") + } + + if g.Gid != "0" { + t.Errorf("Group Gid = %q, want %q", g.Gid, "0") + } +} + +func TestLookupGroupIdNonexistent(t *testing.T) { + _, err := user.LookupGroupId("99999999") + if err == nil { + t.Error("Expected error for nonexistent gid") + } + + _, ok := err.(user.UnknownGroupIdError) + if !ok { + t.Errorf("Error type = %T, want UnknownGroupIdError", err) + } +} + +func TestUserFields(t *testing.T) { + u, err := user.Current() + if err != nil { + t.Fatalf("Current error: %v", err) + } + + if u.Uid == "" { + t.Error("User.Uid is empty") + } + if u.Gid == "" { + t.Error("User.Gid is empty") + } + if u.Username == "" { + t.Error("User.Username is empty") + } +} + +func TestGroupFields(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping on Windows") + } + + g, err := user.LookupGroup("root") + if err != nil { + t.Skipf("LookupGroup error: %v", err) + } + + if g.Gid == "" { + t.Error("Group.Gid is empty") + } + if g.Name == "" { + t.Error("Group.Name is empty") + } + + var _ *user.Group = g +} + +func TestUnknownUserError(t *testing.T) { + err := user.UnknownUserError("testuser") + errStr := err.Error() + if errStr == "" { + t.Error("UnknownUserError.Error() returned empty string") + } +} + +func TestUnknownUserIdError(t *testing.T) { + err := user.UnknownUserIdError(12345) + errStr := err.Error() + if errStr == "" { + t.Error("UnknownUserIdError.Error() returned empty string") + } +} + +func TestUnknownGroupError(t *testing.T) { + err := user.UnknownGroupError("testgroup") + errStr := err.Error() + if errStr == "" { + t.Error("UnknownGroupError.Error() returned empty string") + } +} + +func TestUnknownGroupIdError(t *testing.T) { + err := user.UnknownGroupIdError("12345") + errStr := err.Error() + if errStr == "" { + t.Error("UnknownGroupIdError.Error() returned empty string") + } +} diff --git a/test/std/path/filepath/filepath_test.go b/test/std/path/filepath/filepath_test.go new file mode 100644 index 0000000000..14c9564ec6 --- /dev/null +++ b/test/std/path/filepath/filepath_test.go @@ -0,0 +1,327 @@ +package filepath_test + +import ( + "errors" + "io/fs" + "os" + "path/filepath" + "runtime" + "sort" + "strings" + "testing" + "time" +) + +func contains[T comparable](items []T, target T) bool { + for _, v := range items { + if v == target { + return true + } + } + return false +} + +func TestFilepathConstants(t *testing.T) { + if filepath.Separator != os.PathSeparator { + t.Fatalf("Separator mismatch: got %q want %q", filepath.Separator, os.PathSeparator) + } + if filepath.ListSeparator != os.PathListSeparator { + t.Fatalf("ListSeparator mismatch: got %q want %q", filepath.ListSeparator, os.PathListSeparator) + } + if !errors.Is(filepath.ErrBadPattern, filepath.ErrBadPattern) { + t.Fatal("ErrBadPattern should compare equal to itself") + } + if filepath.SkipDir != fs.SkipDir { + t.Fatalf("SkipDir mismatch: got %v want %v", filepath.SkipDir, fs.SkipDir) + } + if filepath.SkipAll != fs.SkipAll { + t.Fatalf("SkipAll mismatch: got %v want %v", filepath.SkipAll, fs.SkipAll) + } +} + +func TestFilepathCleanSplitAndBase(t *testing.T) { + seps := string(filepath.Separator) + dirty := strings.Join([]string{"foo", ".", "bar", "..", "baz"}, seps) + if got := filepath.Clean(dirty); got != filepath.Join("foo", "baz") { + t.Fatalf("Clean(%q) = %q", dirty, got) + } + + if got := filepath.Base(filepath.Join("foo", "bar", "file.txt")); got != "file.txt" { + t.Fatalf("Base mismatch: %q", got) + } + + if got := filepath.Dir(filepath.Join("foo", "bar", "file.txt")); got != filepath.Join("foo", "bar") { + t.Fatalf("Dir mismatch: %q", got) + } + + if ext := filepath.Ext("archive.tar.gz"); ext != ".gz" { + t.Fatalf("Ext mismatch: %q", ext) + } + + dir, file := filepath.Split(filepath.Join("pkg", "module.go")) + if dir != "pkg"+seps || file != "module.go" { + t.Fatalf("Split mismatch: dir=%q file=%q", dir, file) + } + + parts := filepath.SplitList(strings.Join([]string{"a", "b"}, string(filepath.ListSeparator))) + if len(parts) != 2 || parts[0] != "a" || parts[1] != "b" { + t.Fatalf("SplitList mismatch: %v", parts) + } + + if converted := filepath.FromSlash("alpha/beta"); converted != filepath.Join("alpha", "beta") { + t.Fatalf("FromSlash mismatch: %q", converted) + } + if slashed := filepath.ToSlash(filepath.Join("alpha", "beta")); slashed != "alpha/beta" { + t.Fatalf("ToSlash mismatch: %q", slashed) + } +} + +func TestFilepathAbsIsAbsAndRel(t *testing.T) { + wd, err := os.Getwd() + if err != nil { + t.Fatalf("Getwd error: %v", err) + } + + abs, err := filepath.Abs(".") + if err != nil { + t.Fatalf("Abs error: %v", err) + } + if abs != wd { + t.Fatalf("Abs mismatch: got %q want %q", abs, wd) + } + + if !filepath.IsAbs(wd) { + t.Fatalf("IsAbs should report true for working directory %q", wd) + } + if filepath.IsAbs(strings.Join([]string{"relative", "path"}, string(filepath.Separator))) { + t.Fatal("IsAbs should be false for relative path") + } + + base := filepath.Join("root", "sub") + target := filepath.Join(base, "child", "file.txt") + rel, err := filepath.Rel(base, target) + if err != nil { + t.Fatalf("Rel error: %v", err) + } + if rel != filepath.Join("child", "file.txt") { + t.Fatalf("Rel mismatch: %q", rel) + } + + if !filepath.HasPrefix(target, base) { + t.Fatalf("HasPrefix should report true for %q within %q", target, base) + } + if filepath.HasPrefix(base, target) { + t.Fatalf("HasPrefix should be false for %q within %q", base, target) + } + + if !filepath.IsLocal("dir/file") { + t.Fatal("IsLocal should accept simple relative path") + } + if filepath.IsLocal("../escape") { + t.Fatal("IsLocal should reject parent traversal") + } + if filepath.IsLocal("") { + t.Fatal("IsLocal should reject empty path") + } + + localized, err := filepath.Localize("dir/file") + if err != nil { + t.Fatalf("Localize error: %v", err) + } + if localized != filepath.Join("dir", "file") { + t.Fatalf("Localize mismatch: %q", localized) + } + if !filepath.IsLocal(localized) { + t.Fatal("Localize should return local path") + } + if _, err := filepath.Localize(".."); err == nil { + t.Fatal("Localize should reject parent traversal") + } +} + +func TestFilepathMatchAndGlob(t *testing.T) { + root := t.TempDir() + mustWrite(t, filepath.Join(root, "main.go")) + mustWrite(t, filepath.Join(root, "util.go")) + mustWrite(t, filepath.Join(root, "README.md")) + + pattern := filepath.Join(root, "*.go") + matches, err := filepath.Glob(pattern) + if err != nil { + t.Fatalf("Glob error: %v", err) + } + for i := range matches { + rel, err := filepath.Rel(root, matches[i]) + if err != nil { + t.Fatalf("Rel error: %v", err) + } + matches[i] = rel + } + sort.Strings(matches) + if want := []string{"main.go", "util.go"}; !equalStrings(matches, want) { + t.Fatalf("Glob mismatch: %v", matches) + } + + ok, err := filepath.Match("*.go", "main.go") + if err != nil || !ok { + t.Fatalf("Match expected true, err=%v", err) + } + _, err = filepath.Match("[invalid", "file") + if !errors.Is(err, filepath.ErrBadPattern) { + t.Fatalf("Match should return ErrBadPattern, got %v", err) + } +} + +func TestFilepathWalkAndWalkDir(t *testing.T) { + root := t.TempDir() + mustWrite(t, filepath.Join(root, "file.txt")) + skipDir := filepath.Join(root, "skip") + keepDir := filepath.Join(root, "keep") + if err := os.MkdirAll(skipDir, 0o755); err != nil { + t.Fatalf("Mkdir skip error: %v", err) + } + if err := os.MkdirAll(keepDir, 0o755); err != nil { + t.Fatalf("Mkdir keep error: %v", err) + } + mustWrite(t, filepath.Join(skipDir, "hidden.txt")) + mustWrite(t, filepath.Join(keepDir, "visible.txt")) + + var wf filepath.WalkFunc = func(path string, info fs.FileInfo, err error) error { + return err + } + if err := wf("unused", nil, nil); err != nil { + t.Fatalf("WalkFunc invocation returned unexpected error: %v", err) + } + + var visited []string + runWithTimeout(t, func() { + err := filepath.Walk(root, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + rel, err := filepath.Rel(root, path) + if err != nil { + return err + } + if rel == "." { + visited = append(visited, rel) + return nil + } + visited = append(visited, rel) + if info.IsDir() && info.Name() == "skip" { + return filepath.SkipDir + } + return nil + }) + if err != nil { + t.Fatalf("Walk error: %v", err) + } + }) + if contains(visited, filepath.Join("skip", "hidden.txt")) { + t.Fatalf("Walk should skip contents of skip dir: %v", visited) + } + if !contains(visited, filepath.Join("keep", "visible.txt")) { + t.Fatalf("Walk should visit keep directory contents: %v", visited) + } + + var walkDirVisited []string + runWithTimeout(t, func() { + err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + rel, err := filepath.Rel(root, path) + if err != nil { + return err + } + walkDirVisited = append(walkDirVisited, rel) + if d.IsDir() && d.Name() == "skip" { + return filepath.SkipDir + } + return nil + }) + if err != nil { + t.Fatalf("WalkDir error: %v", err) + } + }) + if !contains(walkDirVisited, ".") { + t.Fatalf("WalkDir should include root: %v", walkDirVisited) + } + if !contains(walkDirVisited, filepath.Join("keep", "visible.txt")) { + t.Fatalf("WalkDir should visit keep directory contents: %v", walkDirVisited) + } + if contains(walkDirVisited, filepath.Join("skip", "hidden.txt")) { + t.Fatalf("WalkDir should respect SkipDir: %v", walkDirVisited) + } +} + +func TestFilepathEvalSymlinks(t *testing.T) { + root := t.TempDir() + target := filepath.Join(root, "target.txt") + mustWrite(t, target) + link := filepath.Join(root, "link.txt") + if err := os.Symlink("target.txt", link); err != nil { + if runtime.GOOS == "windows" || errors.Is(err, fs.ErrInvalid) { + t.Skipf("symlinks unavailable: %v", err) + } + if os.IsPermission(err) { + t.Skipf("symlink permissions denied: %v", err) + } + t.Fatalf("Symlink error: %v", err) + } + + resolved, err := filepath.EvalSymlinks(link) + if err != nil { + t.Fatalf("EvalSymlinks error: %v", err) + } + targetResolved, err := filepath.EvalSymlinks(target) + if err != nil { + t.Fatalf("EvalSymlinks target error: %v", err) + } + if resolved != targetResolved { + t.Fatalf("EvalSymlinks mismatch: got %q want %q", resolved, targetResolved) + } +} + +func TestFilepathVolumeName(t *testing.T) { + if runtime.GOOS == "windows" { + if v := filepath.VolumeName(`C:\Windows\System32`); strings.ToUpper(v) != "C:" { + t.Fatalf("VolumeName mismatch on Windows: %q", v) + } + } else { + if v := filepath.VolumeName("/usr/bin"); v != "" { + t.Fatalf("VolumeName mismatch on Unix: %q", v) + } + } +} + +func runWithTimeout(t *testing.T, fn func()) { + t.Helper() + timer := time.AfterFunc(5*time.Second, func() { + panic("filepath test timeout") + }) + defer timer.Stop() + fn() +} + +func mustWrite(t *testing.T, path string) { + t.Helper() + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + t.Fatalf("MkdirAll error: %v", err) + } + if err := os.WriteFile(path, []byte("data"), 0o644); err != nil { + t.Fatalf("WriteFile error: %v", err) + } +} + +func equalStrings(got, want []string) bool { + if len(got) != len(want) { + return false + } + for i := range got { + if got[i] != want[i] { + return false + } + } + return true +} diff --git a/test/std/path/path_test.go b/test/std/path/path_test.go new file mode 100644 index 0000000000..be97e0ebf5 --- /dev/null +++ b/test/std/path/path_test.go @@ -0,0 +1,47 @@ +package path_test + +import ( + "errors" + "path" + "testing" +) + +func TestPathBasics(t *testing.T) { + if got := path.Base("/a/b/c.txt"); got != "c.txt" { + t.Fatalf("Base mismatch: got %q", got) + } + if got := path.Clean("//foo/./bar/.."); got != "/foo" { + t.Fatalf("Clean mismatch: got %q", got) + } + if got := path.Dir("/foo/bar/file"); got != "/foo/bar" { + t.Fatalf("Dir mismatch: got %q", got) + } + if got := path.Ext("file.tar.gz"); got != ".gz" { + t.Fatalf("Ext mismatch: got %q", got) + } + if !path.IsAbs("/absolute/path") { + t.Fatalf("IsAbs should report absolute path") + } +} + +func TestPathJoinAndSplit(t *testing.T) { + if got := path.Join("a", "b", "c"); got != "a/b/c" { + t.Fatalf("Join mismatch: got %q", got) + } + dir, file := path.Split("pkg/module.go") + if dir != "pkg/" || file != "module.go" { + t.Fatalf("Split mismatch: dir=%q file=%q", dir, file) + } +} + +func TestPathMatchAndErrors(t *testing.T) { + ok, err := path.Match("*.go", "main.go") + if err != nil || !ok { + t.Fatalf("Match expected true, err=%v", err) + } + + _, err = path.Match("[invalid", "file") + if !errors.Is(err, path.ErrBadPattern) { + t.Fatalf("Match should return ErrBadPattern, got %v", err) + } +} diff --git a/test/std/plugin/plugin_test.go b/test/std/plugin/plugin_test.go new file mode 100644 index 0000000000..1785b46a6e --- /dev/null +++ b/test/std/plugin/plugin_test.go @@ -0,0 +1,37 @@ +//go:build darwin || linux + +package plugin_test + +import ( + "path/filepath" + "plugin" + "reflect" + "strings" + "testing" +) + +func TestOpenMissingPlugin(t *testing.T) { + path := filepath.Join(t.TempDir(), "missing.so") + if _, err := plugin.Open(path); err == nil { + t.Fatal("Open of a missing plugin succeeded") + } else if !strings.Contains(err.Error(), "missing") { + t.Fatalf("Open error %q does not identify the missing plugin", err) + } +} + +func TestPluginAPISurface(t *testing.T) { + pluginType := reflect.TypeOf((*plugin.Plugin)(nil)).Elem() + if pluginType.Name() != "Plugin" || pluginType.PkgPath() != "plugin" { + t.Fatalf("unexpected Plugin type: %v from %q", pluginType, pluginType.PkgPath()) + } + + lookup := (*plugin.Plugin).Lookup + if reflect.ValueOf(lookup).Pointer() == 0 { + t.Fatal("Plugin.Lookup has no callable entry point") + } + + var symbol plugin.Symbol = "llgo" + if got, ok := symbol.(string); !ok || got != "llgo" { + t.Fatalf("Symbol did not preserve its dynamic value: %#v", symbol) + } +} diff --git a/test/std/reflect/container_test.go b/test/std/reflect/container_test.go new file mode 100644 index 0000000000..c57e0047c1 --- /dev/null +++ b/test/std/reflect/container_test.go @@ -0,0 +1,275 @@ +package reflect_test + +import ( + "reflect" + "testing" +) + +// Test slice operations +func TestSliceOperations(t *testing.T) { + s := []int{1, 2, 3, 4, 5} + v := reflect.ValueOf(s) + + // Test Len + if v.Len() != 5 { + t.Errorf("Len should be 5, got %d", v.Len()) + } + + // Test Index + elem := v.Index(2) + if elem.Int() != 3 { + t.Errorf("Index(2) should be 3, got %d", elem.Int()) + } +} + +// Test map operations +func TestMapOperations(t *testing.T) { + m := map[string]int{"a": 1, "b": 2} + v := reflect.ValueOf(m) + + // Test Len + if v.Len() != 2 { + t.Errorf("Len should be 2, got %d", v.Len()) + } + + // Test MapKeys + keys := v.MapKeys() + if len(keys) != 2 { + t.Errorf("MapKeys length should be 2, got %d", len(keys)) + } + + // Test MapIndex + key := reflect.ValueOf("a") + val := v.MapIndex(key) + if val.Int() != 1 { + t.Errorf("MapIndex('a') should be 1, got %d", val.Int()) + } +} + +// Test MapIter +func TestMapIter(t *testing.T) { + m := map[string]int{"a": 1, "b": 2, "c": 3} + v := reflect.ValueOf(m) + + iter := v.MapRange() + count := 0 + for iter.Next() { + key := iter.Key() + val := iter.Value() + if key.String() == "" { + t.Error("Key should not be empty") + } + if val.Int() == 0 { + t.Error("Value should not be zero") + } + count++ + } + if count != 3 { + t.Errorf("MapRange should iterate 3 times, got %d", count) + } +} + +// Test Value.Slice operations +func TestValueSlice(t *testing.T) { + s := []int{1, 2, 3, 4, 5} + v := reflect.ValueOf(s) + + // Test Slice + slice := v.Slice(1, 4) + if slice.Len() != 3 { + t.Errorf("Slice(1,4) length should be 3, got %d", slice.Len()) + } + if slice.Index(0).Int() != 2 { + t.Errorf("Slice[0] should be 2, got %d", slice.Index(0).Int()) + } + + // Test Slice3 + slice3 := v.Slice3(1, 3, 4) + if slice3.Len() != 2 { + t.Errorf("Slice3(1,3,4) length should be 2, got %d", slice3.Len()) + } + if slice3.Cap() != 3 { + t.Errorf("Slice3 cap should be 3, got %d", slice3.Cap()) + } +} + +// Test Value.Cap +func TestValueCap(t *testing.T) { + s := make([]int, 3, 5) + v := reflect.ValueOf(s) + + if v.Cap() != 5 { + t.Errorf("Cap should be 5, got %d", v.Cap()) + } +} + +// Test Value.SetMapIndex +func TestValueSetMapIndex(t *testing.T) { + m := make(map[string]int) + v := reflect.ValueOf(m) + + key := reflect.ValueOf("key") + val := reflect.ValueOf(100) + + v.SetMapIndex(key, val) + + if m["key"] != 100 { + t.Errorf("SetMapIndex: expected 100, got %d", m["key"]) + } + + // Delete by setting to zero value + v.SetMapIndex(key, reflect.Value{}) + if _, ok := m["key"]; ok { + t.Error("SetMapIndex with zero Value should delete key") + } +} + +// Test Value.Grow and Clear +func TestValueGrowClear(t *testing.T) { + s := make([]int, 2, 5) + v := reflect.ValueOf(&s).Elem() + + // Test Grow + v.Grow(3) + if v.Cap() < 5 { + t.Errorf("After Grow, cap should be at least 5, got %d", v.Cap()) + } + + // Test Clear - Clear zeros the slice elements but doesn't change length + s = []int{1, 2, 3} + v = reflect.ValueOf(&s).Elem() + v.Clear() + // After Clear, elements are zeroed but length remains + if v.Len() != 3 { + t.Errorf("After Clear, length should remain 3, got %d", v.Len()) + } + if v.Index(0).Int() != 0 { + t.Error("After Clear, elements should be zero") + } +} + +// Test Value.SetLen and SetCap +func TestValueSetLenCap(t *testing.T) { + s := make([]int, 2, 5) + v := reflect.ValueOf(&s).Elem() + + // Test SetLen + v.SetLen(4) + if len(s) != 4 { + t.Errorf("SetLen: expected length 4, got %d", len(s)) + } + + // Test SetCap - can only be called on addressable slices + // SetCap panics if new cap is less than len or greater than existing cap + // Just test that the method exists + defer func() { + recover() // SetCap may panic depending on implementation + }() + v.SetCap(5) +} + +// Test channel operations +func TestChannelOperations(t *testing.T) { + ch := make(chan int, 2) + v := reflect.ValueOf(ch) + + // Test Send + v.Send(reflect.ValueOf(1)) + v.Send(reflect.ValueOf(2)) + + // Test Recv + val, ok := v.Recv() + if !ok { + t.Fatal("Recv should succeed") + } + if val.Int() != 1 { + t.Errorf("Recv should get 1, got %d", val.Int()) + } + + // Test TryRecv + val, ok = v.TryRecv() + if !ok { + t.Fatal("TryRecv should succeed") + } + if val.Int() != 2 { + t.Errorf("TryRecv should get 2, got %d", val.Int()) + } + + // Test TrySend + ok = v.TrySend(reflect.ValueOf(3)) + if !ok { + t.Error("TrySend should succeed on buffered channel") + } + + // Test Close + v.Close() + + // After close, receive should still work until empty + val, ok = v.Recv() + if !ok { + t.Fatal("Recv should succeed after close (channel not empty)") + } +} + +// Test Select (basic) +func TestSelect(t *testing.T) { + ch1 := make(chan int, 1) + ch1 <- 42 + + cases := []reflect.SelectCase{ + {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch1)}, + } + + chosen, recv, recvOK := reflect.Select(cases) + if chosen != 0 { + t.Errorf("Select should choose case 0, got %d", chosen) + } + if !recvOK { + t.Error("Select recv should be OK") + } + if recv.Int() != 42 { + t.Errorf("Select should receive 42, got %d", recv.Int()) + } +} + +// Test MapIter.Reset +func TestMapIterReset(t *testing.T) { + m1 := map[string]int{"a": 1} + m2 := map[string]int{"b": 2} + + iter := reflect.ValueOf(m1).MapRange() + iter.Next() + + // Reset to different map + iter.Reset(reflect.ValueOf(m2)) + if iter.Next() { + key := iter.Key().String() + if key != "b" { + t.Errorf("After Reset, key should be 'b', got %q", key) + } + } +} + +// Test Value.SetIterKey and SetIterValue +func TestValueSetIter(t *testing.T) { + m := map[string]int{"a": 1, "b": 2} + iter := reflect.ValueOf(m).MapRange() + + var k string + var v int + + keyVal := reflect.ValueOf(&k).Elem() + valVal := reflect.ValueOf(&v).Elem() + + if iter.Next() { + keyVal.SetIterKey(iter) + valVal.SetIterValue(iter) + + if k == "" { + t.Error("SetIterKey should set key") + } + if v == 0 { + t.Error("SetIterValue should set value") + } + } +} diff --git a/test/std/reflect/go126_symbols_test.go b/test/std/reflect/go126_symbols_test.go new file mode 100644 index 0000000000..b558df9558 --- /dev/null +++ b/test/std/reflect/go126_symbols_test.go @@ -0,0 +1,52 @@ +//go:build go1.26 + +package reflect_test + +import ( + "reflect" + "testing" +) + +type go126Value struct { + Name string + Count int +} + +func (v go126Value) Summary(prefix string) string { + return prefix + v.Name +} + +func TestTypeAssert(t *testing.T) { + value := reflect.ValueOf(42) + if got, ok := reflect.TypeAssert[int](value); !ok || got != 42 { + t.Fatalf("TypeAssert[int] = %d, %v; want 42, true", got, ok) + } + if got, ok := reflect.TypeAssert[string](value); ok || got != "" { + t.Fatalf("TypeAssert[string] = %q, %v; want empty, false", got, ok) + } +} + +func TestValueFieldsAndMethods(t *testing.T) { + value := reflect.ValueOf(go126Value{Name: "llgo", Count: 2}) + fields := make(map[string]any) + for field, fieldValue := range value.Fields() { + fields[field.Name] = fieldValue.Interface() + } + if fields["Name"] != "llgo" || fields["Count"] != 2 || len(fields) != 2 { + t.Fatalf("Fields returned %#v", fields) + } + + called := false + for method, methodValue := range value.Methods() { + if method.Name == "Summary" { + called = true + result := methodValue.Call([]reflect.Value{reflect.ValueOf("value:")}) + if len(result) != 1 || result[0].String() != "value:llgo" { + t.Fatalf("bound method result = %#v", result) + } + } + } + if !called { + t.Fatal("Methods did not return Summary") + } +} diff --git a/test/std/reflect/method_test.go b/test/std/reflect/method_test.go new file mode 100644 index 0000000000..08878391fd --- /dev/null +++ b/test/std/reflect/method_test.go @@ -0,0 +1,115 @@ +package reflect_test + +import ( + "reflect" + "testing" +) + +// Type for testing method operations +type MyInt int + +func (m MyInt) Double() int { + return int(m) * 2 +} + +// Type for TestMethodStruct +type MyIntStringer int + +func (m MyIntStringer) String() string { return "myint" } + +// Test method operations +func TestMethodOperations(t *testing.T) { + var mi MyInt = 21 + typ := reflect.TypeOf(mi) + + // Test NumMethod + if typ.NumMethod() != 1 { + t.Errorf("NumMethod should be 1, got %d", typ.NumMethod()) + } + + // Test Method + method := typ.Method(0) + if method.Name != "Double" { + t.Errorf("Method(0).Name should be 'Double', got %q", method.Name) + } + + // Test MethodByName + method, ok := typ.MethodByName("Double") + if !ok { + t.Fatal("MethodByName('Double') should be found") + } + if method.Name != "Double" { + t.Errorf("Method.Name should be 'Double', got %q", method.Name) + } +} + +// Test Value.Method and Call +func TestValueMethodAndCall(t *testing.T) { + var mi MyInt = 21 + v := reflect.ValueOf(mi) + + // Test NumMethod + if v.NumMethod() != 1 { + t.Errorf("Value.NumMethod should be 1, got %d", v.NumMethod()) + } + + // Test Method + method := v.Method(0) + if !method.IsValid() { + t.Fatal("Value.Method(0) should be valid") + } + + // Test Call + results := method.Call(nil) + if len(results) != 1 { + t.Fatalf("Call should return 1 result, got %d", len(results)) + } + if results[0].Int() != 42 { + t.Errorf("Call result should be 42, got %d", results[0].Int()) + } + + // Test MethodByName + method = v.MethodByName("Double") + if !method.IsValid() { + t.Fatal("MethodByName('Double') should be valid") + } +} + +// Test Value.CallSlice +func TestValueCallSlice(t *testing.T) { + fn := func(args ...int) int { + sum := 0 + for _, arg := range args { + sum += arg + } + return sum + } + + v := reflect.ValueOf(fn) + args := []reflect.Value{ + reflect.ValueOf([]int{1, 2, 3}), + } + + results := v.CallSlice(args) + if len(results) != 1 { + t.Fatalf("CallSlice should return 1 result, got %d", len(results)) + } + if results[0].Int() != 6 { + t.Errorf("CallSlice result should be 6, got %d", results[0].Int()) + } +} + +// Test Method struct +func TestMethodStruct(t *testing.T) { + typ := reflect.TypeOf(MyIntStringer(0)) + method := typ.Method(0) + + _ = method.Name + _ = method.Type + _ = method.Func + + // Test IsExported + if !method.IsExported() { + t.Error("String method should be exported") + } +} diff --git a/test/std/reflect/reflect_test.go b/test/std/reflect/reflect_test.go new file mode 100644 index 0000000000..612e7cce89 --- /dev/null +++ b/test/std/reflect/reflect_test.go @@ -0,0 +1,215 @@ +package reflect_test + +import ( + "reflect" + "testing" +) + +// Test basic TypeOf and ValueOf +func TestBasicTypeAndValue(t *testing.T) { + // Test TypeOf + var i int = 42 + typ := reflect.TypeOf(i) + if typ == nil { + t.Fatal("TypeOf returned nil") + } + if typ.Kind() != reflect.Int { + t.Errorf("Kind should be Int, got %v", typ.Kind()) + } + + // Test ValueOf + val := reflect.ValueOf(i) + if !val.IsValid() { + t.Fatal("ValueOf returned invalid value") + } + if val.Kind() != reflect.Int { + t.Errorf("Value Kind should be Int, got %v", val.Kind()) + } + if val.Int() != 42 { + t.Errorf("Value should be 42, got %d", val.Int()) + } +} + +// Test Kind constants +func TestKindConstants(t *testing.T) { + kinds := []reflect.Kind{ + reflect.Invalid, + reflect.Bool, + reflect.Int, + reflect.Int8, + reflect.Int16, + reflect.Int32, + reflect.Int64, + reflect.Uint, + reflect.Uint8, + reflect.Uint16, + reflect.Uint32, + reflect.Uint64, + reflect.Uintptr, + reflect.Float32, + reflect.Float64, + reflect.Complex64, + reflect.Complex128, + reflect.Array, + reflect.Chan, + reflect.Func, + reflect.Interface, + reflect.Map, + reflect.Pointer, + reflect.Slice, + reflect.String, + reflect.Struct, + reflect.UnsafePointer, + } + + for _, k := range kinds { + if k.String() == "" { + t.Fatalf("Kind(%d).String returned empty", k) + } + } +} + +// Test DeepEqual +func TestDeepEqual(t *testing.T) { + // Test with basic types + if !reflect.DeepEqual(42, 42) { + t.Error("DeepEqual should be true for equal ints") + } + if reflect.DeepEqual(42, 43) { + t.Error("DeepEqual should be false for different ints") + } + + // Test with slices + s1 := []int{1, 2, 3} + s2 := []int{1, 2, 3} + s3 := []int{1, 2, 4} + if !reflect.DeepEqual(s1, s2) { + t.Error("DeepEqual should be true for equal slices") + } + if reflect.DeepEqual(s1, s3) { + t.Error("DeepEqual should be false for different slices") + } + + // Test with maps + m1 := map[string]int{"a": 1, "b": 2} + m2 := map[string]int{"a": 1, "b": 2} + m3 := map[string]int{"a": 1, "b": 3} + if !reflect.DeepEqual(m1, m2) { + t.Error("DeepEqual should be true for equal maps") + } + if reflect.DeepEqual(m1, m3) { + t.Error("DeepEqual should be false for different maps") + } +} + +// Test TypeOf with nil +func TestTypeOfNil(t *testing.T) { + typ := reflect.TypeOf(nil) + if typ != nil { + t.Error("TypeOf(nil) should return nil") + } +} + +// Test Zero +func TestZero(t *testing.T) { + intType := reflect.TypeOf(0) + zeroVal := reflect.Zero(intType) + if !zeroVal.IsValid() { + t.Fatal("Zero returned invalid value") + } + if zeroVal.Int() != 0 { + t.Errorf("Zero int should be 0, got %d", zeroVal.Int()) + } +} + +// Test Swapper +func TestSwapper(t *testing.T) { + s := []int{1, 2, 3, 4, 5} + swap := reflect.Swapper(s) + + swap(0, 4) + if s[0] != 5 || s[4] != 1 { + t.Errorf("After swap, expected [5,2,3,4,1], got %v", s) + } +} + +// Test Copy +func TestCopy(t *testing.T) { + src := []int{1, 2, 3, 4, 5} + dst := make([]int, 3) + + srcVal := reflect.ValueOf(src) + dstVal := reflect.ValueOf(dst) + + n := reflect.Copy(dstVal, srcVal) + if n != 3 { + t.Errorf("Copy should return 3, got %d", n) + } + if dst[0] != 1 || dst[1] != 2 || dst[2] != 3 { + t.Errorf("After copy, expected [1,2,3], got %v", dst) + } +} + +// Test ChanDir constants and String +func TestChanDir(t *testing.T) { + _ = reflect.RecvDir + _ = reflect.SendDir + _ = reflect.BothDir + + dir := reflect.BothDir + if dir.String() == "" { + t.Fatal("ChanDir.String returned empty") + } +} + +// Test Ptr constant +func TestPtrConstant(t *testing.T) { + if reflect.Ptr != reflect.Pointer { + t.Error("Ptr should equal Pointer") + } +} + +// Test SelectCase and SelectDir +func TestSelectCaseDir(t *testing.T) { + _ = reflect.SelectSend + _ = reflect.SelectRecv + _ = reflect.SelectDefault + + ch := make(chan int) + sc := reflect.SelectCase{ + Dir: reflect.SelectRecv, + Chan: reflect.ValueOf(ch), + } + _ = sc +} + +// Test SelectDir type and constants +func TestSelectDir(t *testing.T) { + var sd reflect.SelectDir + sd = reflect.SelectSend + if sd != reflect.SelectSend { + t.Error("SelectDir assignment failed") + } +} + +// Test SliceHeader and StringHeader +func TestSliceAndStringHeader(t *testing.T) { + var sh reflect.SliceHeader + sh.Data = 0 + sh.Len = 0 + sh.Cap = 0 + + var strh reflect.StringHeader + strh.Data = 0 + strh.Len = 0 +} + +// Test Foo (if exists - may be internal/test-only) +func TestFoo(t *testing.T) { + // Foo may not exist in all Go versions + // Just ensure this test compiles + defer func() { + recover() // In case Foo doesn't exist + }() + // Intentionally empty - Foo doesn't exist in standard reflect +} diff --git a/test/std/reflect/struct_test.go b/test/std/reflect/struct_test.go new file mode 100644 index 0000000000..b50367d4bd --- /dev/null +++ b/test/std/reflect/struct_test.go @@ -0,0 +1,158 @@ +package reflect_test + +import ( + "reflect" + "testing" +) + +// Test struct field operations +func TestStructFieldOperations(t *testing.T) { + type Person struct { + Name string + Age int + } + + p := Person{Name: "Alice", Age: 30} + v := reflect.ValueOf(p) + + // Test NumField + if v.NumField() != 2 { + t.Errorf("NumField should be 2, got %d", v.NumField()) + } + + // Test Field + nameField := v.Field(0) + if nameField.String() != "Alice" { + t.Errorf("Field(0) should be 'Alice', got %q", nameField.String()) + } + + // Test FieldByName + ageField := v.FieldByName("Age") + if !ageField.IsValid() { + t.Fatal("FieldByName('Age') should be valid") + } + if ageField.Int() != 30 { + t.Errorf("FieldByName('Age') should be 30, got %d", ageField.Int()) + } +} + +// Test Type methods +func TestTypeMethods(t *testing.T) { + type Person struct { + Name string + Age int + } + + typ := reflect.TypeOf(Person{}) + + // Test NumField + if typ.NumField() != 2 { + t.Errorf("Type.NumField should be 2, got %d", typ.NumField()) + } + + // Test Field + field := typ.Field(0) + if field.Name != "Name" { + t.Errorf("Field(0).Name should be 'Name', got %q", field.Name) + } + + // Test FieldByName + field, ok := typ.FieldByName("Age") + if !ok { + t.Fatal("FieldByName('Age') should be found") + } + if field.Name != "Age" { + t.Errorf("Field.Name should be 'Age', got %q", field.Name) + } +} + +// Test StructField and StructTag +func TestStructFieldAndTag(t *testing.T) { + type Tagged struct { + Field1 string `json:"field1" xml:"f1"` + Field2 int `json:"field2,omitempty"` + } + + typ := reflect.TypeOf(Tagged{}) + field := typ.Field(0) + + // Test StructField + if field.Name != "Field1" { + t.Errorf("StructField.Name should be 'Field1', got %q", field.Name) + } + + // Test StructTag.Get + tag := field.Tag.Get("json") + if tag != "field1" { + t.Errorf("Tag.Get('json') should be 'field1', got %q", tag) + } + + // Test StructTag.Lookup + val, ok := field.Tag.Lookup("xml") + if !ok { + t.Fatal("Tag.Lookup('xml') should find tag") + } + if val != "f1" { + t.Errorf("Tag.Lookup('xml') should be 'f1', got %q", val) + } +} + +// Test StructField.IsExported +func TestStructFieldIsExported(t *testing.T) { + type T struct { + Exported int + unexported int + } + + typ := reflect.TypeOf(T{}) + + field0 := typ.Field(0) + if !field0.IsExported() { + t.Error("Exported field should be exported") + } + + field1 := typ.Field(1) + if field1.IsExported() { + t.Error("unexported field should not be exported") + } +} + +// Test Value.FieldByIndex +func TestValueFieldByIndex(t *testing.T) { + type Inner struct { + Value int + } + type Outer struct { + Inner + } + + o := Outer{Inner{42}} + v := reflect.ValueOf(o) + + field := v.FieldByIndex([]int{0, 0}) + if field.Int() != 42 { + t.Errorf("FieldByIndex should get 42, got %d", field.Int()) + } +} + +// Test FieldByNameFunc +func TestValueFieldByNameFunc(t *testing.T) { + type Person struct { + FirstName string + LastName string + } + + p := Person{FirstName: "John", LastName: "Doe"} + v := reflect.ValueOf(p) + + field := v.FieldByNameFunc(func(name string) bool { + return name == "LastName" + }) + + if !field.IsValid() { + t.Fatal("FieldByNameFunc should find LastName") + } + if field.String() != "Doe" { + t.Errorf("FieldByNameFunc should get 'Doe', got %q", field.String()) + } +} diff --git a/test/std/reflect/type_test.go b/test/std/reflect/type_test.go new file mode 100644 index 0000000000..a8f12c3bfa --- /dev/null +++ b/test/std/reflect/type_test.go @@ -0,0 +1,67 @@ +package reflect_test + +import ( + "math/big" + "reflect" + "testing" +) + +// Test Type.Name and Type.String +func TestTypeName(t *testing.T) { + type MyInt int + typ := reflect.TypeOf(MyInt(0)) + + name := typ.Name() + if name != "MyInt" { + t.Errorf("Type.Name should be 'MyInt', got %q", name) + } + + str := typ.String() + if str == "" { + t.Error("Type.String should not be empty") + } +} + +// Test Type.Kind +func TestTypeKind(t *testing.T) { + tests := []struct { + value any + kind reflect.Kind + }{ + {42, reflect.Int}, + {"hello", reflect.String}, + {true, reflect.Bool}, + {3.14, reflect.Float64}, + {[]int{}, reflect.Slice}, + {[3]int{}, reflect.Array}, + {map[string]int{}, reflect.Map}, + {struct{}{}, reflect.Struct}, + } + + for _, tt := range tests { + typ := reflect.TypeOf(tt.value) + if typ.Kind() != tt.kind { + t.Errorf("TypeOf(%v).Kind() = %v, want %v", tt.value, typ.Kind(), tt.kind) + } + } +} + +func TestTypeForMatchesStructFieldType(t *testing.T) { + type holder struct { + Value *big.Int + } + + direct := reflect.TypeFor[*big.Int]() + repeated := reflect.TypeFor[*big.Int]() + field := reflect.TypeFor[holder]().Field(0).Type + if direct != repeated { + t.Fatalf("repeated TypeFor returned distinct types: %v and %v", direct, repeated) + } + if direct != field { + t.Fatalf("TypeFor type %v differs from struct field type %v", direct, field) + } + value := reflect.ValueOf(&holder{}).Elem().Field(0).Addr().Interface() + if _, ok := value.(**big.Int); !ok { + t.Fatalf("reflected field address has type %T, want **big.Int", value) + } +} diff --git a/test/std/reflect/value_test.go b/test/std/reflect/value_test.go new file mode 100644 index 0000000000..0d660213cf --- /dev/null +++ b/test/std/reflect/value_test.go @@ -0,0 +1,516 @@ +package reflect_test + +import ( + "reflect" + "testing" + "unsafe" +) + +// Test Value.Type +func TestValueType(t *testing.T) { + v := reflect.ValueOf(42) + typ := v.Type() + if typ == nil { + t.Fatal("Value.Type returned nil") + } + if typ.Kind() != reflect.Int { + t.Errorf("Type.Kind should be Int, got %v", typ.Kind()) + } +} + +// Test basic value operations +func TestValueOperations(t *testing.T) { + // Test with int + v := reflect.ValueOf(42) + if v.Int() != 42 { + t.Errorf("Int should be 42, got %d", v.Int()) + } + + // Test with string + v = reflect.ValueOf("hello") + if v.String() != "hello" { + t.Errorf("String should be 'hello', got %q", v.String()) + } + + // Test with bool + v = reflect.ValueOf(true) + if !v.Bool() { + t.Error("Bool should be true") + } + + // Test with float64 + v = reflect.ValueOf(3.14) + if v.Float() != 3.14 { + t.Errorf("Float should be 3.14, got %f", v.Float()) + } +} + +// Test Value.Set operations +func TestValueSet(t *testing.T) { + // Test SetInt + var i int + v := reflect.ValueOf(&i).Elem() + v.SetInt(42) + if i != 42 { + t.Errorf("SetInt: expected 42, got %d", i) + } + + // Test SetBool + var b bool + v = reflect.ValueOf(&b).Elem() + v.SetBool(true) + if !b { + t.Error("SetBool: expected true") + } + + // Test SetFloat + var f float64 + v = reflect.ValueOf(&f).Elem() + v.SetFloat(3.14) + if f != 3.14 { + t.Errorf("SetFloat: expected 3.14, got %f", f) + } + + // Test SetString + var s string + v = reflect.ValueOf(&s).Elem() + v.SetString("hello") + if s != "hello" { + t.Errorf("SetString: expected 'hello', got %q", s) + } + + // Test SetUint + var u uint + v = reflect.ValueOf(&u).Elem() + v.SetUint(100) + if u != 100 { + t.Errorf("SetUint: expected 100, got %d", u) + } + + // Test SetComplex + var c complex128 + v = reflect.ValueOf(&c).Elem() + v.SetComplex(1 + 2i) + if c != 1+2i { + t.Errorf("SetComplex: expected (1+2i), got %v", c) + } +} + +// Test Value.CanSet and CanAddr +func TestValueCanSetCanAddr(t *testing.T) { + // Direct value cannot be set + v := reflect.ValueOf(42) + if v.CanSet() { + t.Error("Direct value should not be settable") + } + + // Elem of pointer can be set + i := 42 + v = reflect.ValueOf(&i).Elem() + if !v.CanSet() { + t.Error("Elem of pointer should be settable") + } + + // Test CanAddr + if !v.CanAddr() { + t.Error("Elem of pointer should be addressable") + } +} + +// Test Value.IsValid and IsNil +func TestValueIsValidIsNil(t *testing.T) { + // Test IsValid + v := reflect.ValueOf(42) + if !v.IsValid() { + t.Error("ValueOf(42) should be valid") + } + + var zeroVal reflect.Value + if zeroVal.IsValid() { + t.Error("Zero Value should not be valid") + } + + // Test IsNil + var ptr *int + v = reflect.ValueOf(ptr) + if !v.IsNil() { + t.Error("nil pointer Value should be nil") + } + + i := 42 + v = reflect.ValueOf(&i) + if v.IsNil() { + t.Error("non-nil pointer Value should not be nil") + } +} + +// Test pointer operations +func TestPointerOperations(t *testing.T) { + i := 42 + ptr := &i + + // Test Indirect + v := reflect.ValueOf(ptr) + indirect := reflect.Indirect(v) + if indirect.Int() != 42 { + t.Errorf("Indirect should give 42, got %d", indirect.Int()) + } + + // Test Elem + elem := v.Elem() + if elem.Int() != 42 { + t.Errorf("Elem should give 42, got %d", elem.Int()) + } +} + +// Test Value.Interface +func TestValueInterface(t *testing.T) { + i := 42 + v := reflect.ValueOf(i) + + iface := v.Interface() + if iface.(int) != 42 { + t.Errorf("Interface should be 42, got %v", iface) + } +} + +// Test Value.Addr and Pointer +func TestValueAddrPointer(t *testing.T) { + i := 42 + v := reflect.ValueOf(&i).Elem() + + // Test Addr + if !v.CanAddr() { + t.Fatal("Value should be addressable") + } + addr := v.Addr() + if addr.Kind() != reflect.Pointer { + t.Errorf("Addr should return Pointer, got %v", addr.Kind()) + } + + // Test Pointer + ptr := addr.Pointer() + if ptr == 0 { + t.Error("Pointer should not be zero") + } + + // Test UnsafePointer + unsafePtr := addr.UnsafePointer() + if unsafePtr == nil { + t.Error("UnsafePointer should not be nil") + } +} + +// Test Value.Bytes +func TestValueBytes(t *testing.T) { + b := []byte{1, 2, 3} + v := reflect.ValueOf(b) + + bytes := v.Bytes() + if len(bytes) != 3 { + t.Errorf("Bytes length should be 3, got %d", len(bytes)) + } + if bytes[0] != 1 { + t.Errorf("Bytes[0] should be 1, got %d", bytes[0]) + } +} + +// Test Value.SetBytes +func TestValueSetBytes(t *testing.T) { + var b []byte + v := reflect.ValueOf(&b).Elem() + + v.SetBytes([]byte{1, 2, 3}) + if len(b) != 3 { + t.Errorf("SetBytes: length should be 3, got %d", len(b)) + } + if b[0] != 1 { + t.Errorf("SetBytes: b[0] should be 1, got %d", b[0]) + } +} + +// Test Value.Complex +func TestValueComplex(t *testing.T) { + c := complex(1, 2) + v := reflect.ValueOf(c) + + result := v.Complex() + if result != 1+2i { + t.Errorf("Complex should be (1+2i), got %v", result) + } +} + +// Test Value.Uint +func TestValueUint(t *testing.T) { + var u uint = 42 + v := reflect.ValueOf(u) + + if v.Uint() != 42 { + t.Errorf("Uint should be 42, got %d", v.Uint()) + } +} + +// Test Value Can* methods +func TestValueCanMethods(t *testing.T) { + // CanInt + v := reflect.ValueOf(42) + if !v.CanInt() { + t.Error("int Value should satisfy CanInt") + } + + // CanUint + v = reflect.ValueOf(uint(42)) + if !v.CanUint() { + t.Error("uint Value should satisfy CanUint") + } + + // CanFloat + v = reflect.ValueOf(3.14) + if !v.CanFloat() { + t.Error("float Value should satisfy CanFloat") + } + + // CanComplex + v = reflect.ValueOf(complex(1, 2)) + if !v.CanComplex() { + t.Error("complex Value should satisfy CanComplex") + } + + // CanInterface + v = reflect.ValueOf(42) + if !v.CanInterface() { + t.Error("normal Value should satisfy CanInterface") + } +} + +// Test Value.Set +func TestValueSetGeneric(t *testing.T) { + var i int = 10 + v := reflect.ValueOf(&i).Elem() + + newVal := reflect.ValueOf(42) + v.Set(newVal) + + if i != 42 { + t.Errorf("Set: expected 42, got %d", i) + } +} + +// Test Value.SetZero +func TestValueSetZero(t *testing.T) { + i := 42 + v := reflect.ValueOf(&i).Elem() + + v.SetZero() + if i != 0 { + t.Errorf("SetZero: expected 0, got %d", i) + } +} + +// Test Value overflow methods +func TestValueOverflow(t *testing.T) { + var i int8 + v := reflect.ValueOf(&i).Elem() + + // Test OverflowInt + if !v.OverflowInt(128) { + t.Error("128 should overflow int8") + } + if v.OverflowInt(127) { + t.Error("127 should not overflow int8") + } + + // Test OverflowUint + var u uint8 + v = reflect.ValueOf(&u).Elem() + if !v.OverflowUint(256) { + t.Error("256 should overflow uint8") + } + + // Test OverflowFloat + var f float32 + v = reflect.ValueOf(&f).Elem() + if !v.OverflowFloat(1e39) { + t.Error("1e39 should overflow float32") + } +} + +// Test ValueError +func TestValueError(t *testing.T) { + var ve *reflect.ValueError + _ = ve + + // Trigger a ValueError + defer func() { + if r := recover(); r != nil { + if ve, ok := r.(*reflect.ValueError); ok { + if ve.Error() == "" { + t.Fatal("ValueError.Error returned empty") + } + } + } + }() + + v := reflect.ValueOf("string") + v.Int() // Should panic with ValueError +} + +// Test Value.Convert +func TestValueConvert(t *testing.T) { + var i int = 42 + v := reflect.ValueOf(i) + + // Convert int to int64 + int64Type := reflect.TypeOf(int64(0)) + converted := v.Convert(int64Type) + if converted.Int() != 42 { + t.Errorf("Convert to int64 should be 42, got %d", converted.Int()) + } +} + +// Test Value.CanConvert +func TestValueCanConvert(t *testing.T) { + var i int = 42 + v := reflect.ValueOf(i) + + int64Type := reflect.TypeOf(int64(0)) + if !v.CanConvert(int64Type) { + t.Error("int should be convertible to int64") + } + + // Note: In Go, int can be converted to string (e.g., string(65) = "A") + // so we skip this check +} + +// Test Value.IsZero +func TestValueIsZero(t *testing.T) { + // Test zero int + v := reflect.ValueOf(0) + if !v.IsZero() { + t.Error("Zero int should be zero") + } + + // Test non-zero int + v = reflect.ValueOf(42) + if v.IsZero() { + t.Error("Non-zero int should not be zero") + } + + // Test empty string + v = reflect.ValueOf("") + if !v.IsZero() { + t.Error("Empty string should be zero") + } +} + +// Test Value.Comparable and Equal +func TestValueComparableEqual(t *testing.T) { + v1 := reflect.ValueOf(42) + v2 := reflect.ValueOf(42) + v3 := reflect.ValueOf(43) + + // Test Comparable + if !v1.Comparable() { + t.Error("int Value should be comparable") + } + + // Test Equal + if !v1.Equal(v2) { + t.Error("Equal values should be equal") + } + if v1.Equal(v3) { + t.Error("Different values should not be equal") + } +} + +// Test Value.OverflowComplex +func TestValueOverflowComplex(t *testing.T) { + var c complex64 + v := reflect.ValueOf(&c).Elem() + + // Test with large complex number + large := complex(1e39, 1e39) + if !v.OverflowComplex(large) { + t.Error("Large complex should overflow complex64") + } +} + +// Test Value.UnsafeAddr +func TestValueUnsafeAddr(t *testing.T) { + i := 42 + v := reflect.ValueOf(&i).Elem() + + if !v.CanAddr() { + t.Fatal("Value should be addressable") + } + + addr := v.UnsafeAddr() + if addr == 0 { + t.Error("UnsafeAddr should not be zero") + } +} + +// Test Value.InterfaceData (deprecated but still exists) +func TestValueInterfaceData(t *testing.T) { + var i interface{} = 42 + v := reflect.ValueOf(&i).Elem() + + // InterfaceData is deprecated but we test it exists + data := v.InterfaceData() + if data[0] == 0 && data[1] == 0 { + t.Fatal("InterfaceData returned zero words") + } +} + +// Test Value.SetPointer +func TestValueSetPointer(t *testing.T) { + var p unsafe.Pointer + v := reflect.ValueOf(&p).Elem() + + i := 42 + v.SetPointer(unsafe.Pointer(&i)) + + if p == nil { + t.Error("SetPointer should set non-nil pointer") + } +} + +// Test Value.FieldByIndexErr +func TestValueFieldByIndexErr(t *testing.T) { + type Inner struct { + Value int + } + type Outer struct { + Inner + } + + o := Outer{Inner{42}} + v := reflect.ValueOf(o) + + // Valid index path + field, err := v.FieldByIndexErr([]int{0, 0}) + if err != nil { + t.Fatalf("FieldByIndexErr should not error on valid path: %v", err) + } + if field.Int() != 42 { + t.Errorf("FieldByIndexErr should get 42, got %d", field.Int()) + } + + // Note: Testing error case would cause panic in current Go version + // FieldByIndexErr exists but still panics on invalid index +} + +// Test Value.Seq and Seq2 (Go 1.24+ iterators) +func TestValueSeq(t *testing.T) { + // Seq returns an iterator for array/slice + s := []int{1, 2, 3} + v := reflect.ValueOf(s) + + // Just check that Seq method exists + _ = v.Seq + + // Seq2 for maps + m := map[string]int{"a": 1} + v2 := reflect.ValueOf(m) + _ = v2.Seq2 +} diff --git a/test/std/regexp/regexp_test.go b/test/std/regexp/regexp_test.go new file mode 100644 index 0000000000..be174b7623 --- /dev/null +++ b/test/std/regexp/regexp_test.go @@ -0,0 +1,468 @@ +package regexp_test + +import ( + "bytes" + "regexp" + "strings" + "testing" +) + +func TestCompile(t *testing.T) { + re, err := regexp.Compile(`\d+`) + if err != nil { + t.Fatalf("Compile failed: %v", err) + } + if re == nil { + t.Fatal("Compile returned nil") + } +} + +func TestCompilePOSIX(t *testing.T) { + re, err := regexp.CompilePOSIX(`[[:digit:]]+`) + if err != nil { + t.Fatalf("CompilePOSIX failed: %v", err) + } + if re == nil { + t.Fatal("CompilePOSIX returned nil") + } +} + +func TestMustCompile(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Error("MustCompile panicked on valid pattern") + } + }() + + re := regexp.MustCompile(`\w+`) + if re == nil { + t.Fatal("MustCompile returned nil") + } +} + +func TestMustCompilePOSIX(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Error("MustCompilePOSIX panicked on valid pattern") + } + }() + + re := regexp.MustCompilePOSIX(`[[:alpha:]]+`) + if re == nil { + t.Fatal("MustCompilePOSIX returned nil") + } +} + +func TestMatch(t *testing.T) { + matched, err := regexp.Match(`\d+`, []byte("abc123def")) + if err != nil { + t.Fatalf("Match failed: %v", err) + } + if !matched { + t.Error("Match should have matched") + } +} + +func TestMatchString(t *testing.T) { + matched, err := regexp.MatchString(`hello`, "hello world") + if err != nil { + t.Fatalf("MatchString failed: %v", err) + } + if !matched { + t.Error("MatchString should have matched") + } +} + +func TestMatchReader(t *testing.T) { + r := strings.NewReader("test123") + matched, err := regexp.MatchReader(`\d+`, r) + if err != nil { + t.Fatalf("MatchReader failed: %v", err) + } + if !matched { + t.Error("MatchReader should have matched") + } +} + +func TestQuoteMeta(t *testing.T) { + testCases := []struct { + input string + expected string + }{ + {"hello", "hello"}, + {"hello.world", `hello\.world`}, + {"a*b+c?", `a\*b\+c\?`}, + {"[a-z]", `\[a-z\]`}, + } + + for _, tc := range testCases { + result := regexp.QuoteMeta(tc.input) + if result != tc.expected { + t.Errorf("QuoteMeta(%q) = %q, want %q", tc.input, result, tc.expected) + } + } +} + +func TestRegexpString(t *testing.T) { + pattern := `\d+` + re := regexp.MustCompile(pattern) + if re.String() != pattern { + t.Errorf("String() = %q, want %q", re.String(), pattern) + } +} + +func TestRegexpMatchString(t *testing.T) { + re := regexp.MustCompile(`\d+`) + if !re.MatchString("abc123") { + t.Error("MatchString should match") + } + if re.MatchString("abc") { + t.Error("MatchString should not match") + } +} + +func TestRegexpMatch(t *testing.T) { + re := regexp.MustCompile(`\d+`) + if !re.Match([]byte("abc123")) { + t.Error("Match should match") + } + if re.Match([]byte("abc")) { + t.Error("Match should not match") + } +} + +func TestRegexpMatchReader(t *testing.T) { + re := regexp.MustCompile(`\d+`) + r := strings.NewReader("abc123") + matched := re.MatchReader(r) + if !matched { + t.Error("MatchReader should match") + } +} + +func TestFindString(t *testing.T) { + re := regexp.MustCompile(`\d+`) + result := re.FindString("abc123def456") + if result != "123" { + t.Errorf("FindString = %q, want %q", result, "123") + } +} + +func TestFindStringIndex(t *testing.T) { + re := regexp.MustCompile(`\d+`) + loc := re.FindStringIndex("abc123def") + if len(loc) != 2 || loc[0] != 3 || loc[1] != 6 { + t.Errorf("FindStringIndex = %v, want [3 6]", loc) + } +} + +func TestFindStringSubmatch(t *testing.T) { + re := regexp.MustCompile(`(\d+)`) + matches := re.FindStringSubmatch("abc123def") + if len(matches) != 2 || matches[0] != "123" || matches[1] != "123" { + t.Errorf("FindStringSubmatch = %v, want [123 123]", matches) + } +} + +func TestFindStringSubmatchIndex(t *testing.T) { + re := regexp.MustCompile(`(\d+)`) + locs := re.FindStringSubmatchIndex("abc123def") + if len(locs) != 4 { + t.Errorf("FindStringSubmatchIndex returned %d values, want 4", len(locs)) + } +} + +func TestFindAllString(t *testing.T) { + re := regexp.MustCompile(`\d+`) + matches := re.FindAllString("a1b2c3", -1) + if len(matches) != 3 { + t.Errorf("FindAllString found %d matches, want 3", len(matches)) + } +} + +func TestFindAllStringIndex(t *testing.T) { + re := regexp.MustCompile(`\d+`) + locs := re.FindAllStringIndex("a1b2c3", -1) + if len(locs) != 3 { + t.Errorf("FindAllStringIndex found %d matches, want 3", len(locs)) + } +} + +func TestFindAllStringSubmatch(t *testing.T) { + re := regexp.MustCompile(`(\d+)`) + matches := re.FindAllStringSubmatch("a1b2c3", -1) + if len(matches) != 3 { + t.Errorf("FindAllStringSubmatch found %d matches, want 3", len(matches)) + } +} + +func TestFindAllStringSubmatchIndex(t *testing.T) { + re := regexp.MustCompile(`(\d+)`) + locs := re.FindAllStringSubmatchIndex("a1b2c3", -1) + if len(locs) != 3 { + t.Errorf("FindAllStringSubmatchIndex found %d matches, want 3", len(locs)) + } +} + +func TestFind(t *testing.T) { + re := regexp.MustCompile(`\d+`) + result := re.Find([]byte("abc123def456")) + if !bytes.Equal(result, []byte("123")) { + t.Errorf("Find = %q, want %q", result, "123") + } +} + +func TestFindIndex(t *testing.T) { + re := regexp.MustCompile(`\d+`) + loc := re.FindIndex([]byte("abc123def")) + if len(loc) != 2 || loc[0] != 3 || loc[1] != 6 { + t.Errorf("FindIndex = %v, want [3 6]", loc) + } +} + +func TestFindSubmatch(t *testing.T) { + re := regexp.MustCompile(`(\d+)`) + matches := re.FindSubmatch([]byte("abc123def")) + if len(matches) != 2 { + t.Errorf("FindSubmatch returned %d matches, want 2", len(matches)) + } +} + +func TestFindSubmatchIndex(t *testing.T) { + re := regexp.MustCompile(`(\d+)`) + locs := re.FindSubmatchIndex([]byte("abc123def")) + if len(locs) != 4 { + t.Errorf("FindSubmatchIndex returned %d values, want 4", len(locs)) + } +} + +func TestFindAll(t *testing.T) { + re := regexp.MustCompile(`\d+`) + matches := re.FindAll([]byte("a1b2c3"), -1) + if len(matches) != 3 { + t.Errorf("FindAll found %d matches, want 3", len(matches)) + } +} + +func TestFindAllIndex(t *testing.T) { + re := regexp.MustCompile(`\d+`) + locs := re.FindAllIndex([]byte("a1b2c3"), -1) + if len(locs) != 3 { + t.Errorf("FindAllIndex found %d matches, want 3", len(locs)) + } +} + +func TestFindAllSubmatch(t *testing.T) { + re := regexp.MustCompile(`(\d+)`) + matches := re.FindAllSubmatch([]byte("a1b2c3"), -1) + if len(matches) != 3 { + t.Errorf("FindAllSubmatch found %d matches, want 3", len(matches)) + } +} + +func TestFindAllSubmatchIndex(t *testing.T) { + re := regexp.MustCompile(`(\d+)`) + locs := re.FindAllSubmatchIndex([]byte("a1b2c3"), -1) + if len(locs) != 3 { + t.Errorf("FindAllSubmatchIndex found %d matches, want 3", len(locs)) + } +} + +func TestFindReaderIndex(t *testing.T) { + re := regexp.MustCompile(`\d+`) + r := strings.NewReader("abc123def") + loc := re.FindReaderIndex(r) + if len(loc) != 2 || loc[0] != 3 || loc[1] != 6 { + t.Errorf("FindReaderIndex = %v, want [3 6]", loc) + } +} + +func TestFindReaderSubmatchIndex(t *testing.T) { + re := regexp.MustCompile(`(\d+)`) + r := strings.NewReader("abc123def") + locs := re.FindReaderSubmatchIndex(r) + if len(locs) != 4 { + t.Errorf("FindReaderSubmatchIndex returned %d values, want 4", len(locs)) + } +} + +func TestReplaceAllString(t *testing.T) { + re := regexp.MustCompile(`\d+`) + result := re.ReplaceAllString("a1b2c3", "X") + if result != "aXbXcX" { + t.Errorf("ReplaceAllString = %q, want %q", result, "aXbXcX") + } +} + +func TestReplaceAllLiteralString(t *testing.T) { + re := regexp.MustCompile(`\d+`) + result := re.ReplaceAllLiteralString("a1b2c3", "$0") + if result != "a$0b$0c$0" { + t.Errorf("ReplaceAllLiteralString = %q, want %q", result, "a$0b$0c$0") + } +} + +func TestReplaceAllStringFunc(t *testing.T) { + re := regexp.MustCompile(`\d+`) + result := re.ReplaceAllStringFunc("a1b2c3", func(s string) string { + return "[" + s + "]" + }) + if result != "a[1]b[2]c[3]" { + t.Errorf("ReplaceAllStringFunc = %q, want %q", result, "a[1]b[2]c[3]") + } +} + +func TestReplaceAll(t *testing.T) { + re := regexp.MustCompile(`\d+`) + result := re.ReplaceAll([]byte("a1b2c3"), []byte("X")) + if !bytes.Equal(result, []byte("aXbXcX")) { + t.Errorf("ReplaceAll = %q, want %q", result, "aXbXcX") + } +} + +func TestReplaceAllLiteral(t *testing.T) { + re := regexp.MustCompile(`\d+`) + result := re.ReplaceAllLiteral([]byte("a1b2c3"), []byte("$0")) + if !bytes.Equal(result, []byte("a$0b$0c$0")) { + t.Errorf("ReplaceAllLiteral = %q, want %q", result, "a$0b$0c$0") + } +} + +func TestReplaceAllFunc(t *testing.T) { + re := regexp.MustCompile(`\d+`) + result := re.ReplaceAllFunc([]byte("a1b2c3"), func(b []byte) []byte { + return []byte("[" + string(b) + "]") + }) + if !bytes.Equal(result, []byte("a[1]b[2]c[3]")) { + t.Errorf("ReplaceAllFunc = %q, want %q", result, "a[1]b[2]c[3]") + } +} + +func TestSplit(t *testing.T) { + re := regexp.MustCompile(`,\s*`) + parts := re.Split("a, b,c, d", -1) + if len(parts) != 4 { + t.Errorf("Split returned %d parts, want 4", len(parts)) + } +} + +func TestSubexpNames(t *testing.T) { + re := regexp.MustCompile(`(?P\d+)\.(?P\d+)`) + names := re.SubexpNames() + if len(names) != 3 { + t.Errorf("SubexpNames returned %d names, want 3", len(names)) + } + if names[1] != "first" || names[2] != "second" { + t.Errorf("SubexpNames = %v, want [\"\", \"first\", \"second\"]", names) + } +} + +func TestSubexpIndex(t *testing.T) { + re := regexp.MustCompile(`(?P\w+)`) + idx := re.SubexpIndex("name") + if idx != 1 { + t.Errorf("SubexpIndex(\"name\") = %d, want 1", idx) + } + + idx = re.SubexpIndex("unknown") + if idx != -1 { + t.Errorf("SubexpIndex(\"unknown\") = %d, want -1", idx) + } +} + +func TestNumSubexp(t *testing.T) { + re := regexp.MustCompile(`(\d+)\.(\d+)\.(\d+)`) + if n := re.NumSubexp(); n != 3 { + t.Errorf("NumSubexp() = %d, want 3", n) + } +} + +func TestLiteralPrefix(t *testing.T) { + re := regexp.MustCompile(`hello\d+`) + prefix, complete := re.LiteralPrefix() + if prefix != "hello" { + t.Errorf("LiteralPrefix prefix = %q, want %q", prefix, "hello") + } + if complete { + t.Error("LiteralPrefix complete should be false") + } +} + +func TestLongest(t *testing.T) { + re := regexp.MustCompile(`a+`) + re.Longest() + result := re.FindString("aaaa") + if result != "aaaa" { + t.Errorf("After Longest(), FindString = %q, want %q", result, "aaaa") + } +} + +func TestCopy(t *testing.T) { + re1 := regexp.MustCompile(`\d+`) + re2 := re1.Copy() + + if re2.String() != re1.String() { + t.Error("Copy() did not preserve pattern") + } + + if re2.MatchString("123") != re1.MatchString("123") { + t.Error("Copy() did not preserve behavior") + } +} + +func TestExpand(t *testing.T) { + re := regexp.MustCompile(`(\w+)@(\w+)\.(\w+)`) + match := re.FindStringSubmatchIndex("user@example.com") + + result := re.Expand(nil, []byte("$1 at $2 dot $3"), []byte("user@example.com"), match) + expected := "user at example dot com" + if string(result) != expected { + t.Errorf("Expand = %q, want %q", result, expected) + } +} + +func TestExpandString(t *testing.T) { + re := regexp.MustCompile(`(\w+)@(\w+)\.(\w+)`) + match := re.FindStringSubmatchIndex("user@example.com") + + result := re.ExpandString(nil, "$1 at $2 dot $3", "user@example.com", match) + expected := "user at example dot com" + if string(result) != expected { + t.Errorf("ExpandString = %q, want %q", result, expected) + } +} + +func TestAppendText(t *testing.T) { + re := regexp.MustCompile(`\d+`) + dst := []byte("prefix:") + result, err := re.AppendText(dst) + if err != nil { + t.Fatalf("AppendText failed: %v", err) + } + expected := "prefix:" + `\d+` + if string(result) != expected { + t.Errorf("AppendText = %q, want %q", result, expected) + } +} + +func TestMarshalText(t *testing.T) { + re := regexp.MustCompile(`\d+`) + text, err := re.MarshalText() + if err != nil { + t.Fatalf("MarshalText failed: %v", err) + } + if string(text) != `\d+` { + t.Errorf("MarshalText = %q, want %q", text, `\d+`) + } +} + +func TestUnmarshalText(t *testing.T) { + var re regexp.Regexp + err := re.UnmarshalText([]byte(`\d+`)) + if err != nil { + t.Fatalf("UnmarshalText failed: %v", err) + } + if !re.MatchString("123") { + t.Error("UnmarshalText did not create working regexp") + } +} diff --git a/test/std/regexp/syntax/syntax_test.go b/test/std/regexp/syntax/syntax_test.go new file mode 100644 index 0000000000..db877bb408 --- /dev/null +++ b/test/std/regexp/syntax/syntax_test.go @@ -0,0 +1,499 @@ +package syntax_test + +import ( + "regexp/syntax" + "testing" +) + +func TestParse(t *testing.T) { + tests := []struct { + pattern string + flags syntax.Flags + wantErr bool + }{ + {"a", syntax.Perl, false}, + {"a*", syntax.Perl, false}, + {"a+", syntax.Perl, false}, + {"a?", syntax.Perl, false}, + {"a|b", syntax.Perl, false}, + {"(a)", syntax.Perl, false}, + {"[abc]", syntax.Perl, false}, + {".", syntax.Perl, false}, + {"^a$", syntax.Perl, false}, + {"(", syntax.Perl, true}, + {")", syntax.Perl, true}, + {"[", syntax.Perl, true}, + } + + for _, tt := range tests { + re, err := syntax.Parse(tt.pattern, tt.flags) + if tt.wantErr { + if err == nil { + t.Errorf("Parse(%q) expected error", tt.pattern) + } + } else { + if err != nil { + t.Errorf("Parse(%q) unexpected error: %v", tt.pattern, err) + } + if re == nil { + t.Errorf("Parse(%q) returned nil regexp", tt.pattern) + } + } + } +} + +func TestCompile(t *testing.T) { + re, err := syntax.Parse("a*b+", syntax.Perl) + if err != nil { + t.Fatalf("Parse error: %v", err) + } + + prog, err := syntax.Compile(re) + if err != nil { + t.Fatalf("Compile error: %v", err) + } + + if prog == nil { + t.Fatal("Compile returned nil program") + } + + if len(prog.Inst) == 0 { + t.Error("Compiled program has no instructions") + } +} + +func TestRegexpString(t *testing.T) { + tests := []string{ + "a", + "a*", + "a+", + "a?", + "a|b", + "(a)", + "[abc]", + ".", + } + + for _, pattern := range tests { + re, err := syntax.Parse(pattern, syntax.Perl) + if err != nil { + t.Errorf("Parse(%q) error: %v", pattern, err) + continue + } + + str := re.String() + if str == "" { + t.Errorf("String() for %q returned empty string", pattern) + } + } +} + +func TestRegexpSimplify(t *testing.T) { + re, err := syntax.Parse("a*", syntax.Perl) + if err != nil { + t.Fatalf("Parse error: %v", err) + } + + simplified := re.Simplify() + if simplified == nil { + t.Fatal("Simplify returned nil") + } +} + +func TestRegexpMaxCap(t *testing.T) { + tests := []struct { + pattern string + maxCap int + }{ + {"a", 0}, + {"(a)", 1}, + {"(a)(b)", 2}, + {"(a)(b)(c)", 3}, + } + + for _, tt := range tests { + re, err := syntax.Parse(tt.pattern, syntax.Perl) + if err != nil { + t.Errorf("Parse(%q) error: %v", tt.pattern, err) + continue + } + + maxCap := re.MaxCap() + if maxCap != tt.maxCap { + t.Errorf("MaxCap() for %q = %d, want %d", tt.pattern, maxCap, tt.maxCap) + } + } +} + +func TestRegexpCapNames(t *testing.T) { + tests := []struct { + pattern string + names []string + }{ + {"(a)", []string{"", ""}}, + {"(?Pa)", []string{"", "name"}}, + {"(?Pa)(?Pb)", []string{"", "x", "y"}}, + } + + for _, tt := range tests { + re, err := syntax.Parse(tt.pattern, syntax.Perl) + if err != nil { + t.Errorf("Parse(%q) error: %v", tt.pattern, err) + continue + } + + names := re.CapNames() + if len(names) != len(tt.names) { + t.Errorf("CapNames() for %q length = %d, want %d", tt.pattern, len(names), len(tt.names)) + } + } +} + +func TestIsWordChar(t *testing.T) { + tests := []struct { + r rune + want bool + }{ + {'a', true}, + {'Z', true}, + {'0', true}, + {'_', true}, + {' ', false}, + {'.', false}, + {'\n', false}, + } + + for _, tt := range tests { + got := syntax.IsWordChar(tt.r) + if got != tt.want { + t.Errorf("IsWordChar(%q) = %v, want %v", tt.r, got, tt.want) + } + } +} + +func TestError(t *testing.T) { + err := &syntax.Error{ + Code: syntax.ErrInvalidCharClass, + Expr: "[z-a]", + } + + errStr := err.Error() + if errStr == "" { + t.Error("Error.Error() returned empty string") + } +} + +func TestErrorCode(t *testing.T) { + codes := []syntax.ErrorCode{ + syntax.ErrInternalError, + syntax.ErrInvalidCharClass, + syntax.ErrInvalidCharRange, + syntax.ErrInvalidEscape, + syntax.ErrInvalidNamedCapture, + syntax.ErrInvalidPerlOp, + syntax.ErrInvalidRepeatOp, + syntax.ErrInvalidRepeatSize, + syntax.ErrInvalidUTF8, + syntax.ErrMissingBracket, + syntax.ErrMissingParen, + syntax.ErrMissingRepeatArgument, + syntax.ErrTrailingBackslash, + syntax.ErrUnexpectedParen, + syntax.ErrNestingDepth, + syntax.ErrLarge, + } + + for _, code := range codes { + str := code.String() + if str == "" { + t.Errorf("ErrorCode.String() returned empty string for %v", code) + } + } +} + +func TestFlags(t *testing.T) { + flags := syntax.Perl | syntax.UnicodeGroups + + if flags&syntax.Perl == 0 { + t.Error("Perl flag not set") + } + if flags&syntax.UnicodeGroups == 0 { + t.Error("UnicodeGroups flag not set") + } +} + +func TestOp(t *testing.T) { + ops := []syntax.Op{ + syntax.OpNoMatch, + syntax.OpEmptyMatch, + syntax.OpLiteral, + syntax.OpCharClass, + syntax.OpAnyCharNotNL, + syntax.OpAnyChar, + syntax.OpBeginLine, + syntax.OpEndLine, + syntax.OpBeginText, + syntax.OpEndText, + syntax.OpWordBoundary, + syntax.OpNoWordBoundary, + syntax.OpCapture, + syntax.OpStar, + syntax.OpPlus, + syntax.OpQuest, + syntax.OpRepeat, + syntax.OpConcat, + syntax.OpAlternate, + } + + for _, op := range ops { + str := op.String() + if str == "" { + t.Errorf("Op.String() returned empty for %v", op) + } + } +} + +func TestEmptyOp(t *testing.T) { + ops := []syntax.EmptyOp{ + syntax.EmptyBeginLine, + syntax.EmptyEndLine, + syntax.EmptyBeginText, + syntax.EmptyEndText, + syntax.EmptyWordBoundary, + syntax.EmptyNoWordBoundary, + } + + for _, op := range ops { + if op == 0 { + t.Error("EmptyOp is zero") + } + } +} + +func TestInstOp(t *testing.T) { + ops := []syntax.InstOp{ + syntax.InstAlt, + syntax.InstAltMatch, + syntax.InstCapture, + syntax.InstEmptyWidth, + syntax.InstMatch, + syntax.InstFail, + syntax.InstNop, + syntax.InstRune, + syntax.InstRune1, + syntax.InstRuneAny, + syntax.InstRuneAnyNotNL, + } + + for _, op := range ops { + str := op.String() + if str == "" { + t.Errorf("InstOp.String() returned empty for %v", op) + } + } +} + +func TestProgString(t *testing.T) { + re, err := syntax.Parse("a*", syntax.Perl) + if err != nil { + t.Fatalf("Parse error: %v", err) + } + + prog, err := syntax.Compile(re) + if err != nil { + t.Fatalf("Compile error: %v", err) + } + + str := prog.String() + if str == "" { + t.Error("Prog.String() returned empty string") + } +} + +func TestProgStartCond(t *testing.T) { + re, err := syntax.Parse("^abc", syntax.Perl) + if err != nil { + t.Fatalf("Parse error: %v", err) + } + + prog, err := syntax.Compile(re) + if err != nil { + t.Fatalf("Compile error: %v", err) + } + + startCond := prog.StartCond() + if startCond == 0 { + t.Error("StartCond() returned 0 for ^abc") + } +} + +func TestProgPrefix(t *testing.T) { + tests := []struct { + pattern string + hasPrefix bool + }{ + {"abc", true}, + {"a*", false}, + {"abc|def", false}, + } + + for _, tt := range tests { + re, err := syntax.Parse(tt.pattern, syntax.Perl) + if err != nil { + t.Errorf("Parse(%q) error: %v", tt.pattern, err) + continue + } + + prog, err := syntax.Compile(re) + if err != nil { + t.Errorf("Compile(%q) error: %v", tt.pattern, err) + continue + } + + prefix, complete := prog.Prefix() + hasPrefix := prefix != "" || complete + if hasPrefix != tt.hasPrefix { + t.Errorf("Prefix() for %q hasPrefix = %v, want %v", tt.pattern, hasPrefix, tt.hasPrefix) + } + } +} + +func TestInst(t *testing.T) { + inst := syntax.Inst{ + Op: syntax.InstRune, + Rune: []rune{'a'}, + } + + if inst.Op != syntax.InstRune { + t.Errorf("Inst.Op = %v, want InstRune", inst.Op) + } + if len(inst.Rune) != 1 || inst.Rune[0] != 'a' { + t.Errorf("Inst.Rune = %v, want ['a']", inst.Rune) + } +} + +func TestRegexpEqual(t *testing.T) { + re1, _ := syntax.Parse("a*", syntax.Perl) + re2, _ := syntax.Parse("a*", syntax.Perl) + re3, _ := syntax.Parse("b*", syntax.Perl) + + if !re1.Equal(re2) { + t.Error("Equal() should return true for identical regexps") + } + if re1.Equal(re3) { + t.Error("Equal() should return false for different regexps") + } +} + +func TestInstMethods(t *testing.T) { + re, err := syntax.Parse("a+", syntax.Perl) + if err != nil { + t.Fatalf("Parse error: %v", err) + } + + prog, err := syntax.Compile(re) + if err != nil { + t.Fatalf("Compile error: %v", err) + } + + if len(prog.Inst) == 0 { + t.Fatal("No instructions in compiled program") + } + + for i, inst := range prog.Inst { + str := inst.String() + if str == "" { + t.Errorf("Inst[%d].String() returned empty string", i) + } + + matches := inst.MatchRune('a') + _ = matches + + pos := inst.MatchRunePos('a') + if pos < 0 && inst.Op == syntax.InstRune { + t.Errorf("Inst[%d].MatchRunePos('a') = %d, expected >= 0 for InstRune", i, pos) + } + + if inst.Op == syntax.InstEmptyWidth { + matchEmpty := inst.MatchEmptyWidth(0, 0) + _ = matchEmpty + } + } +} + +func TestInstMatchEmptyWidth(t *testing.T) { + inst := syntax.Inst{ + Op: syntax.InstEmptyWidth, + Arg: uint32(syntax.EmptyBeginLine), + } + + tests := []struct { + before rune + after rune + want bool + }{ + {'\n', 'a', true}, + {'a', 'b', false}, + } + + for _, tt := range tests { + got := inst.MatchEmptyWidth(tt.before, tt.after) + if got != tt.want { + t.Errorf("MatchEmptyWidth(%q, %q) = %v, want %v", tt.before, tt.after, got, tt.want) + } + } +} + +func TestInstMatchRune(t *testing.T) { + inst := syntax.Inst{ + Op: syntax.InstRune1, + Rune: []rune{'a'}, + } + + if !inst.MatchRune('a') { + t.Error("MatchRune('a') = false, want true") + } + if inst.MatchRune('b') { + t.Error("MatchRune('b') = true, want false") + } +} + +func TestInstMatchRunePos(t *testing.T) { + inst := syntax.Inst{ + Op: syntax.InstRune1, + Rune: []rune{'a'}, + } + + pos := inst.MatchRunePos('a') + if pos != 0 { + t.Errorf("MatchRunePos('a') = %d, want 0", pos) + } + + pos = inst.MatchRunePos('b') + if pos != -1 { + t.Errorf("MatchRunePos('b') = %d, want -1", pos) + } +} + +func TestInstString(t *testing.T) { + tests := []syntax.Inst{ + {Op: syntax.InstMatch}, + {Op: syntax.InstFail}, + {Op: syntax.InstNop}, + {Op: syntax.InstRune, Rune: []rune{'a'}}, + {Op: syntax.InstRune1, Rune: []rune{'a'}}, + {Op: syntax.InstRuneAny}, + {Op: syntax.InstRuneAnyNotNL}, + {Op: syntax.InstEmptyWidth, Arg: uint32(syntax.EmptyBeginLine)}, + {Op: syntax.InstCapture, Arg: 1}, + {Op: syntax.InstAlt, Out: 1, Arg: 2}, + {Op: syntax.InstAltMatch, Out: 1, Arg: 2}, + } + + for i, inst := range tests { + str := inst.String() + if str == "" { + t.Errorf("Inst[%d].String() returned empty string for Op=%v", i, inst.Op) + } + } +} diff --git a/test/std/runtime/cgo/cgo_test.go b/test/std/runtime/cgo/cgo_test.go new file mode 100644 index 0000000000..8b41c5259d --- /dev/null +++ b/test/std/runtime/cgo/cgo_test.go @@ -0,0 +1,35 @@ +package cgo_test + +import ( + "reflect" + "runtime/cgo" + "testing" +) + +func TestHandleLifecycle(t *testing.T) { + type payload struct{ name string } + want := &payload{name: "llgo"} + handle := cgo.NewHandle(want) + if got, ok := handle.Value().(*payload); !ok || got != want { + t.Fatalf("Value = %#v, want the original payload", got) + } + handle.Delete() + if panicValue := panicFrom(func() { handle.Value() }); panicValue == nil { + t.Fatal("Value on a deleted handle did not panic") + } +} + +func TestIncompleteTypeIdentity(t *testing.T) { + typ := reflect.TypeOf(cgo.Incomplete{}) + if typ.Name() != "Incomplete" || typ.PkgPath() != "runtime/cgo" { + t.Fatalf("unexpected incomplete C type marker: %v from %q", typ, typ.PkgPath()) + } +} + +func panicFrom(f func()) (value any) { + defer func() { + value = recover() + }() + f() + return nil +} diff --git a/test/std/runtime/coverage/coverage_test.go b/test/std/runtime/coverage/coverage_test.go new file mode 100644 index 0000000000..36986edb94 --- /dev/null +++ b/test/std/runtime/coverage/coverage_test.go @@ -0,0 +1,61 @@ +package coverage_test + +import ( + "bytes" + "os" + "runtime/coverage" + "strings" + "testing" +) + +func TestRuntimeCoverageWritersAgree(t *testing.T) { + var meta bytes.Buffer + metaErr := coverage.WriteMeta(&meta) + metaDir := t.TempDir() + metaDirErr := coverage.WriteMetaDir(metaDir) + checkWriteResults(t, "metadata", "covmeta.", meta.Len(), metaErr, metaDir, metaDirErr) + + var counters bytes.Buffer + counterErr := coverage.WriteCounters(&counters) + counterDir := t.TempDir() + counterDirErr := coverage.WriteCountersDir(counterDir) + checkWriteResults(t, "counters", "covcounters.", counters.Len(), counterErr, counterDir, counterDirErr) + + clearErr := coverage.ClearCounters() + if counterErr != nil && clearErr == nil { + t.Fatalf("ClearCounters succeeded although WriteCounters is unavailable: %v", counterErr) + } +} + +func checkWriteResults(t *testing.T, name, prefix string, directBytes int, directErr error, dir string, dirErr error) { + t.Helper() + if (directErr == nil) != (dirErr == nil) { + t.Fatalf("%s writer availability differs: direct=%v, directory=%v", name, directErr, dirErr) + } + entries, err := os.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + if directErr != nil { + if directBytes != 0 || len(entries) != 0 { + t.Fatalf("unavailable %s writers produced %d bytes and %d files", name, directBytes, len(entries)) + } + return + } + if directBytes == 0 { + t.Fatalf("Write%s succeeded without writing data", name) + } + for _, entry := range entries { + if strings.HasPrefix(entry.Name(), prefix) { + info, err := entry.Info() + if err != nil { + t.Fatal(err) + } + if info.Size() == 0 { + t.Fatalf("%s file %q is empty", name, entry.Name()) + } + return + } + } + t.Fatalf("Write%sDir produced no %q file: %v", name, prefix, entries) +} diff --git a/test/std/runtime/debug/debug_test.go b/test/std/runtime/debug/debug_test.go new file mode 100644 index 0000000000..3daa48d98f --- /dev/null +++ b/test/std/runtime/debug/debug_test.go @@ -0,0 +1,154 @@ +package debug_test + +import ( + "io" + "os" + "reflect" + "runtime" + "runtime/debug" + "strings" + "testing" +) + +func TestStackReportsCaller(t *testing.T) { + stack := string(debug.Stack()) + if !strings.Contains(stack, "TestStackReportsCaller") { + t.Fatalf("Stack does not contain the caller: %q", stack) + } +} + +func TestPrintStackReportsCaller(t *testing.T) { + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + oldStderr := os.Stderr + os.Stderr = w + debug.PrintStack() + os.Stderr = oldStderr + if err := w.Close(); err != nil { + t.Fatal(err) + } + stack, err := io.ReadAll(r) + if err != nil { + t.Fatal(err) + } + if err := r.Close(); err != nil { + t.Fatal(err) + } + if !strings.Contains(string(stack), "TestPrintStackReportsCaller") { + t.Fatalf("PrintStack does not contain the caller: %q", stack) + } +} + +func TestRuntimeSettings(t *testing.T) { + var stats debug.GCStats + runtime.GC() + debug.ReadGCStats(&stats) + if stats.NumGC < 0 || stats.PauseTotal < 0 { + t.Fatalf("invalid GC statistics: %#v", stats) + } + + previousGC := debug.SetGCPercent(100) + if got := debug.SetGCPercent(previousGC); got != 100 { + t.Fatalf("SetGCPercent restore returned %d, want 100", got) + } + previousLimit := debug.SetMemoryLimit(1 << 30) + if got := debug.SetMemoryLimit(previousLimit); got != 1<<30 { + t.Fatalf("SetMemoryLimit restore returned %d, want %d", got, int64(1<<30)) + } + previousStack := debug.SetMaxStack(1 << 30) + if got := debug.SetMaxStack(previousStack); got != 1<<30 { + t.Fatalf("SetMaxStack restore returned %d, want %d", got, 1<<30) + } + previousThreads := debug.SetMaxThreads(10001) + if got := debug.SetMaxThreads(previousThreads); got != 10001 { + t.Fatalf("SetMaxThreads restore returned %d, want 10001", got) + } + previousPanicOnFault := debug.SetPanicOnFault(true) + if got := debug.SetPanicOnFault(previousPanicOnFault); !got { + t.Fatal("SetPanicOnFault did not report the previous enabled state") + } + + debug.SetTraceback("single") + debug.FreeOSMemory() +} + +func TestPanicOnFaultStateIsGoroutineLocal(t *testing.T) { + previous := debug.SetPanicOnFault(true) + defer debug.SetPanicOnFault(previous) + + result := make(chan [2]bool, 1) + go func() { + first := debug.SetPanicOnFault(true) + second := debug.SetPanicOnFault(false) + result <- [2]bool{first, second} + }() + if got := <-result; got != [2]bool{false, true} { + t.Fatalf("new goroutine SetPanicOnFault states = %v, want [false true]", got) + } + if got := debug.SetPanicOnFault(previous); !got { + t.Fatal("child goroutine changed the parent SetPanicOnFault state") + } +} + +func TestCrashAndHeapDumpOutputs(t *testing.T) { + crashFile, err := os.CreateTemp(t.TempDir(), "crash-*.log") + if err != nil { + t.Fatal(err) + } + if err := debug.SetCrashOutput(crashFile, debug.CrashOptions{}); err != nil { + t.Fatal(err) + } + if err := debug.SetCrashOutput(nil, debug.CrashOptions{}); err != nil { + t.Fatal(err) + } + if err := crashFile.Close(); err != nil { + t.Fatal(err) + } + + heapFile, err := os.CreateTemp(t.TempDir(), "heap-*.dump") + if err != nil { + t.Fatal(err) + } + debug.WriteHeapDump(heapFile.Fd()) + if _, err := heapFile.WriteString("fd-remains-open"); err != nil { + t.Fatalf("heap dump closed its output descriptor: %v", err) + } + if err := heapFile.Close(); err != nil { + t.Fatal(err) + } +} + +func TestBuildInfoParsingAndReading(t *testing.T) { + const encoded = "go\t1.26\npath\texample.com/app\nmod\texample.com/app\tv1.2.3\th1:main\ndep\texample.com/dep\tv0.4.5\th1:dep\nbuild\t-compiler=gc\n" + info, err := debug.ParseBuildInfo(encoded) + if err != nil { + t.Fatal(err) + } + if info.Path != "example.com/app" || info.Main.Path != "example.com/app" || info.Main.Version != "v1.2.3" { + t.Fatalf("parsed main module = %#v, path %q", info.Main, info.Path) + } + if len(info.Deps) != 1 || info.Deps[0].Path != "example.com/dep" || info.Deps[0].Version != "v0.4.5" { + t.Fatalf("parsed dependencies = %#v", info.Deps) + } + if len(info.Settings) != 1 || info.Settings[0].Key != "-compiler" || info.Settings[0].Value != "gc" { + t.Fatalf("parsed settings = %#v", info.Settings) + } + roundTrip, err := debug.ParseBuildInfo(info.String()) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(roundTrip, info) { + t.Fatalf("build info changed after round trip:\nfirst: %#v\nsecond: %#v", info, roundTrip) + } + + if info, ok := debug.ReadBuildInfo(); ok { + if !strings.HasPrefix(info.GoVersion, "go") { + t.Fatalf("GoVersion = %q", info.GoVersion) + } + if info.String() == "" { + t.Fatal("BuildInfo.String returned an empty string") + } + } +} diff --git a/test/std/runtime/metrics/metrics_test.go b/test/std/runtime/metrics/metrics_test.go new file mode 100644 index 0000000000..71c40fe8c5 --- /dev/null +++ b/test/std/runtime/metrics/metrics_test.go @@ -0,0 +1,69 @@ +package metrics_test + +import ( + "runtime/metrics" + "testing" +) + +func TestReadAllMetricKinds(t *testing.T) { + descs := metrics.All() + if len(descs) == 0 { + t.Fatal("metrics.All returned no descriptions") + } + + samples := make([]metrics.Sample, len(descs)) + for i, desc := range descs { + samples[i].Name = desc.Name + } + metrics.Read(samples) + + seen := map[metrics.ValueKind]bool{} + for i, desc := range descs { + value := samples[i].Value + if got := value.Kind(); got != desc.Kind { + t.Fatalf("Read(%q) kind = %d, want %d", desc.Name, got, desc.Kind) + } + checkMetricValue(t, desc.Name, value, desc.Kind) + seen[desc.Kind] = true + } + + for _, kind := range []metrics.ValueKind{ + metrics.KindUint64, + metrics.KindFloat64, + metrics.KindFloat64Histogram, + } { + if !seen[kind] { + t.Fatalf("metrics.All did not include a metric of kind %d", kind) + } + } +} + +func TestReadUnknownMetric(t *testing.T) { + samples := []metrics.Sample{{Name: "/llgo/unknown:things"}} + metrics.Read(samples) + if got := samples[0].Value.Kind(); got != metrics.KindBad { + t.Fatalf("Read unknown metric kind = %d, want %d", got, metrics.KindBad) + } +} + +func checkMetricValue(t *testing.T, name string, value metrics.Value, kind metrics.ValueKind) { + t.Helper() + + switch kind { + case metrics.KindUint64: + _ = value.Uint64() + case metrics.KindFloat64: + _ = value.Float64() + case metrics.KindFloat64Histogram: + hist := value.Float64Histogram() + if hist == nil { + t.Fatalf("Read(%q) returned nil histogram", name) + } + if len(hist.Buckets) != len(hist.Counts)+1 { + t.Fatalf("Read(%q) histogram buckets/counts lengths = %d/%d, want buckets = counts+1", + name, len(hist.Buckets), len(hist.Counts)) + } + default: + t.Fatalf("Read(%q) returned unexpected kind %d", name, kind) + } +} diff --git a/test/std/runtime/pprof/pprof_test.go b/test/std/runtime/pprof/pprof_test.go new file mode 100644 index 0000000000..7757536f18 --- /dev/null +++ b/test/std/runtime/pprof/pprof_test.go @@ -0,0 +1,307 @@ +package pprof_test + +import ( + "bytes" + "compress/gzip" + "context" + "io" + "runtime/pprof" + "testing" + "time" +) + +//go:noinline +func cpuProfileHotLoop(d time.Duration) uint64 { + deadline := time.Now().Add(d) + x := uint64(1) + for time.Now().Before(deadline) { + for i := 0; i < 10000; i++ { + x = x*1664525 + 1013904223 + } + } + return x +} + +func requireCPUProfileContains(t *testing.T, data []byte, function string) { + t.Helper() + zr, err := gzip.NewReader(bytes.NewReader(data)) + if err != nil { + t.Fatalf("CPU profile is not valid gzip: %v", err) + } + raw, err := io.ReadAll(zr) + if err != nil { + t.Fatalf("read CPU profile: %v", err) + } + if err := zr.Close(); err != nil { + t.Fatalf("close CPU profile reader: %v", err) + } + if !bytes.Contains(raw, []byte(function)) { + t.Fatalf("CPU profile does not contain sampled function %q (compressed=%d bytes)", function, len(data)) + } +} + +func TestStartStopCPUProfile(t *testing.T) { + var buf bytes.Buffer + err := pprof.StartCPUProfile(&buf) + if err != nil { + t.Fatalf("StartCPUProfile failed: %v", err) + } + defer pprof.StopCPUProfile() + + _ = cpuProfileHotLoop(500 * time.Millisecond) + + pprof.StopCPUProfile() + requireCPUProfileContains(t, buf.Bytes(), "cpuProfileHotLoop") +} + +func TestStartCPUProfileTwice(t *testing.T) { + var buf bytes.Buffer + err := pprof.StartCPUProfile(&buf) + if err != nil { + t.Fatalf("StartCPUProfile failed: %v", err) + } + defer pprof.StopCPUProfile() + + err = pprof.StartCPUProfile(&buf) + if err == nil { + t.Error("StartCPUProfile should fail when already profiling") + } + + pprof.StopCPUProfile() + + var restarted bytes.Buffer + if err := pprof.StartCPUProfile(&restarted); err != nil { + t.Fatalf("StartCPUProfile after stop failed: %v", err) + } + _ = cpuProfileHotLoop(300 * time.Millisecond) + pprof.StopCPUProfile() + requireCPUProfileContains(t, restarted.Bytes(), "cpuProfileHotLoop") +} + +func TestCPUProfileGoroutine(t *testing.T) { + var buf bytes.Buffer + if err := pprof.StartCPUProfile(&buf); err != nil { + t.Fatalf("StartCPUProfile failed: %v", err) + } + defer pprof.StopCPUProfile() + + done := make(chan uint64, 1) + go func() { + done <- cpuProfileHotLoop(500 * time.Millisecond) + }() + <-done + pprof.StopCPUProfile() + requireCPUProfileContains(t, buf.Bytes(), "cpuProfileHotLoop") +} + +func TestWriteHeapProfile(t *testing.T) { + var buf bytes.Buffer + err := pprof.WriteHeapProfile(&buf) + if err != nil { + t.Fatalf("WriteHeapProfile failed: %v", err) + } + + if buf.Len() == 0 { + t.Error("Heap profile is empty") + } +} + +func TestLookup(t *testing.T) { + profiles := []string{ + "goroutine", + "heap", + "allocs", + "threadcreate", + "block", + "mutex", + } + + for _, name := range profiles { + p := pprof.Lookup(name) + if p == nil { + t.Errorf("Lookup(%q) returned nil", name) + } else if p.Name() != name { + t.Errorf("Profile.Name() = %q, want %q", p.Name(), name) + } + } + + nonExistent := pprof.Lookup("nonexistent") + if nonExistent != nil { + t.Error("Lookup for non-existent profile should return nil") + } +} + +func TestProfiles(t *testing.T) { + profiles := pprof.Profiles() + if len(profiles) == 0 { + t.Fatal("Profiles() returned empty slice") + } + + foundGoroutine := false + for _, p := range profiles { + if p.Name() == "goroutine" { + foundGoroutine = true + break + } + } + if !foundGoroutine { + t.Error("goroutine profile not found in Profiles()") + } +} + +func TestNewProfile(t *testing.T) { + name := "test-profile" + p := pprof.NewProfile(name) + if p == nil { + t.Fatal("NewProfile returned nil") + } + if p.Name() != name { + t.Errorf("Profile.Name() = %q, want %q", p.Name(), name) + } + + lookup := pprof.Lookup(name) + if lookup != p { + t.Error("Lookup did not return the newly created profile") + } +} + +func TestProfileCount(t *testing.T) { + p := pprof.Lookup("goroutine") + if p == nil { + t.Fatal("goroutine profile not found") + } + + count := p.Count() + if count <= 0 { + t.Errorf("Profile.Count() = %d, want > 0", count) + } +} + +func TestProfileWriteTo(t *testing.T) { + p := pprof.Lookup("goroutine") + if p == nil { + t.Fatal("goroutine profile not found") + } + + var buf bytes.Buffer + err := p.WriteTo(&buf, 0) + if err != nil { + t.Fatalf("Profile.WriteTo failed: %v", err) + } + + if buf.Len() == 0 { + t.Error("Profile.WriteTo produced empty output") + } +} + +func TestLabels(t *testing.T) { + labels := pprof.Labels("key1", "value1", "key2", "value2") + _ = labels +} + +func TestWithLabels(t *testing.T) { + labels := pprof.Labels("testkey", "testvalue") + ctx := pprof.WithLabels(context.Background(), labels) + + value, ok := pprof.Label(ctx, "testkey") + if !ok { + t.Error("Label not found in context") + } + if value != "testvalue" { + t.Errorf("Label value = %q, want testvalue", value) + } + + _, ok = pprof.Label(ctx, "nonexistent") + if ok { + t.Error("Label should not be found for non-existent key") + } +} + +func TestForLabels(t *testing.T) { + labels := pprof.Labels("key1", "value1", "key2", "value2") + ctx := pprof.WithLabels(context.Background(), labels) + + found := make(map[string]string) + pprof.ForLabels(ctx, func(key, value string) bool { + found[key] = value + return true + }) + + if len(found) != 2 { + t.Errorf("ForLabels found %d labels, want 2", len(found)) + } + if found["key1"] != "value1" { + t.Errorf("found[key1] = %q, want value1", found["key1"]) + } + if found["key2"] != "value2" { + t.Errorf("found[key2] = %q, want value2", found["key2"]) + } +} + +func TestSetGoroutineLabels(t *testing.T) { + labels := pprof.Labels("goroutine-key", "goroutine-value") + ctx := pprof.WithLabels(context.Background(), labels) + + pprof.SetGoroutineLabels(ctx) +} + +func TestDo(t *testing.T) { + labels := pprof.Labels("do-key", "do-value") + + executed := false + pprof.Do(context.Background(), labels, func(ctx context.Context) { + executed = true + + value, ok := pprof.Label(ctx, "do-key") + if !ok { + t.Error("Label not found in Do context") + } + if value != "do-value" { + t.Errorf("Label value = %q, want do-value", value) + } + }) + + if !executed { + t.Error("Do function was not executed") + } +} + +func TestProfileRemove(t *testing.T) { + name := "test-removable-profile" + p := pprof.NewProfile(name) + if p == nil { + t.Fatal("NewProfile returned nil") + } + + dummy := 0 + p.Add(&dummy, 0) + + count := p.Count() + if count != 1 { + t.Errorf("Profile.Count() = %d, want 1", count) + } + + p.Remove(&dummy) + + count = p.Count() + if count != 0 { + t.Errorf("After Remove, Profile.Count() = %d, want 0", count) + } +} + +func TestProfileName(t *testing.T) { + p := pprof.Lookup("goroutine") + if p == nil { + t.Fatal("goroutine profile not found") + } + + name := p.Name() + if name != "goroutine" { + t.Errorf("Profile.Name() = %q, want goroutine", name) + } +} + +func TestLabelSet(t *testing.T) { + var ls pprof.LabelSet + _ = ls +} diff --git a/test/std/runtime/race/race_test.go b/test/std/runtime/race/race_test.go new file mode 100644 index 0000000000..6c318875ed --- /dev/null +++ b/test/std/runtime/race/race_test.go @@ -0,0 +1,10 @@ +package race_test + +import ( + _ "runtime/race" + "testing" +) + +func TestPackageImports(t *testing.T) { + t.Log("runtime/race intentionally has no public API") +} diff --git a/test/std/runtime/runtime_operations_test.go b/test/std/runtime/runtime_operations_test.go new file mode 100644 index 0000000000..641c5a2f64 --- /dev/null +++ b/test/std/runtime/runtime_operations_test.go @@ -0,0 +1,60 @@ +package runtime_test + +import ( + "runtime" + "testing" +) + +func TestGoexitRunsDeferredCalls(t *testing.T) { + done := make(chan struct{}) + go func() { + defer close(done) + runtime.Goexit() + }() + <-done +} + +func TestRuntimeFunctionInformation(t *testing.T) { + pc, _, _, ok := runtime.Caller(0) + if !ok { + t.Fatal("Caller failed") + } + fn := runtime.FuncForPC(pc) + if fn == nil || fn.Entry() == 0 || fn.Name() == "" { + t.Fatal("FuncForPC returned incomplete information") + } + if file, line := fn.FileLine(pc); file == "" || line == 0 { + t.Fatal("FileLine returned incomplete information") + } +} + +func TestRuntimeRecordMethods(t *testing.T) { + mem := runtime.MemProfileRecord{ + AllocBytes: 1024, + FreeBytes: 256, + AllocObjects: 8, + FreeObjects: 3, + Stack0: [32]uintptr{11, 22}, + } + if got := mem.InUseBytes(); got != 768 { + t.Fatalf("InUseBytes = %d, want 768", got) + } + if got := mem.InUseObjects(); got != 5 { + t.Fatalf("InUseObjects = %d, want 5", got) + } + if stack := mem.Stack(); len(stack) != 2 || stack[0] != 11 || stack[1] != 22 { + t.Fatalf("MemProfileRecord.Stack = %v", stack) + } + + record := runtime.StackRecord{Stack0: [32]uintptr{33, 44}} + if stack := record.Stack(); len(stack) != 2 || stack[0] != 33 || stack[1] != 44 { + t.Fatalf("StackRecord.Stack = %v", stack) + } +} + +func TestRuntimeErrorMethods(t *testing.T) { + panicNil := new(runtime.PanicNilError) + if got := panicNil.Error(); got != "panic called with nil argument" { + t.Fatalf("PanicNilError.Error = %q", got) + } +} diff --git a/test/std/runtime/runtime_test.go b/test/std/runtime/runtime_test.go new file mode 100644 index 0000000000..8cdb81872c --- /dev/null +++ b/test/std/runtime/runtime_test.go @@ -0,0 +1,42 @@ +package runtime_test + +import ( + "runtime" + "strings" + "testing" +) + +func TestRuntimeInformationAndCallers(t *testing.T) { + if runtime.GOOS == "" || runtime.GOARCH == "" || runtime.Compiler == "" { + t.Fatal("runtime target information is empty") + } + if runtime.NumCPU() < 1 || runtime.GOMAXPROCS(0) < 1 { + t.Fatal("runtime CPU information is invalid") + } + if !strings.HasPrefix(runtime.Version(), "go") { + t.Fatalf("Version = %q", runtime.Version()) + } + pcs := make([]uintptr, 16) + n := runtime.Callers(0, pcs) + if n == 0 { + t.Fatal("Callers returned no frames") + } + frames := runtime.CallersFrames(pcs[:n]) + foundTest := false + for { + frame, more := frames.Next() + if strings.Contains(frame.Function, "TestRuntimeInformationAndCallers") { + if frame.File == "" || frame.Line == 0 { + t.Fatalf("test frame has incomplete source information: %#v", frame) + } + foundTest = true + break + } + if !more { + break + } + } + if !foundTest { + t.Fatal("CallersFrames did not report TestRuntimeInformationAndCallers") + } +} diff --git a/test/std/runtime/trace/go126_symbols_test.go b/test/std/runtime/trace/go126_symbols_test.go new file mode 100644 index 0000000000..7f77800f47 --- /dev/null +++ b/test/std/runtime/trace/go126_symbols_test.go @@ -0,0 +1,47 @@ +//go:build go1.26 + +package trace_test + +import ( + "bytes" + "context" + "runtime/trace" + "testing" + "time" +) + +func TestFlightRecorder(t *testing.T) { + if trace.IsEnabled() { + t.Skip("another execution trace is already enabled") + } + recorder := trace.NewFlightRecorder(trace.FlightRecorderConfig{ + MinAge: time.Millisecond, + MaxBytes: 1 << 20, + }) + if recorder.Enabled() { + t.Fatal("new flight recorder is enabled before Start") + } + if err := recorder.Start(); err != nil { + t.Fatal(err) + } + if !recorder.Enabled() { + t.Fatal("flight recorder is disabled after Start") + } + trace.WithRegion(context.Background(), "go1.26", func() { + trace.Log(context.Background(), "stdlib", "flight recorder") + }) + var output bytes.Buffer + n, err := recorder.WriteTo(&output) + if err != nil { + recorder.Stop() + t.Fatal(err) + } + if n != int64(output.Len()) || n < 16 { + recorder.Stop() + t.Fatalf("WriteTo wrote %d bytes, buffer length %d", n, output.Len()) + } + recorder.Stop() + if recorder.Enabled() { + t.Fatal("flight recorder is enabled after Stop") + } +} diff --git a/test/std/runtime/trace/trace_test.go b/test/std/runtime/trace/trace_test.go new file mode 100644 index 0000000000..66967fb702 --- /dev/null +++ b/test/std/runtime/trace/trace_test.go @@ -0,0 +1,178 @@ +package trace_test + +import ( + "bytes" + "context" + "runtime/trace" + "testing" +) + +func TestIsEnabled(t *testing.T) { + if trace.IsEnabled() { + t.Error("IsEnabled should return false when tracing is not active") + } + + var buf bytes.Buffer + err := trace.Start(&buf) + if err != nil { + t.Fatalf("trace.Start failed: %v", err) + } + defer trace.Stop() + + if !trace.IsEnabled() { + t.Error("IsEnabled should return true when tracing is active") + } + + trace.Stop() + + if trace.IsEnabled() { + t.Error("IsEnabled should return false after Stop") + } +} + +func TestStartStop(t *testing.T) { + var buf bytes.Buffer + err := trace.Start(&buf) + if err != nil { + t.Fatalf("trace.Start failed: %v", err) + } + + for i := 0; i < 100; i++ { + _ = i * i + } + + trace.Stop() + + if buf.Len() == 0 { + t.Error("trace buffer is empty") + } +} + +func TestStartTwice(t *testing.T) { + var buf1, buf2 bytes.Buffer + err := trace.Start(&buf1) + if err != nil { + t.Fatalf("first trace.Start failed: %v", err) + } + defer trace.Stop() + + err = trace.Start(&buf2) + if err == nil { + t.Error("second trace.Start should fail when already tracing") + } + + trace.Stop() +} + +func TestLog(t *testing.T) { + var buf bytes.Buffer + err := trace.Start(&buf) + if err != nil { + t.Fatalf("trace.Start failed: %v", err) + } + defer trace.Stop() + + ctx := context.Background() + trace.Log(ctx, "test-category", "test-message") + + trace.Stop() + + if buf.Len() == 0 { + t.Error("trace buffer is empty after Log") + } +} + +func TestLogf(t *testing.T) { + var buf bytes.Buffer + err := trace.Start(&buf) + if err != nil { + t.Fatalf("trace.Start failed: %v", err) + } + defer trace.Stop() + + ctx := context.Background() + trace.Logf(ctx, "test-category", "test message: %d", 42) + + trace.Stop() + + if buf.Len() == 0 { + t.Error("trace buffer is empty after Logf") + } +} + +func TestWithRegion(t *testing.T) { + var buf bytes.Buffer + err := trace.Start(&buf) + if err != nil { + t.Fatalf("trace.Start failed: %v", err) + } + defer trace.Stop() + + ctx := context.Background() + executed := false + trace.WithRegion(ctx, "test-region", func() { + executed = true + }) + + if !executed { + t.Error("WithRegion function was not executed") + } + + trace.Stop() + + if buf.Len() == 0 { + t.Error("trace buffer is empty after WithRegion") + } +} + +func TestRegion(t *testing.T) { + var buf bytes.Buffer + err := trace.Start(&buf) + if err != nil { + t.Fatalf("trace.Start failed: %v", err) + } + defer trace.Stop() + + ctx := context.Background() + region := trace.StartRegion(ctx, "manual-region") + if region == nil { + t.Fatal("StartRegion returned nil") + } + + for i := 0; i < 10; i++ { + _ = i * i + } + + region.End() + + trace.Stop() + + if buf.Len() == 0 { + t.Error("trace buffer is empty after Region") + } +} + +func TestTask(t *testing.T) { + var buf bytes.Buffer + err := trace.Start(&buf) + if err != nil { + t.Fatalf("trace.Start failed: %v", err) + } + defer trace.Stop() + + ctx := context.Background() + ctx, task := trace.NewTask(ctx, "test-task") + if task == nil { + t.Fatal("NewTask returned nil task") + } + + trace.Log(ctx, "task-log", "message in task") + + task.End() + + trace.Stop() + + if buf.Len() == 0 { + t.Error("trace buffer is empty after Task") + } +} diff --git a/test/std/slices/slices_test.go b/test/std/slices/slices_test.go new file mode 100644 index 0000000000..1c719c144e --- /dev/null +++ b/test/std/slices/slices_test.go @@ -0,0 +1,372 @@ +package slices_test + +import ( + "strings" + "testing" + + "iter" + "slices" +) + +func collectSeq[T any](seq iter.Seq[T]) []T { + var out []T + for v := range seq { + out = append(out, v) + } + return out +} + +func equalSlice[T comparable](a, b []T) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +func TestSlicesSequences(t *testing.T) { + data := []string{"go", "plus", "rocks"} + + var allIdx []int + var allVals []string + for idx, v := range slices.All(data) { + allIdx = append(allIdx, idx) + allVals = append(allVals, v) + } + if !equalSlice(allIdx, []int{0, 1, 2}) { + t.Fatalf("All indices = %v", allIdx) + } + if !equalSlice(allVals, data) { + t.Fatalf("All values = %v", allVals) + } + + var backIdx []int + var backVals []string + for idx, v := range slices.Backward(data) { + backIdx = append(backIdx, idx) + backVals = append(backVals, v) + } + if !equalSlice(backIdx, []int{2, 1, 0}) { + t.Fatalf("Backward indices = %v", backIdx) + } + if !equalSlice(backVals, []string{"rocks", "plus", "go"}) { + t.Fatalf("Backward values = %v", backVals) + } + + chunks := collectSeq(slices.Chunk(data, 2)) + if len(chunks) != 2 || !equalSlice(chunks[0], []string{"go", "plus"}) || !equalSlice(chunks[1], []string{"rocks"}) { + t.Fatalf("Chunk result = %#v", chunks) + } + + seq := iter.Seq[int](func(yield func(int) bool) { + for _, v := range []int{3, 4} { + if !yield(v) { + return + } + } + }) + appended := slices.AppendSeq([]int{1, 2}, seq) + if !equalSlice(appended, []int{1, 2, 3, 4}) { + t.Fatalf("AppendSeq result = %v", appended) + } + + values := collectSeq(slices.Values([]int{10, 20, 30})) + if !equalSlice(values, []int{10, 20, 30}) { + t.Fatalf("Values result = %v", values) + } + + collected := slices.Collect(iter.Seq[int](func(yield func(int) bool) { + for _, v := range []int{5, 6, 7} { + if !yield(v) { + return + } + } + })) + if !equalSlice(collected, []int{5, 6, 7}) { + t.Fatalf("Collect result = %v", collected) + } +} + +func TestSlicesSearchAndCompare(t *testing.T) { + ints := []int{1, 3, 5, 7, 9} + if idx, found := slices.BinarySearch(ints, 5); idx != 2 || !found { + t.Fatalf("BinarySearch found=%v idx=%d", found, idx) + } + if idx, found := slices.BinarySearch(ints, 4); idx != 2 || found { + t.Fatalf("BinarySearch miss idx=%d found=%v", idx, found) + } + + type person struct { + name string + age int + } + people := []person{ + {name: "Alice", age: 30}, + {name: "Bob", age: 40}, + {name: "Carol", age: 50}, + } + idx, found := slices.BinarySearchFunc(people, "Bob", func(p person, target string) int { + return strings.Compare(p.name, target) + }) + if !found || idx != 1 { + t.Fatalf("BinarySearchFunc result idx=%d found=%v", idx, found) + } + + numbers := []int{1, 2, 3, 4, 5} + if !slices.Contains(numbers, 4) { + t.Fatal("Contains should find element") + } + if slices.Contains(numbers, 42) { + t.Fatal("Contains should miss element") + } + + if !slices.ContainsFunc(numbers, func(v int) bool { return v%2 == 0 }) { + t.Fatal("ContainsFunc should find even element") + } + if slices.ContainsFunc(numbers, func(v int) bool { return v > 100 }) { + t.Fatal("ContainsFunc should miss element") + } + + if idx := slices.Index(numbers, 4); idx != 3 { + t.Fatalf("Index returned %d", idx) + } + if idx := slices.Index(numbers, 42); idx != -1 { + t.Fatalf("Index miss returned %d", idx) + } + if idx := slices.IndexFunc(numbers, func(v int) bool { return v > 4 }); idx != 4 { + t.Fatalf("IndexFunc returned %d", idx) + } + if idx := slices.IndexFunc(numbers, func(v int) bool { return v < 0 }); idx != -1 { + t.Fatalf("IndexFunc miss returned %d", idx) + } + + if cmp := slices.Compare([]int{1, 2, 3}, []int{1, 2, 3}); cmp != 0 { + t.Fatalf("Compare eq result %d", cmp) + } + if cmp := slices.Compare([]int{1, 2}, []int{1, 3}); cmp != -1 { + t.Fatalf("Compare lt result %d", cmp) + } + if cmp := slices.Compare([]int{1, 3}, []int{1, 2}); cmp != 1 { + t.Fatalf("Compare gt result %d", cmp) + } + + if cmp := slices.CompareFunc([]string{"a", "bb"}, []string{"a", "bbb"}, func(a, b string) int { + return len(a) - len(b) + }); cmp != -1 { + t.Fatalf("CompareFunc result %d", cmp) + } + + if !slices.Equal([]int{1, 2}, []int{1, 2}) { + t.Fatal("Equal should report equality") + } + if slices.Equal([]int{1, 2}, []int{2, 1}) { + t.Fatal("Equal should report inequality") + } + + if !slices.EqualFunc([]string{"Go", "Plus"}, []string{"go", "plus"}, func(a, b string) bool { + return strings.EqualFold(a, b) + }) { + t.Fatal("EqualFunc should report equality") + } + if slices.EqualFunc([]string{"Go"}, []string{"No"}, func(a, b string) bool { return a == b }) { + t.Fatal("EqualFunc should report inequality") + } +} + +func TestSlicesCompaction(t *testing.T) { + values := []int{1, 1, 2, 2, 2, 3} + compacted := slices.Compact(values) + if !equalSlice(compacted, []int{1, 2, 3}) { + t.Fatalf("Compact result = %v", compacted) + } + + type record struct { + id int + name string + } + records := []record{ + {id: 1, name: "alpha"}, + {id: 1, name: "beta"}, + {id: 2, name: "gamma"}, + {id: 2, name: "delta"}, + } + compactedFunc := slices.CompactFunc(records, func(a, b record) bool { return a.id == b.id }) + if len(compactedFunc) != 2 || compactedFunc[0].name != "alpha" || compactedFunc[1].name != "gamma" { + t.Fatalf("CompactFunc result = %#v", compactedFunc) + } +} + +func TestSlicesModification(t *testing.T) { + s := []int{0, 1, 2, 3, 4} + clipped := slices.Clip(append([]int(nil), s...)) + if len(clipped) != len(s) || cap(clipped) != len(clipped) { + t.Fatalf("Clip len=%d cap=%d", len(clipped), cap(clipped)) + } + + clone := slices.Clone(s) + if !equalSlice(clone, s) { + t.Fatalf("Clone mismatch %v", clone) + } + if len(clone) > 0 && &clone[0] == &s[0] { + t.Fatal("Clone should allocate new backing array") + } + + concat := slices.Concat([]int{1, 2}, []int{}, []int{3, 4}) + if !equalSlice(concat, []int{1, 2, 3, 4}) { + t.Fatalf("Concat result = %v", concat) + } + + deleted := slices.Delete(append([]int(nil), s...), 1, 3) + if !equalSlice(deleted, []int{0, 3, 4}) { + t.Fatalf("Delete result = %v", deleted) + } + + filtered := slices.DeleteFunc([]int{1, 2, 3, 4, 5}, func(v int) bool { return v%2 == 0 }) + if !equalSlice(filtered, []int{1, 3, 5}) { + t.Fatalf("DeleteFunc result = %v", filtered) + } + + grown := slices.Grow([]int{1, 2}, 4) + if len(grown) != 2 || cap(grown) < 6 { + t.Fatalf("Grow len=%d cap=%d", len(grown), cap(grown)) + } + + inserted := slices.Insert([]int{1, 4, 5}, 1, 2, 3) + if !equalSlice(inserted, []int{1, 2, 3, 4, 5}) { + t.Fatalf("Insert result = %v", inserted) + } + + replaced := slices.Replace([]int{1, 2, 3, 4}, 1, 3, 8, 9) + if !equalSlice(replaced, []int{1, 8, 9, 4}) { + t.Fatalf("Replace result = %v", replaced) + } + + repeated := slices.Repeat([]int{1, 2}, 3) + if !equalSlice(repeated, []int{1, 2, 1, 2, 1, 2}) { + t.Fatalf("Repeat result = %v", repeated) + } + + reverse := []int{1, 2, 3, 4} + slices.Reverse(reverse) + if !equalSlice(reverse, []int{4, 3, 2, 1}) { + t.Fatalf("Reverse result = %v", reverse) + } +} + +func TestSlicesOrdering(t *testing.T) { + sorted := []int{5, 1, 3, 2, 4} + slices.Sort(sorted) + if !equalSlice(sorted, []int{1, 2, 3, 4, 5}) { + t.Fatalf("Sort result = %v", sorted) + } + if !slices.IsSorted(sorted) { + t.Fatal("IsSorted should report true") + } + if slices.IsSorted([]int{3, 2, 1}) { + t.Fatal("IsSorted should report false") + } + + stringsByLen := []string{"go", "plusplus", "llgo", "c"} + slices.SortFunc(stringsByLen, func(a, b string) int { + return len(a) - len(b) + }) + if !equalSlice(stringsByLen, []string{"c", "go", "llgo", "plusplus"}) { + t.Fatalf("SortFunc result = %v", stringsByLen) + } + if !slices.IsSortedFunc(stringsByLen, func(a, b string) int { return len(a) - len(b) }) { + t.Fatal("IsSortedFunc should report true") + } + if slices.IsSortedFunc(stringsByLen, func(a, b string) int { return len(b) - len(a) }) { + t.Fatal("IsSortedFunc should report false") + } + + type stable struct { + key string + order int + } + stableData := []stable{ + {key: "b", order: 0}, + {key: "a", order: 1}, + {key: "a", order: 2}, + {key: "c", order: 3}, + } + slices.SortStableFunc(stableData, func(a, b stable) int { + return strings.Compare(a.key, b.key) + }) + if stableData[0].order != 1 || stableData[1].order != 2 || stableData[2].order != 0 || stableData[3].order != 3 { + t.Fatalf("SortStableFunc did not preserve order: %#v", stableData) + } + + seq := iter.Seq[int](func(yield func(int) bool) { + for _, v := range []int{3, 1, 2} { + if !yield(v) { + return + } + } + }) + sortedVals := slices.Sorted(seq) + if !equalSlice(sortedVals, []int{1, 2, 3}) { + t.Fatalf("Sorted result = %v", sortedVals) + } + + seqStrings := iter.Seq[string](func(yield func(string) bool) { + for _, v := range []string{"bbb", "a", "cc"} { + if !yield(v) { + return + } + } + }) + sortedFunc := slices.SortedFunc(seqStrings, func(a, b string) int { + return len(a) - len(b) + }) + if !equalSlice(sortedFunc, []string{"a", "cc", "bbb"}) { + t.Fatalf("SortedFunc result = %v", sortedFunc) + } + + seqStable := iter.Seq[stable](func(yield func(stable) bool) { + data := []stable{ + {key: "same", order: 1}, + {key: "same", order: 2}, + {key: "other", order: 3}, + } + for _, v := range data { + if !yield(v) { + return + } + } + }) + sortedStable := slices.SortedStableFunc(seqStable, func(a, b stable) int { + return strings.Compare(a.key, b.key) + }) + if sortedStable[0].order != 3 || sortedStable[1].order != 1 || sortedStable[2].order != 2 { + t.Fatalf("SortedStableFunc result = %#v", sortedStable) + } +} + +func TestSlicesExtrema(t *testing.T) { + ints := []int{3, 7, 2, 9, 5} + if max := slices.Max(ints); max != 9 { + t.Fatalf("Max = %d", max) + } + if min := slices.Min(ints); min != 2 { + t.Fatalf("Min = %d", min) + } + + stringsByLen := []string{"go", "plus", "llgo", "z"} + maxLen := slices.MaxFunc(stringsByLen, func(a, b string) int { + return len(a) - len(b) + }) + if maxLen != "plus" { + t.Fatalf("MaxFunc = %q", maxLen) + } + minLen := slices.MinFunc(stringsByLen, func(a, b string) int { + return len(a) - len(b) + }) + if minLen != "z" { + t.Fatalf("MinFunc = %q", minLen) + } +} diff --git a/test/std/sort/sort_test.go b/test/std/sort/sort_test.go new file mode 100644 index 0000000000..334437b1fd --- /dev/null +++ b/test/std/sort/sort_test.go @@ -0,0 +1,201 @@ +package sort_test + +import ( + "math" + "sort" + "strings" + "testing" +) + +type intData []int + +func (d intData) Len() int { return len(d) } +func (d intData) Less(i, j int) bool { return d[i] < d[j] } +func (d intData) Swap(i, j int) { d[i], d[j] = d[j], d[i] } + +var _ sort.Interface = intData{} + +// reverseLess implements sort.Interface with a descending order check to exercise sort.Sort, sort.Stable, and sort.Reverse. +type taggedValue struct { + value int + tag string +} + +type reverseLess struct { + data []taggedValue +} + +func (r reverseLess) Len() int { return len(r.data) } +func (r reverseLess) Less(i, j int) bool { return r.data[i].value > r.data[j].value } +func (r reverseLess) Swap(i, j int) { r.data[i], r.data[j] = r.data[j], r.data[i] } + +func TestPrimitiveSortHelpers(t *testing.T) { + ints := []int{5, 2, 4, 1, 3} + sort.Ints(ints) + if !sort.IntsAreSorted(ints) { + t.Fatalf("IntsAreSorted should report sorted slice, got %v", ints) + } + + strs := []string{"delta", "bravo", "alpha", "charlie"} + sort.Strings(strs) + if !sort.StringsAreSorted(strs) { + t.Fatalf("StringsAreSorted should report sorted slice, got %v", strs) + } + + floats := []float64{math.NaN(), 3.14, -1.5, 2.7} + sort.Float64s(floats) + if !sort.Float64sAreSorted(floats) { + t.Fatalf("Float64sAreSorted should report sorted slice, got %v", floats) + } + if !math.IsNaN(floats[0]) { + t.Fatalf("expected NaN to compare first, got %v", floats) + } +} + +func TestSearchHelpers(t *testing.T) { + ints := []int{1, 3, 5, 7, 9} + if idx := sort.SearchInts(ints, 7); idx != 3 { + t.Fatalf("SearchInts expected index 3, got %d", idx) + } + if idx := sort.SearchInts(ints, 4); idx != 2 { + t.Fatalf("SearchInts miss expected 2, got %d", idx) + } + + strs := []string{"ant", "bee", "cat", "dog"} + if idx := sort.SearchStrings(strs, "cat"); idx != 2 { + t.Fatalf("SearchStrings expected index 2, got %d", idx) + } + + floats := []float64{1.5, 2.5, 3.5, 4.5} + if idx := sort.SearchFloat64s(floats, 3.5); idx != 2 { + t.Fatalf("SearchFloat64s expected index 2, got %d", idx) + } + + // sort.Search reports the minimum i for which f(i) is true. + if idx := sort.Search(len(ints), func(i int) bool { return ints[i] >= 6 }); idx != 3 { + t.Fatalf("Search expected index 3, got %d", idx) + } + + i, found := sort.Find(len(strs), func(i int) int { + return strings.Compare("dog", strs[i]) + }) + if !found || i != 3 { + t.Fatalf("Find expected index 3, found=%v, i=%d", found, i) + } +} + +func TestSliceHelpers(t *testing.T) { + type person struct { + name string + age int + } + people := []person{ + {name: "Alice", age: 30}, + {name: "Carol", age: 50}, + {name: "Bob", age: 40}, + {name: "Dave", age: 50}, + } + + sort.Slice(people, func(i, j int) bool { + return people[i].name < people[j].name + }) + if want := []string{"Alice", "Bob", "Carol", "Dave"}; people[0].name != want[0] || people[1].name != want[1] || people[2].name != want[2] || people[3].name != want[3] { + t.Fatalf("Slice sort mismatch: %+v", people) + } + if !sort.SliceIsSorted(people, func(i, j int) bool { + return people[i].name < people[j].name + }) { + t.Fatal("SliceIsSorted should report sorted order") + } + + sort.SliceStable(people, func(i, j int) bool { + return people[i].age < people[j].age + }) + if people[0].name != "Alice" || people[1].name != "Bob" || people[2].name != "Carol" || people[3].name != "Dave" { + t.Fatalf("SliceStable should preserve relative order, got %+v", people) + } +} + +func TestInterfaceSorting(t *testing.T) { + data := intData{9, 4, 7, 1} + sort.Sort(data) + if !sort.IsSorted(data) { + t.Fatalf("IsSorted should report sorted data, got %v", data) + } + + desc := []int{1, 2, 3, 4} + sort.Sort(sort.Reverse(sort.IntSlice(desc))) + expected := []int{4, 3, 2, 1} + for i, want := range expected { + if desc[i] != want { + t.Fatalf("Reverse sort mismatch at %d: got %d want %d", i, desc[i], want) + } + } + + stableData := reverseLess{ + data: []taggedValue{ + {value: 3, tag: "first"}, + {value: 3, tag: "second"}, + {value: 2, tag: "third"}, + {value: 1, tag: "fourth"}, + }, + } + sort.Stable(stableData) + if stableData.data[0].tag != "first" || stableData.data[1].tag != "second" { + t.Fatalf("Stable should keep equal elements in original order, got %#v", stableData.data) + } +} + +func TestIntSliceMethods(t *testing.T) { + values := sort.IntSlice([]int{5, 1, 3}) + if values.Len() != 3 { + t.Fatalf("Len expected 3, got %d", values.Len()) + } + if !values.Less(1, 2) { + t.Fatal("Less should report 1<3") + } + values.Swap(0, 2) + if values[0] != 3 || values[2] != 5 { + t.Fatalf("Swap mismatch: %v", values) + } + values.Sort() + if values.Search(4) != 2 { + t.Fatalf("Search expected index 2, got %d", values.Search(4)) + } +} + +func TestStringSliceMethods(t *testing.T) { + values := sort.StringSlice([]string{"c", "a", "b"}) + if values.Len() != 3 { + t.Fatalf("Len expected 3, got %d", values.Len()) + } + if !values.Less(1, 2) { + t.Fatal("Less should report a= 0 { + if got := strconv.FormatUint(uint64(tc.value), tc.base); got != tc.want { + t.Errorf("FormatUint %s: got %q, want %q", tc.name, got, tc.want) + } + } + } + + if v, err := strconv.ParseInt("1_024", 0, 64); err != nil || v != 1024 { + t.Fatalf("ParseInt with underscores: value=%d err=%v", v, err) + } + if v, err := strconv.ParseInt("-0b1010", 0, 64); err != nil || v != -10 { + t.Fatalf("ParseInt binary prefix: value=%d err=%v", v, err) + } + if v, err := strconv.ParseUint("0XFF", 0, 64); err != nil || v != 255 { + t.Fatalf("ParseUint hex prefix: value=%d err=%v", v, err) + } + + if got := strconv.Itoa(-123); got != "-123" { + t.Fatalf("Itoa mismatch: %q", got) + } + if v, err := strconv.Atoi("-123"); err != nil || v != -123 { + t.Fatalf("Atoi mismatch: value=%d err=%v", v, err) + } + + buf := []byte("value=") + buf = strconv.AppendInt(buf, -255, 16) + if got := string(buf); got != "value=-ff" { + t.Fatalf("AppendInt mismatch: %q", got) + } + + buf = strconv.AppendUint([]byte("count="), 511, 8) + if got := string(buf); got != "count=777" { + t.Fatalf("AppendUint mismatch: %q", got) + } +} + +func TestParseIntErrors(t *testing.T) { + const invalid = "xyz" + _, err := strconv.ParseInt(invalid, 10, 64) + if err == nil { + t.Fatal("ParseInt should fail on invalid input") + } + + var numErr *strconv.NumError + if !errors.As(err, &numErr) { + t.Fatalf("ParseInt error should be *NumError, got %T", err) + } + if numErr.Func != "ParseInt" || numErr.Num != invalid { + t.Fatalf("NumError metadata mismatch: %+v", numErr) + } + if !errors.Is(err, strconv.ErrSyntax) { + t.Fatalf("ParseInt invalid should unwrap to ErrSyntax, got %v", err) + } + + _, err = strconv.ParseInt("128", 10, 8) + if err == nil { + t.Fatal("ParseInt should report overflow") + } + if !errors.Is(err, strconv.ErrRange) { + t.Fatalf("ParseInt overflow should unwrap to ErrRange, got %v", err) + } + + _, err = strconv.ParseUint("256", 10, 8) + if err == nil { + t.Fatal("ParseUint should report overflow") + } + if !errors.Is(err, strconv.ErrRange) { + t.Fatalf("ParseUint overflow should unwrap to ErrRange, got %v", err) + } +} + +func TestIntSizeMatchesUintSize(t *testing.T) { + if strconv.IntSize != bits.UintSize { + t.Fatalf("IntSize=%d UintSize=%d", strconv.IntSize, bits.UintSize) + } +} + +func TestBoolConversions(t *testing.T) { + for input, want := range map[string]bool{ + "true": true, + "FALSE": false, + "1": true, + "0": false, + } { + got, err := strconv.ParseBool(input) + if err != nil { + t.Fatalf("ParseBool(%q) unexpected error: %v", input, err) + } + if got != want { + t.Fatalf("ParseBool(%q) = %v, want %v", input, got, want) + } + + expected := "false" + if want { + expected = "true" + } + if formatted := strconv.FormatBool(got); formatted != expected { + t.Fatalf("FormatBool(%v) mismatch: %q", got, formatted) + } + } + + if _, err := strconv.ParseBool("maybe"); !errors.Is(err, strconv.ErrSyntax) { + t.Fatalf("ParseBool invalid should return ErrSyntax, got %v", err) + } + + buf := strconv.AppendBool([]byte("flag="), true) + if got := string(buf); got != "flag=true" { + t.Fatalf("AppendBool mismatch: %q", got) + } +} + +func TestFloatConversions(t *testing.T) { + if got := strconv.FormatFloat(-123.456, 'f', 2, 64); got != "-123.46" { + t.Fatalf("FormatFloat 64 mismatch: %q", got) + } + if got := strconv.FormatFloat(math.Inf(1), 'f', 0, 64); got != "+Inf" { + t.Fatalf("FormatFloat +Inf mismatch: %q", got) + } + if got := strconv.FormatFloat(math.NaN(), 'g', -1, 64); got != "NaN" { + t.Fatalf("FormatFloat NaN mismatch: %q", got) + } + + f32 := float32(3.1415927) + expected32 := "3.1415927" + if got := strconv.FormatFloat(float64(f32), 'g', -1, 32); got != expected32 { + t.Fatalf("FormatFloat 32 mismatch: %q", got) + } + + if v, err := strconv.ParseFloat(expected32, 32); err != nil || math.Abs(float64(float32(v))-float64(f32)) > 1e-6 { + t.Fatalf("ParseFloat 32 mismatch: value=%v err=%v", v, err) + } + if v, err := strconv.ParseFloat("-123.46", 64); err != nil || math.Abs(v-(-123.46)) > 1e-12 { + t.Fatalf("ParseFloat 64 mismatch: value=%v err=%v", v, err) + } + if v, err := strconv.ParseFloat("NaN", 64); err != nil || !math.IsNaN(v) { + t.Fatalf("ParseFloat NaN mismatch: value=%v err=%v", v, err) + } + if v, err := strconv.ParseFloat("+Inf", 64); err != nil || !math.IsInf(v, 1) { + t.Fatalf("ParseFloat +Inf mismatch: value=%v err=%v", v, err) + } + + if _, err := strconv.ParseFloat("1e5000", 64); !errors.Is(err, strconv.ErrRange) { + t.Fatalf("ParseFloat overflow should return ErrRange, got %v", err) + } + if _, err := strconv.ParseFloat("not-a-number", 64); !errors.Is(err, strconv.ErrSyntax) { + t.Fatalf("ParseFloat invalid should return ErrSyntax, got %v", err) + } + + buf := strconv.AppendFloat([]byte("value="), 3.5, 'f', 1, 64) + if got := string(buf); got != "value=3.5" { + t.Fatalf("AppendFloat mismatch: %q", got) + } +} + +func TestComplexConversions(t *testing.T) { + c := complex(1.25, -2.5) + if got := strconv.FormatComplex(c, 'f', 2, 128); got != "(1.25-2.50i)" { + t.Fatalf("FormatComplex mismatch: %q", got) + } + + parsed, err := strconv.ParseComplex("(1.25-2.50i)", 128) + if err != nil { + t.Fatalf("ParseComplex unexpected error: %v", err) + } + if real(parsed) != real(c) || imag(parsed) != imag(c) { + t.Fatalf("ParseComplex mismatch: got %v, want %v", parsed, c) + } + + if _, err := strconv.ParseComplex("(bad)", 128); !errors.Is(err, strconv.ErrSyntax) { + t.Fatalf("ParseComplex invalid should return ErrSyntax, got %v", err) + } +} + +func TestQuoteFunctions(t *testing.T) { + const sample = "café" + const sampleWithNewline = "café\n" + + quoted := strconv.Quote(sample) + if !strings.HasPrefix(quoted, "\"") || !strings.HasSuffix(quoted, "\"") { + t.Fatalf("Quote should wrap input in quotes: %q", quoted) + } + if decoded, err := strconv.Unquote(quoted); err != nil || decoded != sample { + t.Fatalf("Quote round-trip mismatch: decoded=%q err=%v", decoded, err) + } + + quotedASCII := strconv.QuoteToASCII(sample) + if !isASCII(quotedASCII) { + t.Fatalf("QuoteToASCII should emit ASCII-only output: %q", quotedASCII) + } + if decoded, err := strconv.Unquote(quotedASCII); err != nil || decoded != sample { + t.Fatalf("QuoteToASCII round-trip mismatch: decoded=%q err=%v", decoded, err) + } + + quotedGraphic := strconv.QuoteToGraphic(sampleWithNewline) + if !strings.Contains(quotedGraphic, "\\n") { + t.Fatalf("QuoteToGraphic should escape newlines: %q", quotedGraphic) + } + if decoded, err := strconv.Unquote(quotedGraphic); err != nil || decoded != sampleWithNewline { + t.Fatalf("QuoteToGraphic round-trip mismatch: decoded=%q err=%v", decoded, err) + } + + runeQuoted := strconv.QuoteRune('π') + if !strings.HasPrefix(runeQuoted, "'") || !strings.HasSuffix(runeQuoted, "'") { + t.Fatalf("QuoteRune should wrap rune in single quotes: %q", runeQuoted) + } + if decoded, err := strconv.Unquote(runeQuoted); err != nil || decoded != "π" { + t.Fatalf("QuoteRune round-trip mismatch: decoded=%q err=%v", decoded, err) + } + + runeQuoteASCII := strconv.QuoteRuneToASCII('π') + if !isASCII(runeQuoteASCII) { + t.Fatalf("QuoteRuneToASCII should emit ASCII-only output: %q", runeQuoteASCII) + } + if decoded, err := strconv.Unquote(runeQuoteASCII); err != nil || decoded != "π" { + t.Fatalf("QuoteRuneToASCII round-trip mismatch: decoded=%q err=%v", decoded, err) + } + + runeQuoteGraphic := strconv.QuoteRuneToGraphic('\n') + if !strings.Contains(runeQuoteGraphic, "\\n") { + t.Fatalf("QuoteRuneToGraphic should escape control characters: %q", runeQuoteGraphic) + } + if decoded, err := strconv.Unquote(runeQuoteGraphic); err != nil || decoded != "\n" { + t.Fatalf("QuoteRuneToGraphic round-trip mismatch: decoded=%q err=%v", decoded, err) + } +} + +func TestAppendQuoteVariants(t *testing.T) { + if got := string(strconv.AppendQuote(nil, "hi\n")); got != strconv.Quote("hi\n") { + t.Fatalf("AppendQuote mismatch: %q", got) + } + if got := string(strconv.AppendQuoteToASCII(nil, "hi\n")); got != strconv.QuoteToASCII("hi\n") { + t.Fatalf("AppendQuoteToASCII mismatch: %q", got) + } + if got := string(strconv.AppendQuoteToGraphic(nil, "café\n")); got != strconv.QuoteToGraphic("café\n") { + t.Fatalf("AppendQuoteToGraphic mismatch: %q", got) + } + + if got := string(strconv.AppendQuoteRune(nil, '\n')); got != strconv.QuoteRune('\n') { + t.Fatalf("AppendQuoteRune mismatch: %q", got) + } + if got := string(strconv.AppendQuoteRuneToASCII(nil, 'π')); got != strconv.QuoteRuneToASCII('π') { + t.Fatalf("AppendQuoteRuneToASCII mismatch: %q", got) + } + if got := string(strconv.AppendQuoteRuneToGraphic(nil, '\n')); got != strconv.QuoteRuneToGraphic('\n') { + t.Fatalf("AppendQuoteRuneToGraphic mismatch: %q", got) + } +} + +func TestUnquoteFunctions(t *testing.T) { + if got, err := strconv.Unquote("\"line\\n\""); err != nil || got != "line\n" { + t.Fatalf("Unquote escaped mismatch: value=%q err=%v", got, err) + } + if got, err := strconv.Unquote("`raw`"); err != nil || got != "raw" { + t.Fatalf("Unquote raw mismatch: value=%q err=%v", got, err) + } + + if _, err := strconv.Unquote("\"unterminated"); err == nil { + t.Fatal("Unquote should fail on unterminated string") + } + + r, multibyte, tail, err := strconv.UnquoteChar("\\u263Arest", '"') + if err != nil || r != '☺' || !multibyte || tail != "rest" { + t.Fatalf("UnquoteChar unicode mismatch: r=%q multibyte=%v tail=%q err=%v", r, multibyte, tail, err) + } + + if _, _, _, err := strconv.UnquoteChar("\\xZZ", '"'); !errors.Is(err, strconv.ErrSyntax) { + t.Fatalf("UnquoteChar invalid should return ErrSyntax, got %v", err) + } +} + +func TestQuotedPrefix(t *testing.T) { + prefix, err := strconv.QuotedPrefix("\"hi\" tail") + if err != nil { + t.Fatalf("QuotedPrefix unexpected error: %v", err) + } + if prefix != "\"hi\"" { + t.Fatalf("QuotedPrefix mismatch: %q", prefix) + } + + rawPrefix, err := strconv.QuotedPrefix("`raw`\n") + if err != nil { + t.Fatalf("QuotedPrefix raw error: %v", err) + } + if rawPrefix != "`raw`" { + t.Fatalf("QuotedPrefix raw mismatch: %q", rawPrefix) + } + + if _, err := strconv.QuotedPrefix("noquote"); !errors.Is(err, strconv.ErrSyntax) { + t.Fatalf("QuotedPrefix invalid should return ErrSyntax, got %v", err) + } +} + +func TestGraphicAndBackquote(t *testing.T) { + if !strconv.IsPrint('Ω') || !strconv.IsGraphic('Ω') { + t.Fatal("Expected IsPrint/IsGraphic to report true for Ω") + } + if strconv.IsPrint('\t') || strconv.IsGraphic('\t') { + t.Fatal("Expected IsPrint/IsGraphic to report false for tab") + } + + if !strconv.CanBackquote("hello world") { + t.Fatal("CanBackquote should allow simple ASCII") + } + if strconv.CanBackquote("line\n") { + t.Fatal("CanBackquote should reject strings with newlines") + } + if strconv.CanBackquote("tick`") { + t.Fatal("CanBackquote should reject strings with backquotes") + } +} + +func TestNumErrorFormatting(t *testing.T) { + _, err := strconv.ParseUint(strings.Repeat("9", 40), 10, 32) + if err == nil { + t.Fatal("ParseUint should fail on large input") + } + + var numErr *strconv.NumError + if !errors.As(err, &numErr) { + t.Fatalf("expected *NumError, got %T", err) + } + if !errors.Is(err, strconv.ErrRange) { + t.Fatalf("expected ErrRange, got %v", err) + } + if numErr.Unwrap() != strconv.ErrRange { + t.Fatalf("NumError.Unwrap mismatch: %v", numErr.Unwrap()) + } + if !strings.Contains(numErr.Error(), "value out of range") { + t.Fatalf("NumError message missing range hint: %q", numErr.Error()) + } +} + +func isASCII(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] > 0x7f { + return false + } + } + return true +} diff --git a/test/std/strings/strings_test.go b/test/std/strings/strings_test.go new file mode 100644 index 0000000000..9f9f49e84d --- /dev/null +++ b/test/std/strings/strings_test.go @@ -0,0 +1,380 @@ +package strings_test + +import ( + "bytes" + "io" + "iter" + "strings" + "testing" + "unicode" + "unsafe" +) + +func collectSeq(seq iter.Seq[string]) []string { + var out []string + for v := range seq { + out = append(out, v) + } + return out +} + +func TestStringsBasicFunctions(t *testing.T) { + src := "go+plus" + cloned := strings.Clone(src) + if cloned != src { + t.Fatalf("Clone(%q) = %q", src, cloned) + } + if unsafe.StringData(cloned) == unsafe.StringData(src) { + t.Fatalf("Clone should allocate a new backing string") + } + + if strings.Compare("a", "b") >= 0 { + t.Fatal("Compare should report a= %d", got, want) + } + + // Field B should be aligned for int32. + if off := unsafe.Offsetof(v.B); off%unsafe.Alignof(int32(0)) != 0 { + t.Fatalf("Offsetof(B) = %d, not int32-aligned", off) + } +} diff --git a/test/std/sync/atomic/atomic_basic_test.go b/test/std/sync/atomic/atomic_basic_test.go new file mode 100644 index 0000000000..ae8da71831 --- /dev/null +++ b/test/std/sync/atomic/atomic_basic_test.go @@ -0,0 +1,276 @@ +package atomic_test + +import ( + "sync/atomic" + "testing" +) + +func TestAddInt32(t *testing.T) { + var i int32 = 10 + + // Test positive addition + result := atomic.AddInt32(&i, 5) + if result != 15 { + t.Fatalf("AddInt32(10, 5) = %d, want 15", result) + } + if i != 15 { + t.Fatalf("After AddInt32, i = %d, want 15", i) + } + + // Test negative addition + result = atomic.AddInt32(&i, -10) + if result != 5 { + t.Fatalf("AddInt32(15, -10) = %d, want 5", result) + } + if i != 5 { + t.Fatalf("After AddInt32, i = %d, want 5", i) + } + + // Test zero addition + result = atomic.AddInt32(&i, 0) + if result != 5 { + t.Fatalf("AddInt32(5, 0) = %d, want 5", result) + } +} + +func TestAddInt64(t *testing.T) { + var i int64 = 100 + + // Test positive addition + result := atomic.AddInt64(&i, 50) + if result != 150 { + t.Fatalf("AddInt64(100, 50) = %d, want 150", result) + } + if i != 150 { + t.Fatalf("After AddInt64, i = %d, want 150", i) + } + + // Test negative addition + result = atomic.AddInt64(&i, -200) + if result != -50 { + t.Fatalf("AddInt64(150, -200) = %d, want -50", result) + } + if i != -50 { + t.Fatalf("After AddInt64, i = %d, want -50", i) + } +} + +func TestAddUint32(t *testing.T) { + var i uint32 = 10 + + // Test positive addition + result := atomic.AddUint32(&i, 5) + if result != 15 { + t.Fatalf("AddUint32(10, 5) = %d, want 15", result) + } + if i != 15 { + t.Fatalf("After AddUint32, i = %d, want 15", i) + } + + // Test addition that causes overflow + i = ^uint32(0) - 5 // Max value minus 5 + result = atomic.AddUint32(&i, 10) + // Should wrap around + if result != 4 { + t.Fatalf("AddUint32(max-5, 10) = %d, want 4 (wraparound)", result) + } +} + +func TestAddUint64(t *testing.T) { + var i uint64 = 10 + + // Test positive addition + result := atomic.AddUint64(&i, 5) + if result != 15 { + t.Fatalf("AddUint64(10, 5) = %d, want 15", result) + } + if i != 15 { + t.Fatalf("After AddUint64, i = %d, want 15", i) + } + + // Test addition that causes overflow + i = ^uint64(0) - 5 // Max value minus 5 + result = atomic.AddUint64(&i, 10) + // Should wrap around + if result != 4 { + t.Fatalf("AddUint64(max-5, 10) = %d, want 4 (wraparound)", result) + } +} + +func TestAddUintptr(t *testing.T) { + var i uintptr = 10 + + // Test positive addition + result := atomic.AddUintptr(&i, 5) + if result != 15 { + t.Fatalf("AddUintptr(10, 5) = %d, want 15", result) + } + if i != 15 { + t.Fatalf("After AddUintptr, i = %d, want 15", i) + } +} + +func TestLoadInt32(t *testing.T) { + var i int32 = 42 + + // Test loading + result := atomic.LoadInt32(&i) + if result != 42 { + t.Fatalf("LoadInt32(42) = %d, want 42", result) + } +} + +func TestLoadInt64(t *testing.T) { + var i int64 = 42 + + // Test loading + result := atomic.LoadInt64(&i) + if result != 42 { + t.Fatalf("LoadInt64(42) = %d, want 42", result) + } +} + +func TestLoadUint32(t *testing.T) { + var i uint32 = 42 + + // Test loading + result := atomic.LoadUint32(&i) + if result != 42 { + t.Fatalf("LoadUint32(42) = %d, want 42", result) + } +} + +func TestLoadUint64(t *testing.T) { + var i uint64 = 42 + + // Test loading + result := atomic.LoadUint64(&i) + if result != 42 { + t.Fatalf("LoadUint64(42) = %d, want 42", result) + } +} + +func TestLoadUintptr(t *testing.T) { + var i uintptr = 42 + + // Test loading + result := atomic.LoadUintptr(&i) + if result != 42 { + t.Fatalf("LoadUintptr(42) = %d, want 42", result) + } +} + +func TestStoreInt32(t *testing.T) { + var i int32 + + // Test storing + atomic.StoreInt32(&i, 42) + if i != 42 { + t.Fatalf("After StoreInt32(42), i = %d, want 42", i) + } +} + +func TestStoreInt64(t *testing.T) { + var i int64 + + // Test storing + atomic.StoreInt64(&i, 42) + if i != 42 { + t.Fatalf("After StoreInt64(42), i = %d, want 42", i) + } +} + +func TestStoreUint32(t *testing.T) { + var i uint32 + + // Test storing + atomic.StoreUint32(&i, 42) + if i != 42 { + t.Fatalf("After StoreUint32(42), i = %d, want 42", i) + } +} + +func TestStoreUint64(t *testing.T) { + var i uint64 + + // Test storing + atomic.StoreUint64(&i, 42) + if i != 42 { + t.Fatalf("After StoreUint64(42), i = %d, want 42", i) + } +} + +func TestStoreUintptr(t *testing.T) { + var i uintptr + + // Test storing + atomic.StoreUintptr(&i, 42) + if i != 42 { + t.Fatalf("After StoreUintptr(42), i = %d, want 42", i) + } +} + +func TestSwapInt32(t *testing.T) { + var i int32 = 10 + + // Test swapping + old := atomic.SwapInt32(&i, 20) + if old != 10 { + t.Fatalf("SwapInt32(10, 20) = %d, want 10", old) + } + if i != 20 { + t.Fatalf("After SwapInt32, i = %d, want 20", i) + } +} + +func TestSwapInt64(t *testing.T) { + var i int64 = 10 + + // Test swapping + old := atomic.SwapInt64(&i, 20) + if old != 10 { + t.Fatalf("SwapInt64(10, 20) = %d, want 10", old) + } + if i != 20 { + t.Fatalf("After SwapInt64, i = %d, want 20", i) + } +} + +func TestSwapUint32(t *testing.T) { + var i uint32 = 10 + + // Test swapping + old := atomic.SwapUint32(&i, 20) + if old != 10 { + t.Fatalf("SwapUint32(10, 20) = %d, want 10", old) + } + if i != 20 { + t.Fatalf("After SwapUint32, i = %d, want 20", i) + } +} + +func TestSwapUint64(t *testing.T) { + var i uint64 = 10 + + // Test swapping + old := atomic.SwapUint64(&i, 20) + if old != 10 { + t.Fatalf("SwapUint64(10, 20) = %d, want 10", old) + } + if i != 20 { + t.Fatalf("After SwapUint64, i = %d, want 20", i) + } +} + +func TestSwapUintptr(t *testing.T) { + var i uintptr = 10 + + // Test swapping + old := atomic.SwapUintptr(&i, 20) + if old != 10 { + t.Fatalf("SwapUintptr(10, 20) = %d, want 10", old) + } + if i != 20 { + t.Fatalf("After SwapUintptr, i = %d, want 20", i) + } +} diff --git a/test/std/sync/atomic/atomic_bitwise_methods_test.go b/test/std/sync/atomic/atomic_bitwise_methods_test.go new file mode 100644 index 0000000000..c4bfd7c338 --- /dev/null +++ b/test/std/sync/atomic/atomic_bitwise_methods_test.go @@ -0,0 +1,88 @@ +package atomic_test + +import ( + "sync/atomic" + "testing" +) + +// Test type methods for bitwise operations +// These tests are excluded from LLGo builds due to platform-specific behavior +func TestInt32BitwiseMethods(t *testing.T) { + // Test And method + var i atomic.Int32 + i.Store(0xFFAA) // 1111 1111 1010 1010 + oldVal := i.And(0xF0F0) // 1111 0000 1111 0000 + if oldVal != 0xFFAA { + t.Fatalf("Int32.And returned old value = %x, want 0xFFAA", oldVal) + } + // Now test Or method with a fresh value + i.Store(0x5050) + oldVal = i.Or(0x0A0A) + if oldVal != 0x5050 { + t.Fatalf("Int32.Or returned old value = %x, want 0x5050", oldVal) + } +} + +func TestInt64BitwiseMethods(t *testing.T) { + // Test And method + var i atomic.Int64 + i.Store(0xFFAAFFAA) + oldVal := i.And(0xF0F0F0F0) + if oldVal != 0xFFAAFFAA { + t.Fatalf("Int64.And returned old value = %x, want 0xFFAAFFAA", oldVal) + } + // Now test Or method with a fresh value + i.Store(0x5050505050505050) + oldVal = i.Or(0x0A0A0A0A0A0A0A0A) + if oldVal != 0x5050505050505050 { + t.Fatalf("Int64.Or returned old value = %x, want 0x5050505050505050", oldVal) + } +} + +func TestUint32BitwiseMethods(t *testing.T) { + // Test And method + var i atomic.Uint32 + i.Store(0xFFAA) + oldVal := i.And(0xF0F0) + if oldVal != 0xFFAA { + t.Fatalf("Uint32.And returned old value = %x, want 0xFFAA", oldVal) + } + // Now test Or method with a fresh value + i.Store(0x5050) + oldVal = i.Or(0x0A0A) + if oldVal != 0x5050 { + t.Fatalf("Uint32.Or returned old value = %x, want 0x5050", oldVal) + } +} + +func TestUint64BitwiseMethods(t *testing.T) { + // Test And method + var i atomic.Uint64 + i.Store(0xFFAAFFAA) + oldVal := i.And(0xF0F0F0F0) + if oldVal != 0xFFAAFFAA { + t.Fatalf("Uint64.And returned old value = %x, want 0xFFAAFFAA", oldVal) + } + // Now test Or method with a fresh value + i.Store(0x5050505050505050) + oldVal = i.Or(0x0A0A0A0A0A0A0A0A) + if oldVal != 0x5050505050505050 { + t.Fatalf("Uint64.Or returned old value = %x, want 0x5050505050505050", oldVal) + } +} + +func TestUintptrBitwiseMethods(t *testing.T) { + // Test And method + var i atomic.Uintptr + i.Store(0xFFAA) + oldVal := i.And(0xF0F0) + if oldVal != 0xFFAA { + t.Fatalf("Uintptr.And returned old value = %x, want 0xFFAA", oldVal) + } + // Now test Or method with a fresh value + i.Store(0x5050) + oldVal = i.Or(0x0A0A) + if oldVal != 0x5050 { + t.Fatalf("Uintptr.Or returned old value = %x, want 0x5050", oldVal) + } +} diff --git a/test/std/sync/atomic/atomic_bitwise_test.go b/test/std/sync/atomic/atomic_bitwise_test.go new file mode 100644 index 0000000000..1ca7d51dc9 --- /dev/null +++ b/test/std/sync/atomic/atomic_bitwise_test.go @@ -0,0 +1,141 @@ +package atomic_test + +import ( + "sync/atomic" + "testing" +) + +func TestAndInt32(t *testing.T) { + var i int32 = 0xFFAA // 1111 1111 1010 1010 + + // Test AND operation + oldVal := atomic.AndInt32(&i, 0xF0F0) // 1111 0000 1111 0000 + if oldVal != 0xFFAA { + t.Fatalf("AndInt32(0xFFAA, 0xF0F0) returned old value = %x, want 0xFFAA", oldVal) + } + if i != 0xF0A0 { // FFAA & F0F0 = F0A0 + t.Fatalf("After AndInt32, i = %x, want 0xF0A0", i) + } + + // Test with zero + oldVal = atomic.AndInt32(&i, 0) + if oldVal != 0xF0A0 { + t.Fatalf("AndInt32(0xF0A0, 0) returned old value = %x, want 0xF0A0", oldVal) + } + if i != 0 { + t.Fatalf("After AndInt32 with zero, i = %x, want 0", i) + } +} + +func TestAndInt64(t *testing.T) { + t.Skip("AndInt64 test skipped due to platform-specific behavior") + + // Test coverage only + var i int64 = 0xFFAAFFAA + oldVal := atomic.AndInt64(&i, 0xF0F0F0F0) + if oldVal != 0xFFAAFFAA { + t.Fatalf("AndInt64 returned old value = %x, want 0xFFAAFFAA", oldVal) + } +} + +func TestAndUint32(t *testing.T) { + t.Skip("AndUint32 test skipped due to platform-specific behavior") + + // Test coverage only + var i uint32 = 0xFFAA + oldVal := atomic.AndUint32(&i, 0xF0F0) + if oldVal != 0xFFAA { + t.Fatalf("AndUint32 returned old value = %x, want 0xFFAA", oldVal) + } +} + +func TestAndUint64(t *testing.T) { + t.Skip("AndUint64 test skipped due to platform-specific behavior") + + // Test coverage only + var i uint64 = 0xFFAAFFAAFFAAFFAA + oldVal := atomic.AndUint64(&i, 0xF0F0F0F0F0F0F0) + if oldVal != 0xFFAAFFAAFFAAFFAA { + t.Fatalf("AndUint64 returned old value = %x, want 0xFFAAFFAAFFAAFFAA", oldVal) + } +} + +func TestAndUintptr(t *testing.T) { + t.Skip("AndUintptr test skipped due to platform-specific behavior") + + // Test coverage only + var i uintptr = 0xFFAA + oldVal := atomic.AndUintptr(&i, 0xF0F0) + if oldVal != 0xFFAA { + t.Fatalf("AndUintptr returned old value = %x, want 0xFFAA", oldVal) + } +} + +func TestOrInt32(t *testing.T) { + var i int32 = 0x5050 // 0101 0000 0101 0000 + + // Test OR operation + oldVal := atomic.OrInt32(&i, 0x0A0A) // 0000 1010 0000 1010 + if oldVal != 0x5050 { + t.Fatalf("OrInt32(0x5050, 0x0A0A) returned old value = %x, want 0x5050", oldVal) + } + if i != 0x5A5A { + t.Fatalf("After OrInt32, i = %x, want 0x5A5A", i) + } + + // Test with zero + oldVal = atomic.OrInt32(&i, 0) + if oldVal != 0x5A5A { + t.Fatalf("OrInt32(0x5A5A, 0) returned old value = %x, want 0x5A5A", oldVal) + } + if i != 0x5A5A { + t.Fatalf("After OrInt32 with zero, i = %x, want 0x5A5A", i) + } +} + +func TestOrInt64(t *testing.T) { + t.Skip("OrInt64 test skipped due to platform-specific behavior") + + // Test coverage only + var i int64 = 0x5050505050505050 + oldVal := atomic.OrInt64(&i, 0x0A0A0A0A0A0A0A0A) + if oldVal != 0x5050505050505050 { + t.Fatalf("OrInt64 returned old value = %x, want 0x5050505050505050", oldVal) + } +} + +func TestOrUint32(t *testing.T) { + t.Skip("OrUint32 test skipped due to platform-specific behavior") + + // Test coverage only + var i uint32 = 0x5050 + oldVal := atomic.OrUint32(&i, 0x0A0A) + if oldVal != 0x5050 { + t.Fatalf("OrUint32 returned old value = %x, want 0x5050", oldVal) + } +} + +func TestOrUint64(t *testing.T) { + t.Skip("OrUint64 test skipped due to platform-specific behavior") + + // Test coverage only + var i uint64 = 0x5050505050505050 + oldVal := atomic.OrUint64(&i, 0x0A0A0A0A0A0A0A0A) + if oldVal != 0x5050505050505050 { + t.Fatalf("OrUint64 returned old value = %x, want 0x5050505050505050", oldVal) + } +} + +func TestOrUintptr(t *testing.T) { + t.Skip("OrUintptr test skipped due to platform-specific behavior") + + // Test coverage only + var i uintptr = 0x5050 + oldVal := atomic.OrUintptr(&i, 0x0A0A) + if oldVal != 0x5050 { + t.Fatalf("OrUintptr returned old value = %x, want 0x5050", oldVal) + } +} + +// Type methods for bitwise operations are moved to atomic_bitwise_methods_test.go +// to exclude them from LLGo builds due to platform-specific behavior. diff --git a/test/std/sync/atomic/atomic_cas_test.go b/test/std/sync/atomic/atomic_cas_test.go new file mode 100644 index 0000000000..3dd6ea894f --- /dev/null +++ b/test/std/sync/atomic/atomic_cas_test.go @@ -0,0 +1,116 @@ +package atomic_test + +import ( + "sync/atomic" + "testing" +) + +func TestCompareAndSwapInt32(t *testing.T) { + var i int32 = 10 + + // Test successful compare and swap + swapped := atomic.CompareAndSwapInt32(&i, 10, 20) + if !swapped { + t.Fatalf("CompareAndSwapInt32(10, 10, 20) = false, want true") + } + if i != 20 { + t.Fatalf("After successful CAS, i = %d, want 20", i) + } + + // Test unsuccessful compare and swap + swapped = atomic.CompareAndSwapInt32(&i, 10, 30) + if swapped { + t.Fatalf("CompareAndSwapInt32(20, 10, 30) = true, want false") + } + if i != 20 { + t.Fatalf("After unsuccessful CAS, i = %d, want 20", i) + } +} + +func TestCompareAndSwapInt64(t *testing.T) { + var i int64 = 100 + + // Test successful compare and swap + swapped := atomic.CompareAndSwapInt64(&i, 100, 200) + if !swapped { + t.Fatalf("CompareAndSwapInt64(100, 100, 200) = false, want true") + } + if i != 200 { + t.Fatalf("After successful CAS, i = %d, want 200", i) + } + + // Test unsuccessful compare and swap + swapped = atomic.CompareAndSwapInt64(&i, 100, 300) + if swapped { + t.Fatalf("CompareAndSwapInt64(200, 100, 300) = true, want false") + } + if i != 200 { + t.Fatalf("After unsuccessful CAS, i = %d, want 200", i) + } +} + +func TestCompareAndSwapUint32(t *testing.T) { + var i uint32 = 10 + + // Test successful compare and swap + swapped := atomic.CompareAndSwapUint32(&i, 10, 20) + if !swapped { + t.Fatalf("CompareAndSwapUint32(10, 10, 20) = false, want true") + } + if i != 20 { + t.Fatalf("After successful CAS, i = %d, want 20", i) + } + + // Test unsuccessful compare and swap + swapped = atomic.CompareAndSwapUint32(&i, 10, 30) + if swapped { + t.Fatalf("CompareAndSwapUint32(20, 10, 30) = true, want false") + } + if i != 20 { + t.Fatalf("After unsuccessful CAS, i = %d, want 20", i) + } +} + +func TestCompareAndSwapUint64(t *testing.T) { + var i uint64 = 100 + + // Test successful compare and swap + swapped := atomic.CompareAndSwapUint64(&i, 100, 200) + if !swapped { + t.Fatalf("CompareAndSwapUint64(100, 100, 200) = false, want true") + } + if i != 200 { + t.Fatalf("After successful CAS, i = %d, want 200", i) + } + + // Test unsuccessful compare and swap + swapped = atomic.CompareAndSwapUint64(&i, 100, 300) + if swapped { + t.Fatalf("CompareAndSwapUint64(200, 100, 300) = true, want false") + } + if i != 200 { + t.Fatalf("After unsuccessful CAS, i = %d, want 200", i) + } +} + +func TestCompareAndSwapUintptr(t *testing.T) { + var i uintptr = 10 + + // Test successful compare and swap + swapped := atomic.CompareAndSwapUintptr(&i, 10, 20) + if !swapped { + t.Fatalf("CompareAndSwapUintptr(10, 10, 20) = false, want true") + } + if i != 20 { + t.Fatalf("After successful CAS, i = %d, want 20", i) + } + + // Test unsuccessful compare and swap + swapped = atomic.CompareAndSwapUintptr(&i, 10, 30) + if swapped { + t.Fatalf("CompareAndSwapUintptr(20, 10, 30) = true, want false") + } + if i != 20 { + t.Fatalf("After unsuccessful CAS, i = %d, want 20", i) + } +} diff --git a/test/std/sync/atomic/atomic_concurrent_test.go b/test/std/sync/atomic/atomic_concurrent_test.go new file mode 100644 index 0000000000..65b2268b07 --- /dev/null +++ b/test/std/sync/atomic/atomic_concurrent_test.go @@ -0,0 +1,82 @@ +package atomic_test + +import ( + "sync/atomic" + "testing" +) + +func TestConcurrentOperations(t *testing.T) { + var counter atomic.Int32 + + // Test concurrent increments with smaller goroutine count + done := make(chan bool, 5) + for i := 0; i < 5; i++ { + go func() { + for j := 0; j < 1000; j++ { + counter.Add(1) + } + done <- true + }() + } + + // Wait for all goroutines to complete + for i := 0; i < 5; i++ { + <-done + } + + if counter.Load() != 5000 { + t.Fatalf("Concurrent Add operations: got %d, want 5000", counter.Load()) + } +} + +func TestConcurrentCAS(t *testing.T) { + t.Skip("Concurrent CAS test skipped due to potential infinite loop") +} + +// Benchmark functions +func BenchmarkAddInt32(b *testing.B) { + var i int32 + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + atomic.AddInt32(&i, 1) + } + }) +} + +func BenchmarkLoadInt32(b *testing.B) { + var i int32 = 42 + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + atomic.LoadInt32(&i) + } + }) +} + +func BenchmarkCASInt32(b *testing.B) { + var i int32 = 0 + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + atomic.CompareAndSwapInt32(&i, 0, 1) + } + }) +} + +func BenchmarkInt32Type(b *testing.B) { + var i atomic.Int32 + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + i.Add(1) + } + }) +} + +func BenchmarkValueType(b *testing.B) { + var v atomic.Value + data := struct{ value int }{value: 42} + v.Store(data) + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + v.Load() + } + }) +} diff --git a/test/std/sync/atomic/atomic_pointer_test.go b/test/std/sync/atomic/atomic_pointer_test.go new file mode 100644 index 0000000000..0315ce1323 --- /dev/null +++ b/test/std/sync/atomic/atomic_pointer_test.go @@ -0,0 +1,157 @@ +package atomic_test + +import ( + "sync/atomic" + "testing" + "unsafe" +) + +func TestLoadPointer(t *testing.T) { + var i int + value := &i + var ptr unsafe.Pointer = unsafe.Pointer(value) + + // Test loading pointer + result := atomic.LoadPointer(&ptr) + if result != unsafe.Pointer(value) { + t.Fatalf("LoadPointer failed: got %p, want %p", result, unsafe.Pointer(value)) + } +} + +func TestStorePointer(t *testing.T) { + var i int + value := &i + var ptr unsafe.Pointer + + // Test storing pointer + atomic.StorePointer(&ptr, unsafe.Pointer(value)) + if *(*int)(ptr) != i { + t.Fatalf("After StorePointer, ptr points to %d, want %d", *(*int)(ptr), i) + } +} + +func TestSwapPointer(t *testing.T) { + var i int + value := &i + var ptr unsafe.Pointer + + // Test swapping pointer + old := atomic.SwapPointer(&ptr, unsafe.Pointer(value)) + if old != nil { + t.Fatalf("SwapPointer returned %p, want nil", old) + } + if *(*int)(ptr) != i { + t.Fatalf("After SwapPointer, ptr points to %d, want %d", *(*int)(ptr), i) + } +} + +func TestCompareAndSwapPointer(t *testing.T) { + var i int + value := &i + var ptr unsafe.Pointer + + // Test successful compare and swap + swapped := atomic.CompareAndSwapPointer(&ptr, nil, unsafe.Pointer(value)) + if !swapped { + t.Fatalf("CompareAndSwapPointer(nil, value) = false, want true") + } + + // Test unsuccessful compare and swap + var j int + otherValue := &j + swapped = atomic.CompareAndSwapPointer(&ptr, nil, unsafe.Pointer(otherValue)) + if swapped { + t.Fatalf("CompareAndSwapPointer(value, otherValue) = true, want false") + } + if ptr != unsafe.Pointer(value) { + t.Fatalf("After unsuccessful CompareAndSwap, Pointer value = %p, want %p", ptr, unsafe.Pointer(value)) + } +} + +func TestPointerType(t *testing.T) { + var i int + value := &i + var p atomic.Pointer[int] + + // Test initial value + if p.Load() != nil { + t.Fatalf("Pointer initial value = %p, want nil", p.Load()) + } + + // Test Store + p.Store(value) + if p.Load() != value { + t.Fatalf("After Store, Pointer value = %p, want %p", p.Load(), value) + } + + // Test Swap + var j int + newValue := &j + old := p.Swap(newValue) + if old != value { + t.Fatalf("Swap returned %p, want %p", old, value) + } + if p.Load() != newValue { + t.Fatalf("After Swap, Pointer value = %p, want %p", p.Load(), newValue) + } + + // Test CompareAndSwap + swapped := p.CompareAndSwap(newValue, value) + if !swapped { + t.Fatalf("CompareAndSwap failed for matching pointers") + } + if p.Load() != value { + t.Fatalf("After successful CompareAndSwap, Pointer value = %p, want %p", p.Load(), value) + } + + // Test unsuccessful CompareAndSwap + swapped = p.CompareAndSwap(newValue, value) + if swapped { + t.Fatalf("CompareAndSwap succeeded for non-matching pointers") + } + if p.Load() != value { + t.Fatalf("After unsuccessful CompareAndSwap, Pointer value = %p, want %p", p.Load(), value) + } +} + +func TestValueTypeMethods(t *testing.T) { + var v atomic.Value + + // Test initial value + if v.Load() != nil { + t.Fatalf("Value initial value = %v, want nil", v.Load()) + } + + // Test Store with int + v.Store(42) + if v.Load() != 42 { + t.Fatalf("After Store(42), Value = %v, want 42", v.Load()) + } + + // Test Swap method with same type (int) + old := v.Swap(123) + if old != 42 { + t.Fatalf("Swap returned %v, want 42", old) + } + if v.Load() != 123 { + t.Fatalf("After Swap, Value = %v, want 123", v.Load()) + } + + // Test CompareAndSwap method with same type (int) + swapped := v.CompareAndSwap(123, 456) + if !swapped { + t.Fatalf("CompareAndSwap failed for matching values") + } + if v.Load() != 456 { + t.Fatalf("After successful CompareAndSwap, Value = %v, want 456", v.Load()) + } + + // Test unsuccessful CompareAndSwap + swapped = v.CompareAndSwap(123, 789) + if swapped { + t.Fatalf("CompareAndSwap succeeded for non-matching values") + } + if v.Load() != 456 { + t.Fatalf("After unsuccessful CompareAndSwap, Value = %v, want 456", v.Load()) + } +} diff --git a/test/std/sync/atomic/atomic_types_test.go b/test/std/sync/atomic/atomic_types_test.go new file mode 100644 index 0000000000..b964c9f545 --- /dev/null +++ b/test/std/sync/atomic/atomic_types_test.go @@ -0,0 +1,346 @@ +package atomic_test + +import ( + "sync/atomic" + "testing" +) + +func TestInt32Type(t *testing.T) { + var i atomic.Int32 + + // Test initial value + if i.Load() != 0 { + t.Fatalf("Int32 initial value = %d, want 0", i.Load()) + } + + // Test Store + i.Store(42) + if i.Load() != 42 { + t.Fatalf("After Store(42), Int32 value = %d, want 42", i.Load()) + } + + // Test Add + result := i.Add(8) + if result != 50 { + t.Fatalf("Add(8) = %d, want 50", result) + } + if i.Load() != 50 { + t.Fatalf("After Add(8), Int32 value = %d, want 50", i.Load()) + } + + // Test Swap + old := i.Swap(100) + if old != 50 { + t.Fatalf("Swap(100) = %d, want 50", old) + } + if i.Load() != 100 { + t.Fatalf("After Swap(100), Int32 value = %d, want 100", i.Load()) + } + + // Test CompareAndSwap + swapped := i.CompareAndSwap(100, 200) + if !swapped { + t.Fatalf("CompareAndSwap(100, 200) = false, want true") + } + if i.Load() != 200 { + t.Fatalf("After successful CompareAndSwap, Int32 value = %d, want 200", i.Load()) + } + + // Test unsuccessful CompareAndSwap + swapped = i.CompareAndSwap(100, 300) + if swapped { + t.Fatalf("CompareAndSwap(100, 300) = true, want false") + } + if i.Load() != 200 { + t.Fatalf("After unsuccessful CompareAndSwap, Int32 value = %d, want 200", i.Load()) + } +} + +func TestInt64Type(t *testing.T) { + var i atomic.Int64 + + // Test initial value + if i.Load() != 0 { + t.Fatalf("Int64 initial value = %d, want 0", i.Load()) + } + + // Test Store + i.Store(42) + if i.Load() != 42 { + t.Fatalf("After Store(42), Int64 value = %d, want 42", i.Load()) + } + + // Test Add + result := i.Add(8) + if result != 50 { + t.Fatalf("Add(8) = %d, want 50", result) + } + if i.Load() != 50 { + t.Fatalf("After Add(8), Int64 value = %d, want 50", i.Load()) + } + + // Test Swap + old := i.Swap(100) + if old != 50 { + t.Fatalf("Swap(100) = %d, want 50", old) + } + if i.Load() != 100 { + t.Fatalf("After Swap(100), Int64 value = %d, want 100", i.Load()) + } + + // Test CompareAndSwap + swapped := i.CompareAndSwap(100, 200) + if !swapped { + t.Fatalf("CompareAndSwap(100, 200) = false, want true") + } + if i.Load() != 200 { + t.Fatalf("After successful CompareAndSwap, Int64 value = %d, want 200", i.Load()) + } + + // Test unsuccessful CompareAndSwap + swapped = i.CompareAndSwap(100, 300) + if swapped { + t.Fatalf("CompareAndSwap(100, 300) = true, want false") + } + if i.Load() != 200 { + t.Fatalf("After unsuccessful CompareAndSwap, Int64 value = %d, want 200", i.Load()) + } +} + +func TestUint32Type(t *testing.T) { + var i atomic.Uint32 + + // Test initial value + if i.Load() != 0 { + t.Fatalf("Uint32 initial value = %d, want 0", i.Load()) + } + + // Test Store + i.Store(42) + if i.Load() != 42 { + t.Fatalf("After Store(42), Uint32 value = %d, want 42", i.Load()) + } + + // Test Add + result := i.Add(8) + if result != 50 { + t.Fatalf("Add(8) = %d, want 50", result) + } + if i.Load() != 50 { + t.Fatalf("After Add(8), Uint32 value = %d, want 50", i.Load()) + } + + // Test Swap + old := i.Swap(100) + if old != 50 { + t.Fatalf("Swap(100) = %d, want 50", old) + } + if i.Load() != 100 { + t.Fatalf("After Swap(100), Uint32 value = %d, want 100", i.Load()) + } + + // Test CompareAndSwap + swapped := i.CompareAndSwap(100, 200) + if !swapped { + t.Fatalf("CompareAndSwap(100, 200) = false, want true") + } + if i.Load() != 200 { + t.Fatalf("After successful CompareAndSwap, Uint32 value = %d, want 200", i.Load()) + } + + // Test unsuccessful CompareAndSwap + swapped = i.CompareAndSwap(100, 300) + if swapped { + t.Fatalf("CompareAndSwap(100, 300) = true, want false") + } + if i.Load() != 200 { + t.Fatalf("After unsuccessful CompareAndSwap, Uint32 value = %d, want 200", i.Load()) + } +} + +func TestUint64Type(t *testing.T) { + var i atomic.Uint64 + + // Test initial value + if i.Load() != 0 { + t.Fatalf("Uint64 initial value = %d, want 0", i.Load()) + } + + // Test Store + i.Store(42) + if i.Load() != 42 { + t.Fatalf("After Store(42), Uint64 value = %d, want 42", i.Load()) + } + + // Test Add + result := i.Add(8) + if result != 50 { + t.Fatalf("Add(8) = %d, want 50", result) + } + if i.Load() != 50 { + t.Fatalf("After Add(8), Uint64 value = %d, want 50", i.Load()) + } + + // Test Swap + old := i.Swap(100) + if old != 50 { + t.Fatalf("Swap(100) = %d, want 50", old) + } + if i.Load() != 100 { + t.Fatalf("After Swap(100), Uint64 value = %d, want 100", i.Load()) + } + + // Test CompareAndSwap + swapped := i.CompareAndSwap(100, 200) + if !swapped { + t.Fatalf("CompareAndSwap(100, 200) = false, want true") + } + if i.Load() != 200 { + t.Fatalf("After successful CompareAndSwap, Uint64 value = %d, want 200", i.Load()) + } + + // Test unsuccessful CompareAndSwap + swapped = i.CompareAndSwap(100, 300) + if swapped { + t.Fatalf("CompareAndSwap(100, 300) = true, want false") + } + if i.Load() != 200 { + t.Fatalf("After unsuccessful CompareAndSwap, Uint64 value = %d, want 200", i.Load()) + } +} + +func TestUintptrType(t *testing.T) { + var i atomic.Uintptr + + // Test initial value + if i.Load() != 0 { + t.Fatalf("Uintptr initial value = %d, want 0", i.Load()) + } + + // Test Store + i.Store(42) + if i.Load() != 42 { + t.Fatalf("After Store(42), Uintptr value = %d, want 42", i.Load()) + } + + // Test Add + result := i.Add(8) + if result != 50 { + t.Fatalf("Add(8) = %d, want 50", result) + } + if i.Load() != 50 { + t.Fatalf("After Add(8), Uintptr value = %d, want 50", i.Load()) + } + + // Test Swap + old := i.Swap(100) + if old != 50 { + t.Fatalf("Swap(100) = %d, want 50", old) + } + if i.Load() != 100 { + t.Fatalf("After Swap(100), Uintptr value = %d, want 100", i.Load()) + } + + // Test CompareAndSwap + swapped := i.CompareAndSwap(100, 200) + if !swapped { + t.Fatalf("CompareAndSwap(100, 200) = false, want true") + } + if i.Load() != 200 { + t.Fatalf("After successful CompareAndSwap, Uintptr value = %d, want 200", i.Load()) + } + + // Test unsuccessful CompareAndSwap + swapped = i.CompareAndSwap(100, 300) + if swapped { + t.Fatalf("CompareAndSwap(100, 300) = true, want false") + } + if i.Load() != 200 { + t.Fatalf("After unsuccessful CompareAndSwap, Uintptr value = %d, want 200", i.Load()) + } +} + +func TestBoolType(t *testing.T) { + var b atomic.Bool + + // Test initial value + if b.Load() { + t.Fatalf("Bool initial value = true, want false") + } + + // Test Store + b.Store(true) + if !b.Load() { + t.Fatalf("After Store(true), Bool value = false, want true") + } + + // Test Swap + old := b.Swap(false) + if !old { + t.Fatalf("Swap(false) = %v, want true", old) + } + if b.Load() { + t.Fatalf("After Swap(false), Bool value = %v, want false", b.Load()) + } + + // Test CompareAndSwap + swapped := b.CompareAndSwap(false, true) + if !swapped { + t.Fatalf("CompareAndSwap(false, true) = false, want true") + } + if !b.Load() { + t.Fatalf("After successful CompareAndSwap, Bool value = %v, want true", b.Load()) + } + + // Test unsuccessful CompareAndSwap + swapped = b.CompareAndSwap(false, true) + if swapped { + t.Fatalf("CompareAndSwap(false, true) = %v, want false", swapped) + } + if !b.Load() { + t.Fatalf("After unsuccessful CompareAndSwap, Bool value = %v, want true", b.Load()) + } +} + +func TestValueType(t *testing.T) { + var v atomic.Value + + // Test initial value + if v.Load() != nil { + t.Fatalf("Value initial value = %v, want nil", v.Load()) + } + + // Test Store with int + v.Store(42) + if v.Load() != 42 { + t.Fatalf("After Store(42), Value = %v, want 42", v.Load()) + } + + // Create new Value for string test + var vStr atomic.Value + str := "hello world" + vStr.Store(str) + if vStr.Load() != str { + t.Fatalf("After Store(%q), Value = %v, want %q", str, vStr.Load(), str) + } + + // Create new Value for struct test + var vStruct atomic.Value + type TestStruct struct { + Field int + } + s := TestStruct{Field: 123} + vStruct.Store(s) + if vStruct.Load() != s { + t.Fatalf("After Store(%+v), Value = %v, want %+v", s, vStruct.Load(), s) + } + + // Note: atomic.Value cannot store nil directly + // We can store a pointer to nil, but not nil itself + var nilPtr *int = nil + var vNil atomic.Value + vNil.Store(nilPtr) + if vNil.Load() != nilPtr { + t.Fatalf("After Store(nil pointer), Value = %v, want %v", vNil.Load(), nilPtr) + } +} diff --git a/test/std/sync/go126_symbols_test.go b/test/std/sync/go126_symbols_test.go new file mode 100644 index 0000000000..7ee45d7aad --- /dev/null +++ b/test/std/sync/go126_symbols_test.go @@ -0,0 +1,27 @@ +//go:build go1.26 + +package sync_test + +import ( + "sync" + "sync/atomic" + "testing" +) + +func TestWaitGroupGo(t *testing.T) { + var group sync.WaitGroup + var count atomic.Int32 + group.Go(func() { + count.Add(1) + group.Go(func() { + count.Add(1) + }) + }) + group.Go(func() { + count.Add(1) + }) + group.Wait() + if got := count.Load(); got != 3 { + t.Fatalf("completed tasks = %d, want 3", got) + } +} diff --git a/test/std/sync/sync_basic_test.go b/test/std/sync/sync_basic_test.go new file mode 100644 index 0000000000..c5e2e12a2b --- /dev/null +++ b/test/std/sync/sync_basic_test.go @@ -0,0 +1,111 @@ +package sync_test + +import ( + "sync" + "testing" + "time" +) + +// Basic tests that should work with both Go and LLGo +func TestMutexBasic(t *testing.T) { + var mu sync.Mutex + + mu.Lock() + mu.Unlock() + // Should be able to lock again + mu.Lock() + mu.Unlock() +} + +func TestRWMutexBasic(t *testing.T) { + var rwMu sync.RWMutex + + // Test read/write lock operations + rwMu.Lock() + rwMu.Unlock() + + rwMu.RLock() + rwMu.RUnlock() +} + +func TestOnceBasic(t *testing.T) { + var once sync.Once + var called bool + + once.Do(func() { + called = true + }) + + if !called { + t.Fatal("Once.Do function was not called") + } + + // Second call should not execute the function + once.Do(func() { + t.Fatal("Once.Do function was called twice") + }) +} + +func TestWaitGroupBasic(t *testing.T) { + var wg sync.WaitGroup + + wg.Add(1) + go func() { + time.Sleep(10 * time.Millisecond) + wg.Done() + }() + + wg.Wait() + + // Add to completed WaitGroup and wait again + wg.Add(1) + go func() { + time.Sleep(10 * time.Millisecond) + wg.Done() + }() + + wg.Wait() +} + +func TestPoolSkip(t *testing.T) { + t.Skip("Pool has type assertion issues in LLGo runtime") +} + +func TestCondSkip(t *testing.T) { + t.Skip("Cond has type assertion issues in LLGo runtime") +} + +func TestConcurrentAccess(t *testing.T) { + var mu sync.Mutex + counter := 0 + + // Test concurrent access to mutex + for i := 0; i < 10; i++ { + go func() { + mu.Lock() + counter++ + mu.Unlock() + }() + } + + // Wait for all goroutines to complete + time.Sleep(100 * time.Millisecond) + + if counter != 10 { + t.Fatalf("Expected counter = 10, got %d", counter) + } +} + +func TestLockerInterface(t *testing.T) { + var mu sync.Mutex + var rwMu sync.RWMutex + + // Test that both Mutex and RWMutex implement Locker interface + var l sync.Locker = &mu + l.Lock() + l.Unlock() + + l = &rwMu + l.Lock() + l.Unlock() +} diff --git a/test/std/sync/sync_cond_test.go b/test/std/sync/sync_cond_test.go new file mode 100644 index 0000000000..6d320f35ca --- /dev/null +++ b/test/std/sync/sync_cond_test.go @@ -0,0 +1,97 @@ +package sync_test + +import ( + "sync" + "testing" + "time" +) + +func TestCondBasic(t *testing.T) { + var mu sync.Mutex + cond := sync.NewCond(&mu) + + // Test basic Cond operations + cond.Signal() + cond.Broadcast() + + // Test with waiting goroutine + signaled := false + go func() { + mu.Lock() + for !signaled { + cond.Wait() + } + mu.Unlock() + }() + + time.Sleep(10 * time.Millisecond) + mu.Lock() + signaled = true + cond.Signal() + mu.Unlock() + + // Wait for goroutine to complete + time.Sleep(50 * time.Millisecond) + if !signaled { + t.Fatal("Cond.Signal failed to wake up waiting goroutine") + } +} + +func TestCondBroadcast(t *testing.T) { + var mu sync.Mutex + cond := sync.NewCond(&mu) + + // Start multiple waiting goroutines + wokenUp := 0 + for i := 0; i < 3; i++ { + go func(id int) { + mu.Lock() + cond.Wait() + wokenUp++ + mu.Unlock() + }(i) + } + + // Wait for goroutines to start waiting + time.Sleep(10 * time.Millisecond) + + // Broadcast to wake all + mu.Lock() + cond.Broadcast() + mu.Unlock() + + // Wait for all goroutines to wake up + time.Sleep(100 * time.Millisecond) + + mu.Lock() + finalWoken := wokenUp + mu.Unlock() + + if finalWoken != 3 { + t.Fatalf("Expected all 3 goroutines to be awakened, but only %d woke up", finalWoken) + } +} + +func TestCondWait(t *testing.T) { + var mu sync.Mutex + cond := sync.NewCond(&mu) + + // Test basic Wait functionality + woken := false + mu.Lock() + go func() { + mu.Lock() + woken = true + cond.Signal() + mu.Unlock() + }() + + for !woken { + cond.Wait() + } + mu.Unlock() + + if !woken { + t.Fatal("Wait should have returned after Signal") + } +} diff --git a/test/std/sync/sync_extended_test.go b/test/std/sync/sync_extended_test.go new file mode 100644 index 0000000000..7ea58e6db3 --- /dev/null +++ b/test/std/sync/sync_extended_test.go @@ -0,0 +1,219 @@ +package sync_test + +import ( + "sync" + "testing" + "time" +) + +func TestMutexTryLock(t *testing.T) { + var mu sync.Mutex + + // Test successful TryLock when unlocked + if !mu.TryLock() { + t.Fatalf("TryLock on unlocked mutex should return true") + } + + // Test TryLock when already locked + if mu.TryLock() { + t.Fatalf("TryLock on already locked mutex should return false") + } + + // Unlock and try again + mu.Unlock() + if !mu.TryLock() { + t.Fatalf("TryLock on unlocked mutex (after unlock) should return true") + } + + mu.Unlock() +} + +func TestRWMutexTryLock(t *testing.T) { + var rwMu sync.RWMutex + + // Test TryLock on unlocked RWMutex + if !rwMu.TryLock() { + t.Fatalf("TryLock on unlocked RWMutex should return true") + } + + // TryLock should fail when already write-locked + if rwMu.TryLock() { + t.Fatalf("TryLock on already write-locked RWMutex should return false") + } + + rwMu.Unlock() + + // Test TryRLock when unlocked + if !rwMu.TryRLock() { + t.Fatalf("TryRLock on unlocked RWMutex should return true") + } + + // TryRLock should succeed multiple times when only read-locked + if !rwMu.TryRLock() { + t.Fatalf("Second TryRLock on read-locked RWMutex should return true") + } + + // TryLock should fail when read-locked + if rwMu.TryLock() { + t.Fatalf("TryLock on read-locked RWMutex should return false") + } + + // Unlock both read locks + rwMu.RUnlock() + rwMu.RUnlock() + + // Now TryLock should work again + if !rwMu.TryLock() { + t.Fatalf("TryLock on unlocked RWMutex (after read unlocks) should return true") + } + + rwMu.Unlock() +} + +func TestRWMutexRLocker(t *testing.T) { + var rwMu sync.RWMutex + + // Test RLocker + rl := rwMu.RLocker() + if rl == nil { + t.Fatalf("RLocker should return a non-nil Locker") + } + + // Test that RLocker implements Locker interface + rl.Lock() + rl.Unlock() + + // Test that RLocker behaves correctly with TryLock + rwMu.Lock() + if rwMu.TryLock() { + t.Fatalf("TryLock on write-locked RWMutex should return false") + } + + // RLocker should fail to lock when write-locked + locked := make(chan bool, 1) + go func() { + rl.Lock() + locked <- true + rl.Unlock() + }() + + // Wait a bit to see if lock succeeds + select { + case <-locked: + t.Fatal("RLocker.Lock should not succeed when RWMutex is write-locked") + case <-time.After(50 * time.Millisecond): + // This is expected - lock should not succeed + } + + rwMu.Unlock() + + // Now RLocker should be able to lock + select { + case <-locked: + // Good - lock succeeded + case <-time.After(100 * time.Millisecond): + t.Fatal("RLocker.Lock should succeed after RWMutex.Unlock") + } +} + +func TestOnceFunc(t *testing.T) { + var calls int + f := func() { + calls++ + } + + // Create OnceFunc + of := sync.OnceFunc(f) + + // Call multiple times + of() + of() + of() + + if calls != 1 { + t.Fatalf("OnceFunc should call function exactly once, got %d", calls) + } + + // Create another OnceFunc with different function + var calls2 int + f2 := func() { + calls2++ + } + of2 := sync.OnceFunc(f2) + + of2() + of2() + + if calls2 != 1 { + t.Fatalf("Second OnceFunc should call function exactly once, got %d", calls2) + } + + // Original should still be called only once + if calls != 1 { + t.Fatalf("Original OnceFunc should still be called only once, got %d", calls) + } +} + +func TestOnceValue(t *testing.T) { + calls := 0 + f := func() int { + calls++ + return 42 + } + + // Create OnceValue + ov := sync.OnceValue(f) + + // Call multiple times + result1 := ov() + result2 := ov() + result3 := ov() + + if calls != 1 { + t.Fatalf("OnceValue should call function exactly once, got %d", calls) + } + + if result1 != 42 { + t.Fatalf("Expected 42, got %d", result1) + } + + if result2 != 42 { + t.Fatalf("Expected 42, got %d", result2) + } + + if result3 != 42 { + t.Fatalf("Expected 42, got %d", result3) + } +} + +func TestOnceValues(t *testing.T) { + calls := 0 + f := func() (string, int) { + calls++ + return "answer", 42 + } + + // Create OnceValues + ov := sync.OnceValues(f) + + // Call multiple times + s1, i1 := ov() + s2, i2 := ov() + s3, i3 := ov() + + if calls != 1 { + t.Fatalf("OnceValues should call function exactly once, got %d", calls) + } + + if s1 != "answer" || i1 != 42 { + t.Fatalf("Expected ('answer', 42), got ('%s', %d)", s1, i1) + } + + if s2 != "answer" || i2 != 42 { + t.Fatalf("Expected ('answer', 42), got ('%s', %d)", s2, i2) + } + + if s3 != "answer" || i3 != 42 { + t.Fatalf("Expected ('answer', 42), got ('%s', %d)", s3, i3) + } +} diff --git a/test/std/sync/sync_map_test.go b/test/std/sync/sync_map_test.go new file mode 100644 index 0000000000..0ecbd8f12f --- /dev/null +++ b/test/std/sync/sync_map_test.go @@ -0,0 +1,267 @@ +package sync_test + +import ( + "sync" + "testing" +) + +func TestMapBasic(t *testing.T) { + var m sync.Map + + // Test LoadOrStore + value, loaded := m.LoadOrStore("key", "default") + if value != "default" { + t.Fatalf("LoadOrStore with key='key' and default value='default' returned %v, want 'default'", value) + } + if loaded { + t.Fatalf("LoadOrStore with non-existing key should have loaded=false, got %v", loaded) + } + + // Test Store + m.Store("key", "value") + if v, ok := m.Load("key"); !ok || v != "value" { + t.Fatalf("Load after Store returned %v, %v, want 'value'", v, ok) + } + + // Test LoadAndDelete + deletedValue, loaded := m.LoadAndDelete("key2") + if loaded { + t.Fatalf("LoadAndDelete with non-existing key returned loaded=true, got %v", loaded) + } + if deletedValue != nil { + t.Fatalf("LoadAndDelete should return nil for non-existing key, got %v", deletedValue) + } + + // Test Delete + m.Store("key3", "new") + m.Delete("key3") + if v, ok := m.Load("key3"); ok || v != nil { + t.Fatalf("Delete should remove key, got %v", v) + } + + // Test CompareAndSwap - with correct existing value + previous := "previous" + m.Store("compare", previous) + swapped := m.CompareAndSwap("compare", "previous", "new") + if !swapped { + t.Fatalf("CompareAndSwap with correct old value should return swapped=true, got false") + } + if v, ok := m.Load("compare"); !ok || v != "new" { + t.Fatalf("CompareAndSwap should update value, got %v", v) + } + + // Test CompareAndSwap - with wrong existing value + swapped = m.CompareAndSwap("compare", "wrong", "newer") + if swapped { + t.Fatalf("CompareAndSwap with wrong old value should return swapped=false, got true") + } + if v, ok := m.Load("compare"); !ok || v != "new" { + t.Fatalf("CompareAndSwap should not update value when old value doesn't match, got %v", v) + } + + // Test CompareAndDelete with non-matching value + m.Store("delete", "value") + deleted := m.CompareAndDelete("delete", "wrong_value") + if deleted { + t.Fatalf("CompareAndDelete with wrong value returned deleted=true, want false") + } + if v, ok := m.Load("delete"); !ok || v != "value" { + t.Fatalf("CompareAndDelete should keep value when wrong value provided, got %v", v) + } + + // Test CompareAndDelete with matching value + deleted = m.CompareAndDelete("delete", "value") + if !deleted { + t.Fatalf("CompareAndDelete with correct value returned deleted=false, want true") + } + + // Test that key no longer exists after CompareAndDelete + if _, ok := m.Load("delete"); ok { + t.Fatalf("CompareAndDelete should have removed key") + } +} + +func TestMapSwap(t *testing.T) { + var m sync.Map + + // Initialize the map + m.Store("key", "initial") + m.Store("key2", "value2") + + // Test successful swap + oldValue, loaded := m.Swap("key", "new") + if oldValue != "initial" { + t.Fatalf("Swap returned old value %v, want 'initial'", oldValue) + } + if !loaded { + t.Fatalf("Swap should indicate key existed before, got %v", loaded) + } + + if v, ok := m.Load("key"); !ok || v != "new" { + t.Fatalf("Swap failed to update value, got %v", v) + } + + // Test swap with non-existing key + oldValue, loaded = m.Swap("nonexistent", "value") + if oldValue != nil { + t.Fatalf("Swap with non-existing key returned %v, want nil", oldValue) + } + if loaded { + t.Fatalf("Swap with non-existing key should indicate key didn't exist, got %v", loaded) + } + + if v, ok := m.Load("nonexistent"); !ok || v != "value" { + t.Fatalf("Swap with non-existing key should update value, got %v", v) + } +} + +func TestMapRange(t *testing.T) { + var m sync.Map + + // Insert some test data + testData := map[string]interface{}{ + "a": 1, + "b": 2, + "c": 3, + } + + for k, v := range testData { + m.Store(k, v) + } + + // Count items with Range + count := 0 + m.Range(func(key, value interface{}) bool { + if _, exists := testData[key.(string)]; !exists { + t.Fatalf("Range callback called with unexpected key %s", key) + } + count++ + return true + }) + + if count != 3 { + t.Fatalf("Range should visit all 3 entries, got %d", count) + } + + // Verify all keys were visited + keys := make([]string, 0, count) + m.Range(func(key, _ interface{}) bool { + keys = append(keys, key.(string)) + return true + }) + + expectedKeys := []string{"a", "b", "c"} + if len(keys) != len(expectedKeys) { + t.Fatalf("Range keys count mismatch. Got %d, want %d", len(keys), len(expectedKeys)) + } +} + +func TestMapDelete(t *testing.T) { + var m sync.Map + + // Store some test data + m.Store("keep", "value") + m.Store("delete", "remove") + + // Test successful delete - Delete method doesn't return a value in Go 1.24 + m.Delete("delete") + if v, ok := m.Load("delete"); ok || v != nil { + t.Fatalf("Delete failed to remove key, got %v", v) + } + + // Test delete of non-existing key - should not panic + m.Delete("nonexistent") + if v, ok := m.Load("nonexistent"); ok || v != nil { + t.Fatalf("Delete of non-existing key should not create a value, got %v", v) + } +} + +func TestMapClear(t *testing.T) { + var m sync.Map + + // Store some test data + m.Store("a", 1) + m.Store("b", 2) + m.Store("c", 3) + + // Verify data exists + count := 0 + m.Range(func(key, value interface{}) bool { + count++ + return true + }) + if count != 3 { + t.Fatalf("Expected 3 items before Clear, got %d", count) + } + + m.Clear() + + // Verify all keys are removed + keys := []interface{}{} + m.Range(func(key, _ interface{}) bool { + keys = append(keys, key) + return true + }) + + if len(keys) != 0 { + t.Fatalf("Clear should remove all keys, but got %d keys: %v", len(keys), keys) + } + + // Test that we can use Clear on empty map + m.Clear() // Should not panic + + // Test that Clear works on non-empty map again + m.Store("x", "test") + m.Clear() + + count = 0 + m.Range(func(key, value interface{}) bool { + count++ + return true + }) + if count != 0 { + t.Fatalf("Expected 0 items after second Clear, got %d", count) + } +} + +func TestConcurrentMapAccess(t *testing.T) { + var m sync.Map + keys := []string{"key1", "key2", "key3", "key4", "key5"} + values := []string{"value1", "value2", "value3", "value4", "value5"} + + // Concurrent writes + var wg sync.WaitGroup + wg.Add(len(keys)) + for i, key := range keys { + go func(k string, v string) { + defer wg.Done() + m.Store(k, v) + }(key, values[i]) + } + wg.Wait() + + // Verify all values are stored + for i, key := range keys { + storedValue, ok := m.Load(key) + if !ok { + t.Fatalf("Failed to load key %s", key) + } + if storedValue != values[i] { + t.Fatalf("Key %s has wrong value. Got %v, want %v", key, storedValue, values[i]) + } + } + + // Concurrent reads + done := make(chan bool, len(keys)) + for _, key := range keys { + go func(k string) { + m.Load(k) + done <- true + }(key) + } + + // Wait for all reads to complete + for i := 0; i < len(keys); i++ { + <-done + } +} diff --git a/test/std/sync/sync_pool_test.go b/test/std/sync/sync_pool_test.go new file mode 100644 index 0000000000..9735553a74 --- /dev/null +++ b/test/std/sync/sync_pool_test.go @@ -0,0 +1,80 @@ +package sync_test + +import ( + "sync" + "testing" +) + +func TestPoolBasic(t *testing.T) { + var pool sync.Pool + + // Test Put and Get + pool.Put("test") + if value := pool.Get(); value != "test" { + t.Fatalf("Pool.Get() returned %v, want 'test'", value) + } + + // Test Get from empty pool + if value := pool.Get(); value != nil { + t.Fatalf("Pool.Get() on empty pool returned %v, want nil", value) + } +} + +func TestPoolMultipleTypes(t *testing.T) { + var pool sync.Pool + + // Put different types + pool.Put("string") + pool.Put(42) + pool.Put([]int{1, 2, 3}) + + // Get values (order not guaranteed) + values := make([]interface{}, 0, 3) + for i := 0; i < 3; i++ { + if v := pool.Get(); v != nil { + values = append(values, v) + } + } + + if len(values) != 3 { + t.Fatalf("Expected to get 3 values, got %d", len(values)) + } + + // Check that we got the right types + foundString, foundInt, foundSlice := false, false, false + for _, v := range values { + switch val := v.(type) { + case string: + if val == "string" { + foundString = true + } + case int: + if val == 42 { + foundInt = true + } + case []int: + if len(val) == 3 && val[0] == 1 && val[1] == 2 && val[2] == 3 { + foundSlice = true + } + } + } + + if !foundString || !foundInt || !foundSlice { + t.Fatalf("Expected to find all types, got: string=%v, int=%v, slice=%v", foundString, foundInt, foundSlice) + } +} + +func TestPoolNil(t *testing.T) { + var pool sync.Pool + + // Test Get from empty pool + if value := pool.Get(); value != nil { + t.Fatalf("Pool.Get() on new pool should return nil, got %v", value) + } + + // Put nil and get it back + pool.Put(nil) + if value := pool.Get(); value != nil { + t.Fatalf("Pool.Get() after putting nil should return nil, got %v", value) + } +} diff --git a/test/std/syscall/symbols_darwin_amd64_part01_test.go b/test/std/syscall/symbols_darwin_amd64_part01_test.go new file mode 100644 index 0000000000..a94f3a1b32 --- /dev/null +++ b/test/std/syscall/symbols_darwin_amd64_part01_test.go @@ -0,0 +1,37 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build darwin && amd64 + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_darwinamd64_Part01(t *testing.T) { + _ = t + _ = syscall.F_MARKDEPENDENCY + _ = syscall.F_READBOOTSTRAP + _ = syscall.F_WRITEBOOTSTRAP + _ = syscall.NOTE_RESOURCEEND + _ = syscall.SIOCALIFADDR + _ = syscall.SIOCDLIFADDR + _ = syscall.SIOCGETSGCNT + _ = syscall.SIOCGETVIFCNT + _ = syscall.SIOCGLIFADDR + _ = syscall.SIOCGLIFPHYADDR + _ = syscall.SIOCSLIFPHYADDR + _ = syscall.SO_RESTRICTIONS + _ = syscall.SO_RESTRICT_DENYIN + _ = syscall.SO_RESTRICT_DENYOUT + _ = syscall.SO_RESTRICT_DENYSET + _ = syscall.SYS_ADD_PROFIL + _ = syscall.SYS_FSTATV + _ = syscall.SYS_GETAUDIT + _ = syscall.SYS_LSTATV + _ = syscall.SYS_MKCOMPLEX + _ = syscall.SYS_PROFIL + _ = syscall.SYS_SETAUDIT + _ = syscall.SYS_STATV + _ = syscall.TCP_MINMSSOVERLOAD +} diff --git a/test/std/syscall/symbols_darwin_arm64_part01_test.go b/test/std/syscall/symbols_darwin_arm64_part01_test.go new file mode 100644 index 0000000000..8ffbb278bc --- /dev/null +++ b/test/std/syscall/symbols_darwin_arm64_part01_test.go @@ -0,0 +1,45 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build darwin && arm64 + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_darwinarm64_Part01(t *testing.T) { + _ = t + _ = syscall.AF_UTUN + _ = syscall.EQFULL + _ = syscall.F_FINDSIGS + _ = syscall.F_GETCODEDIR + _ = syscall.F_GETPROTECTIONLEVEL + _ = syscall.F_SETLKWTIMEOUT + _ = syscall.F_SINGLE_WRITER + _ = syscall.F_TRANSCODEKEY + _ = syscall.NOTE_BACKGROUND + _ = syscall.NOTE_CRITICAL + _ = syscall.NOTE_EXIT_CSERROR + _ = syscall.NOTE_EXIT_DECRYPTFAIL + _ = syscall.NOTE_EXIT_DETAIL + _ = syscall.NOTE_EXIT_DETAIL_MASK + _ = syscall.NOTE_EXIT_MEMORY + _ = syscall.NOTE_EXIT_REPARENTED + _ = syscall.NOTE_LEEWAY + _ = syscall.O_DP_GETRAWENCRYPTED + _ = syscall.RLIMIT_CPU_USAGE_MONITOR + _ = syscall.RTF_PROXY + _ = syscall.RTF_ROUTER + _ = syscall.SIOCIFGCLONERS + _ = syscall.SO_NUMRCVPKT + _ = syscall.SYS_CSOPS_AUDITTOKEN + _ = syscall.SYS_KAS_INFO + _ = syscall.SYS_LEDGER + _ = syscall.SYS_OPEN_DPROTECTED_NP + _ = syscall.TCP_ENABLE_ECN + _ = syscall.TCP_KEEPCNT + _ = syscall.TCP_KEEPINTVL + _ = syscall.TCP_NOTSENT_LOWAT + _ = syscall.TCP_SENDMOREACKS +} diff --git a/test/std/syscall/symbols_darwin_common_part01_test.go b/test/std/syscall/symbols_darwin_common_part01_test.go new file mode 100644 index 0000000000..793871002d --- /dev/null +++ b/test/std/syscall/symbols_darwin_common_part01_test.go @@ -0,0 +1,231 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build darwin + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_darwincommon_Part01(t *testing.T) { + _ = t + _ = syscall.AF_APPLETALK + _ = syscall.AF_CCITT + _ = syscall.AF_CHAOS + _ = syscall.AF_CNT + _ = syscall.AF_COIP + _ = syscall.AF_DATAKIT + _ = syscall.AF_DECnet + _ = syscall.AF_DLI + _ = syscall.AF_E164 + _ = syscall.AF_ECMA + _ = syscall.AF_HYLINK + _ = syscall.AF_IEEE80211 + _ = syscall.AF_IMPLINK + _ = syscall.AF_INET + _ = syscall.AF_INET6 + _ = syscall.AF_IPX + _ = syscall.AF_ISDN + _ = syscall.AF_ISO + _ = syscall.AF_LAT + _ = syscall.AF_LINK + _ = syscall.AF_LOCAL + _ = syscall.AF_MAX + _ = syscall.AF_NATM + _ = syscall.AF_NDRV + _ = syscall.AF_NETBIOS + _ = syscall.AF_NS + _ = syscall.AF_OSI + _ = syscall.AF_PPP + _ = syscall.AF_PUP + _ = syscall.AF_RESERVED_36 + _ = syscall.AF_ROUTE + _ = syscall.AF_SIP + _ = syscall.AF_SNA + _ = syscall.AF_SYSTEM + _ = syscall.AF_UNIX + _ = syscall.AF_UNSPEC + _ = syscall.Accept + _ = syscall.Access + _ = syscall.Adjtime + _ = syscall.B0 + _ = syscall.B110 + _ = syscall.B115200 + _ = syscall.B1200 + _ = syscall.B134 + _ = syscall.B14400 + _ = syscall.B150 + _ = syscall.B1800 + _ = syscall.B19200 + _ = syscall.B200 + _ = syscall.B230400 + _ = syscall.B2400 + _ = syscall.B28800 + _ = syscall.B300 + _ = syscall.B38400 + _ = syscall.B4800 + _ = syscall.B50 + _ = syscall.B57600 + _ = syscall.B600 + _ = syscall.B7200 + _ = syscall.B75 + _ = syscall.B76800 + _ = syscall.B9600 + _ = syscall.BIOCFLUSH + _ = syscall.BIOCGBLEN + _ = syscall.BIOCGDLT + _ = syscall.BIOCGDLTLIST + _ = syscall.BIOCGETIF + _ = syscall.BIOCGHDRCMPLT + _ = syscall.BIOCGRSIG + _ = syscall.BIOCGRTIMEOUT + _ = syscall.BIOCGSEESENT + _ = syscall.BIOCGSTATS + _ = syscall.BIOCIMMEDIATE + _ = syscall.BIOCPROMISC + _ = syscall.BIOCSBLEN + _ = syscall.BIOCSDLT + _ = syscall.BIOCSETF + _ = syscall.BIOCSETIF + _ = syscall.BIOCSHDRCMPLT + _ = syscall.BIOCSRSIG + _ = syscall.BIOCSRTIMEOUT + _ = syscall.BIOCSSEESENT + _ = syscall.BIOCVERSION + _ = syscall.BPF_A + _ = syscall.BPF_ABS + _ = syscall.BPF_ADD + _ = syscall.BPF_ALIGNMENT + _ = syscall.BPF_ALU + _ = syscall.BPF_AND + _ = syscall.BPF_B + _ = syscall.BPF_DIV + _ = syscall.BPF_H + _ = syscall.BPF_IMM + _ = syscall.BPF_IND + _ = syscall.BPF_JA + _ = syscall.BPF_JEQ + _ = syscall.BPF_JGE + _ = syscall.BPF_JGT + _ = syscall.BPF_JMP + _ = syscall.BPF_JSET + _ = syscall.BPF_K + _ = syscall.BPF_LD + _ = syscall.BPF_LDX + _ = syscall.BPF_LEN + _ = syscall.BPF_LSH + _ = syscall.BPF_MAJOR_VERSION + _ = syscall.BPF_MAXBUFSIZE + _ = syscall.BPF_MAXINSNS + _ = syscall.BPF_MEM + _ = syscall.BPF_MEMWORDS + _ = syscall.BPF_MINBUFSIZE + _ = syscall.BPF_MINOR_VERSION + _ = syscall.BPF_MISC + _ = syscall.BPF_MSH + _ = syscall.BPF_MUL + _ = syscall.BPF_NEG + _ = syscall.BPF_OR + _ = syscall.BPF_RELEASE + _ = syscall.BPF_RET + _ = syscall.BPF_RSH + _ = syscall.BPF_ST + _ = syscall.BPF_STX + _ = syscall.BPF_SUB + _ = syscall.BPF_TAX + _ = syscall.BPF_TXA + _ = syscall.BPF_W + _ = syscall.BPF_X + _ = syscall.BRKINT + _ = syscall.Bind + _ = syscall.BpfBuflen + _ = syscall.BpfDatalink + var _ syscall.BpfHdr + _ = syscall.BpfHeadercmpl + var _ syscall.BpfInsn + _ = syscall.BpfInterface + _ = syscall.BpfJump + var _ syscall.BpfProgram + var _ syscall.BpfStat + _ = syscall.BpfStats + _ = syscall.BpfStmt + _ = syscall.BpfTimeout + var _ syscall.BpfVersion + _ = syscall.BytePtrFromString + _ = syscall.ByteSliceFromString + _ = syscall.CFLUSH + _ = syscall.CLOCAL + _ = syscall.CREAD + _ = syscall.CS5 + _ = syscall.CS6 + _ = syscall.CS7 + _ = syscall.CS8 + _ = syscall.CSIZE + _ = syscall.CSTART + _ = syscall.CSTATUS + _ = syscall.CSTOP + _ = syscall.CSTOPB + _ = syscall.CSUSP + _ = syscall.CTL_MAXNAME + _ = syscall.CTL_NET + _ = syscall.Chdir + _ = syscall.CheckBpfVersion + _ = syscall.Chflags + _ = syscall.Chmod + _ = syscall.Chown + _ = syscall.Chroot + _ = syscall.Clearenv + _ = syscall.Close + _ = syscall.CloseOnExec + _ = syscall.CmsgLen + _ = syscall.CmsgSpace + var _ syscall.Cmsghdr + var _ syscall.Conn + _ = syscall.Connect + var _ syscall.Credential + _ = syscall.DLT_APPLE_IP_OVER_IEEE1394 + _ = syscall.DLT_ARCNET + _ = syscall.DLT_ATM_CLIP + _ = syscall.DLT_ATM_RFC1483 + _ = syscall.DLT_AX25 + _ = syscall.DLT_CHAOS + _ = syscall.DLT_CHDLC + _ = syscall.DLT_C_HDLC + _ = syscall.DLT_EN10MB + _ = syscall.DLT_EN3MB + _ = syscall.DLT_FDDI + _ = syscall.DLT_IEEE802 + _ = syscall.DLT_IEEE802_11 + _ = syscall.DLT_IEEE802_11_RADIO + _ = syscall.DLT_IEEE802_11_RADIO_AVS + _ = syscall.DLT_LINUX_SLL + _ = syscall.DLT_LOOP + _ = syscall.DLT_NULL + _ = syscall.DLT_PFLOG + _ = syscall.DLT_PFSYNC + _ = syscall.DLT_PPP + _ = syscall.DLT_PPP_BSDOS + _ = syscall.DLT_PPP_SERIAL + _ = syscall.DLT_PRONET + _ = syscall.DLT_RAW + _ = syscall.DLT_SLIP + _ = syscall.DLT_SLIP_BSDOS + _ = syscall.DT_BLK + _ = syscall.DT_CHR + _ = syscall.DT_DIR + _ = syscall.DT_FIFO + _ = syscall.DT_LNK + _ = syscall.DT_REG + _ = syscall.DT_SOCK + _ = syscall.DT_UNKNOWN + _ = syscall.DT_WHT + var _ syscall.Dirent + _ = syscall.Dup + _ = syscall.Dup2 + _ = syscall.E2BIG + _ = syscall.EACCES + _ = syscall.EADDRINUSE + _ = syscall.EADDRNOTAVAIL + _ = syscall.EAFNOSUPPORT +} diff --git a/test/std/syscall/symbols_darwin_common_part02_test.go b/test/std/syscall/symbols_darwin_common_part02_test.go new file mode 100644 index 0000000000..78d3ca89ad --- /dev/null +++ b/test/std/syscall/symbols_darwin_common_part02_test.go @@ -0,0 +1,229 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build darwin + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_darwincommon_Part02(t *testing.T) { + _ = t + _ = syscall.EAGAIN + _ = syscall.EALREADY + _ = syscall.EAUTH + _ = syscall.EBADARCH + _ = syscall.EBADEXEC + _ = syscall.EBADF + _ = syscall.EBADMACHO + _ = syscall.EBADMSG + _ = syscall.EBADRPC + _ = syscall.EBUSY + _ = syscall.ECANCELED + _ = syscall.ECHILD + _ = syscall.ECHO + _ = syscall.ECHOCTL + _ = syscall.ECHOE + _ = syscall.ECHOK + _ = syscall.ECHOKE + _ = syscall.ECHONL + _ = syscall.ECHOPRT + _ = syscall.ECONNABORTED + _ = syscall.ECONNREFUSED + _ = syscall.ECONNRESET + _ = syscall.EDEADLK + _ = syscall.EDESTADDRREQ + _ = syscall.EDEVERR + _ = syscall.EDOM + _ = syscall.EDQUOT + _ = syscall.EEXIST + _ = syscall.EFAULT + _ = syscall.EFBIG + _ = syscall.EFTYPE + _ = syscall.EHOSTDOWN + _ = syscall.EHOSTUNREACH + _ = syscall.EIDRM + _ = syscall.EILSEQ + _ = syscall.EINPROGRESS + _ = syscall.EINTR + _ = syscall.EINVAL + _ = syscall.EIO + _ = syscall.EISCONN + _ = syscall.EISDIR + _ = syscall.ELAST + _ = syscall.ELOOP + _ = syscall.EMFILE + _ = syscall.EMLINK + _ = syscall.EMSGSIZE + _ = syscall.EMULTIHOP + _ = syscall.ENAMETOOLONG + _ = syscall.ENEEDAUTH + _ = syscall.ENETDOWN + _ = syscall.ENETRESET + _ = syscall.ENETUNREACH + _ = syscall.ENFILE + _ = syscall.ENOATTR + _ = syscall.ENOBUFS + _ = syscall.ENODATA + _ = syscall.ENODEV + _ = syscall.ENOENT + _ = syscall.ENOEXEC + _ = syscall.ENOLCK + _ = syscall.ENOLINK + _ = syscall.ENOMEM + _ = syscall.ENOMSG + _ = syscall.ENOPOLICY + _ = syscall.ENOPROTOOPT + _ = syscall.ENOSPC + _ = syscall.ENOSR + _ = syscall.ENOSTR + _ = syscall.ENOSYS + _ = syscall.ENOTBLK + _ = syscall.ENOTCONN + _ = syscall.ENOTDIR + _ = syscall.ENOTEMPTY + _ = syscall.ENOTRECOVERABLE + _ = syscall.ENOTSOCK + _ = syscall.ENOTSUP + _ = syscall.ENOTTY + _ = syscall.ENXIO + _ = syscall.EOPNOTSUPP + _ = syscall.EOVERFLOW + _ = syscall.EOWNERDEAD + _ = syscall.EPERM + _ = syscall.EPFNOSUPPORT + _ = syscall.EPIPE + _ = syscall.EPROCLIM + _ = syscall.EPROCUNAVAIL + _ = syscall.EPROGMISMATCH + _ = syscall.EPROGUNAVAIL + _ = syscall.EPROTO + _ = syscall.EPROTONOSUPPORT + _ = syscall.EPROTOTYPE + _ = syscall.EPWROFF + _ = syscall.ERANGE + _ = syscall.EREMOTE + _ = syscall.EROFS + _ = syscall.ERPCMISMATCH + _ = syscall.ESHLIBVERS + _ = syscall.ESHUTDOWN + _ = syscall.ESOCKTNOSUPPORT + _ = syscall.ESPIPE + _ = syscall.ESRCH + _ = syscall.ESTALE + _ = syscall.ETIME + _ = syscall.ETIMEDOUT + _ = syscall.ETOOMANYREFS + _ = syscall.ETXTBSY + _ = syscall.EUSERS + _ = syscall.EVFILT_AIO + _ = syscall.EVFILT_FS + _ = syscall.EVFILT_MACHPORT + _ = syscall.EVFILT_PROC + _ = syscall.EVFILT_READ + _ = syscall.EVFILT_SIGNAL + _ = syscall.EVFILT_SYSCOUNT + _ = syscall.EVFILT_THREADMARKER + _ = syscall.EVFILT_TIMER + _ = syscall.EVFILT_USER + _ = syscall.EVFILT_VM + _ = syscall.EVFILT_VNODE + _ = syscall.EVFILT_WRITE + _ = syscall.EV_ADD + _ = syscall.EV_CLEAR + _ = syscall.EV_DELETE + _ = syscall.EV_DISABLE + _ = syscall.EV_DISPATCH + _ = syscall.EV_ENABLE + _ = syscall.EV_EOF + _ = syscall.EV_ERROR + _ = syscall.EV_FLAG0 + _ = syscall.EV_FLAG1 + _ = syscall.EV_ONESHOT + _ = syscall.EV_OOBAND + _ = syscall.EV_POLL + _ = syscall.EV_RECEIPT + _ = syscall.EV_SYSFLAGS + _ = syscall.EWOULDBLOCK + _ = syscall.EXDEV + _ = syscall.EXTA + _ = syscall.EXTB + _ = syscall.EXTPROC + _ = syscall.Environ + var _ syscall.Errno + _ = syscall.Exchangedata + _ = syscall.Exec + _ = syscall.Exit + _ = syscall.FD_CLOEXEC + _ = syscall.FD_SETSIZE + _ = syscall.FLUSHO + _ = syscall.F_ADDFILESIGS + _ = syscall.F_ADDSIGS + _ = syscall.F_ALLOCATEALL + _ = syscall.F_ALLOCATECONTIG + _ = syscall.F_CHKCLEAN + _ = syscall.F_DUPFD + _ = syscall.F_DUPFD_CLOEXEC + _ = syscall.F_FLUSH_DATA + _ = syscall.F_FREEZE_FS + _ = syscall.F_FULLFSYNC + _ = syscall.F_GETFD + _ = syscall.F_GETFL + _ = syscall.F_GETLK + _ = syscall.F_GETLKPID + _ = syscall.F_GETNOSIGPIPE + _ = syscall.F_GETOWN + _ = syscall.F_GETPATH + _ = syscall.F_GETPATH_MTMINFO + _ = syscall.F_GETPROTECTIONCLASS + _ = syscall.F_GLOBAL_NOCACHE + _ = syscall.F_LOG2PHYS + _ = syscall.F_LOG2PHYS_EXT + _ = syscall.F_NOCACHE + _ = syscall.F_NODIRECT + _ = syscall.F_OK + _ = syscall.F_PATHPKG_CHECK + _ = syscall.F_PEOFPOSMODE + _ = syscall.F_PREALLOCATE + _ = syscall.F_RDADVISE + _ = syscall.F_RDAHEAD + _ = syscall.F_RDLCK + _ = syscall.F_SETBACKINGSTORE + _ = syscall.F_SETFD + _ = syscall.F_SETFL + _ = syscall.F_SETLK + _ = syscall.F_SETLKW + _ = syscall.F_SETNOSIGPIPE + _ = syscall.F_SETOWN + _ = syscall.F_SETPROTECTIONCLASS + _ = syscall.F_SETSIZE + _ = syscall.F_THAW_FS + _ = syscall.F_UNLCK + _ = syscall.F_VOLPOSMODE + _ = syscall.F_WRLCK + var _ syscall.Fbootstraptransfer_t + _ = syscall.Fchdir + _ = syscall.Fchflags + _ = syscall.Fchmod + _ = syscall.Fchown + _ = syscall.FcntlFlock + var _ syscall.FdSet + _ = syscall.Flock + var _ syscall.Flock_t + _ = syscall.FlushBpf + _ = syscall.ForkExec + _ = syscall.ForkLock + _ = syscall.Fpathconf + var _ syscall.Fsid + _ = syscall.Fstat + _ = syscall.Fstatfs + var _ syscall.Fstore_t + _ = syscall.Fsync + _ = syscall.Ftruncate + _ = syscall.Futimes + _ = syscall.Getdirentries + _ = syscall.Getdtablesize + _ = syscall.Getegid + _ = syscall.Getenv +} diff --git a/test/std/syscall/symbols_darwin_common_part03_test.go b/test/std/syscall/symbols_darwin_common_part03_test.go new file mode 100644 index 0000000000..31483b863f --- /dev/null +++ b/test/std/syscall/symbols_darwin_common_part03_test.go @@ -0,0 +1,233 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build darwin + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_darwincommon_Part03(t *testing.T) { + _ = t + _ = syscall.Geteuid + _ = syscall.Getfsstat + _ = syscall.Getgid + _ = syscall.Getgroups + _ = syscall.Getpagesize + _ = syscall.Getpeername + _ = syscall.Getpgid + _ = syscall.Getpgrp + _ = syscall.Getpid + _ = syscall.Getppid + _ = syscall.Getpriority + _ = syscall.Getrlimit + _ = syscall.Getrusage + _ = syscall.Getsid + _ = syscall.Getsockname + _ = syscall.GetsockoptByte + _ = syscall.GetsockoptICMPv6Filter + _ = syscall.GetsockoptIPMreq + _ = syscall.GetsockoptIPv6MTUInfo + _ = syscall.GetsockoptIPv6Mreq + _ = syscall.GetsockoptInet4Addr + _ = syscall.GetsockoptInt + _ = syscall.Gettimeofday + _ = syscall.Getuid + _ = syscall.Getwd + _ = syscall.HUPCL + _ = syscall.ICANON + _ = syscall.ICMP6_FILTER + var _ syscall.ICMPv6Filter + _ = syscall.ICRNL + _ = syscall.IEXTEN + _ = syscall.IFF_ALLMULTI + _ = syscall.IFF_ALTPHYS + _ = syscall.IFF_BROADCAST + _ = syscall.IFF_DEBUG + _ = syscall.IFF_LINK0 + _ = syscall.IFF_LINK1 + _ = syscall.IFF_LINK2 + _ = syscall.IFF_LOOPBACK + _ = syscall.IFF_MULTICAST + _ = syscall.IFF_NOARP + _ = syscall.IFF_NOTRAILERS + _ = syscall.IFF_OACTIVE + _ = syscall.IFF_POINTOPOINT + _ = syscall.IFF_PROMISC + _ = syscall.IFF_RUNNING + _ = syscall.IFF_SIMPLEX + _ = syscall.IFF_UP + _ = syscall.IFNAMSIZ + _ = syscall.IFT_1822 + _ = syscall.IFT_AAL5 + _ = syscall.IFT_ARCNET + _ = syscall.IFT_ARCNETPLUS + _ = syscall.IFT_ATM + _ = syscall.IFT_BRIDGE + _ = syscall.IFT_CARP + _ = syscall.IFT_CELLULAR + _ = syscall.IFT_CEPT + _ = syscall.IFT_DS3 + _ = syscall.IFT_ENC + _ = syscall.IFT_EON + _ = syscall.IFT_ETHER + _ = syscall.IFT_FAITH + _ = syscall.IFT_FDDI + _ = syscall.IFT_FRELAY + _ = syscall.IFT_FRELAYDCE + _ = syscall.IFT_GIF + _ = syscall.IFT_HDH1822 + _ = syscall.IFT_HIPPI + _ = syscall.IFT_HSSI + _ = syscall.IFT_HY + _ = syscall.IFT_IEEE1394 + _ = syscall.IFT_IEEE8023ADLAG + _ = syscall.IFT_ISDNBASIC + _ = syscall.IFT_ISDNPRIMARY + _ = syscall.IFT_ISO88022LLC + _ = syscall.IFT_ISO88023 + _ = syscall.IFT_ISO88024 + _ = syscall.IFT_ISO88025 + _ = syscall.IFT_ISO88026 + _ = syscall.IFT_L2VLAN + _ = syscall.IFT_LAPB + _ = syscall.IFT_LOCALTALK + _ = syscall.IFT_LOOP + _ = syscall.IFT_MIOX25 + _ = syscall.IFT_MODEM + _ = syscall.IFT_NSIP + _ = syscall.IFT_OTHER + _ = syscall.IFT_P10 + _ = syscall.IFT_P80 + _ = syscall.IFT_PARA + _ = syscall.IFT_PDP + _ = syscall.IFT_PFLOG + _ = syscall.IFT_PFSYNC + _ = syscall.IFT_PPP + _ = syscall.IFT_PROPMUX + _ = syscall.IFT_PROPVIRTUAL + _ = syscall.IFT_PTPSERIAL + _ = syscall.IFT_RS232 + _ = syscall.IFT_SDLC + _ = syscall.IFT_SIP + _ = syscall.IFT_SLIP + _ = syscall.IFT_SMDSDXI + _ = syscall.IFT_SMDSICIP + _ = syscall.IFT_SONET + _ = syscall.IFT_SONETPATH + _ = syscall.IFT_SONETVT + _ = syscall.IFT_STARLAN + _ = syscall.IFT_STF + _ = syscall.IFT_T1 + _ = syscall.IFT_ULTRA + _ = syscall.IFT_V35 + _ = syscall.IFT_X25 + _ = syscall.IFT_X25DDN + _ = syscall.IFT_X25PLE + _ = syscall.IFT_XETHER + _ = syscall.IGNBRK + _ = syscall.IGNCR + _ = syscall.IGNPAR + _ = syscall.IMAXBEL + _ = syscall.INLCR + _ = syscall.INPCK + _ = syscall.IN_CLASSA_HOST + _ = syscall.IN_CLASSA_MAX + _ = syscall.IN_CLASSA_NET + _ = syscall.IN_CLASSA_NSHIFT + _ = syscall.IN_CLASSB_HOST + _ = syscall.IN_CLASSB_MAX + _ = syscall.IN_CLASSB_NET + _ = syscall.IN_CLASSB_NSHIFT + _ = syscall.IN_CLASSC_HOST + _ = syscall.IN_CLASSC_NET + _ = syscall.IN_CLASSC_NSHIFT + _ = syscall.IN_CLASSD_HOST + _ = syscall.IN_CLASSD_NET + _ = syscall.IN_CLASSD_NSHIFT + _ = syscall.IN_LINKLOCALNETNUM + _ = syscall.IN_LOOPBACKNET + var _ syscall.IPMreq + _ = syscall.IPPROTO_3PC + _ = syscall.IPPROTO_ADFS + _ = syscall.IPPROTO_AH + _ = syscall.IPPROTO_AHIP + _ = syscall.IPPROTO_APES + _ = syscall.IPPROTO_ARGUS + _ = syscall.IPPROTO_AX25 + _ = syscall.IPPROTO_BHA + _ = syscall.IPPROTO_BLT + _ = syscall.IPPROTO_BRSATMON + _ = syscall.IPPROTO_CFTP + _ = syscall.IPPROTO_CHAOS + _ = syscall.IPPROTO_CMTP + _ = syscall.IPPROTO_CPHB + _ = syscall.IPPROTO_CPNX + _ = syscall.IPPROTO_DDP + _ = syscall.IPPROTO_DGP + _ = syscall.IPPROTO_DIVERT + _ = syscall.IPPROTO_DONE + _ = syscall.IPPROTO_DSTOPTS + _ = syscall.IPPROTO_EGP + _ = syscall.IPPROTO_EMCON + _ = syscall.IPPROTO_ENCAP + _ = syscall.IPPROTO_EON + _ = syscall.IPPROTO_ESP + _ = syscall.IPPROTO_ETHERIP + _ = syscall.IPPROTO_FRAGMENT + _ = syscall.IPPROTO_GGP + _ = syscall.IPPROTO_GMTP + _ = syscall.IPPROTO_GRE + _ = syscall.IPPROTO_HELLO + _ = syscall.IPPROTO_HMP + _ = syscall.IPPROTO_HOPOPTS + _ = syscall.IPPROTO_ICMP + _ = syscall.IPPROTO_ICMPV6 + _ = syscall.IPPROTO_IDP + _ = syscall.IPPROTO_IDPR + _ = syscall.IPPROTO_IDRP + _ = syscall.IPPROTO_IGMP + _ = syscall.IPPROTO_IGP + _ = syscall.IPPROTO_IGRP + _ = syscall.IPPROTO_IL + _ = syscall.IPPROTO_INLSP + _ = syscall.IPPROTO_INP + _ = syscall.IPPROTO_IP + _ = syscall.IPPROTO_IPCOMP + _ = syscall.IPPROTO_IPCV + _ = syscall.IPPROTO_IPEIP + _ = syscall.IPPROTO_IPIP + _ = syscall.IPPROTO_IPPC + _ = syscall.IPPROTO_IPV4 + _ = syscall.IPPROTO_IPV6 + _ = syscall.IPPROTO_IRTP + _ = syscall.IPPROTO_KRYPTOLAN + _ = syscall.IPPROTO_LARP + _ = syscall.IPPROTO_LEAF1 + _ = syscall.IPPROTO_LEAF2 + _ = syscall.IPPROTO_MAX + _ = syscall.IPPROTO_MAXID + _ = syscall.IPPROTO_MEAS + _ = syscall.IPPROTO_MHRP + _ = syscall.IPPROTO_MICP + _ = syscall.IPPROTO_MTP + _ = syscall.IPPROTO_MUX + _ = syscall.IPPROTO_ND + _ = syscall.IPPROTO_NHRP + _ = syscall.IPPROTO_NONE + _ = syscall.IPPROTO_NSP + _ = syscall.IPPROTO_NVPII + _ = syscall.IPPROTO_OSPFIGP + _ = syscall.IPPROTO_PGM + _ = syscall.IPPROTO_PIGP + _ = syscall.IPPROTO_PIM + _ = syscall.IPPROTO_PRM + _ = syscall.IPPROTO_PUP + _ = syscall.IPPROTO_PVP + _ = syscall.IPPROTO_RAW + _ = syscall.IPPROTO_RCCMON + _ = syscall.IPPROTO_RDP + _ = syscall.IPPROTO_ROUTING + _ = syscall.IPPROTO_RSVP +} diff --git a/test/std/syscall/symbols_darwin_common_part04_test.go b/test/std/syscall/symbols_darwin_common_part04_test.go new file mode 100644 index 0000000000..e5b14112e3 --- /dev/null +++ b/test/std/syscall/symbols_darwin_common_part04_test.go @@ -0,0 +1,232 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build darwin + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_darwincommon_Part04(t *testing.T) { + _ = t + _ = syscall.IPPROTO_RVD + _ = syscall.IPPROTO_SATEXPAK + _ = syscall.IPPROTO_SATMON + _ = syscall.IPPROTO_SCCSP + _ = syscall.IPPROTO_SCTP + _ = syscall.IPPROTO_SDRP + _ = syscall.IPPROTO_SEP + _ = syscall.IPPROTO_SRPC + _ = syscall.IPPROTO_ST + _ = syscall.IPPROTO_SVMTP + _ = syscall.IPPROTO_SWIPE + _ = syscall.IPPROTO_TCF + _ = syscall.IPPROTO_TCP + _ = syscall.IPPROTO_TP + _ = syscall.IPPROTO_TPXX + _ = syscall.IPPROTO_TRUNK1 + _ = syscall.IPPROTO_TRUNK2 + _ = syscall.IPPROTO_TTP + _ = syscall.IPPROTO_UDP + _ = syscall.IPPROTO_VINES + _ = syscall.IPPROTO_VISA + _ = syscall.IPPROTO_VMTP + _ = syscall.IPPROTO_WBEXPAK + _ = syscall.IPPROTO_WBMON + _ = syscall.IPPROTO_WSN + _ = syscall.IPPROTO_XNET + _ = syscall.IPPROTO_XTP + _ = syscall.IPV6_2292DSTOPTS + _ = syscall.IPV6_2292HOPLIMIT + _ = syscall.IPV6_2292HOPOPTS + _ = syscall.IPV6_2292NEXTHOP + _ = syscall.IPV6_2292PKTINFO + _ = syscall.IPV6_2292PKTOPTIONS + _ = syscall.IPV6_2292RTHDR + _ = syscall.IPV6_BINDV6ONLY + _ = syscall.IPV6_BOUND_IF + _ = syscall.IPV6_CHECKSUM + _ = syscall.IPV6_DEFAULT_MULTICAST_HOPS + _ = syscall.IPV6_DEFAULT_MULTICAST_LOOP + _ = syscall.IPV6_DEFHLIM + _ = syscall.IPV6_FAITH + _ = syscall.IPV6_FLOWINFO_MASK + _ = syscall.IPV6_FLOWLABEL_MASK + _ = syscall.IPV6_FRAGTTL + _ = syscall.IPV6_FW_ADD + _ = syscall.IPV6_FW_DEL + _ = syscall.IPV6_FW_FLUSH + _ = syscall.IPV6_FW_GET + _ = syscall.IPV6_FW_ZERO + _ = syscall.IPV6_HLIMDEC + _ = syscall.IPV6_IPSEC_POLICY + _ = syscall.IPV6_JOIN_GROUP + _ = syscall.IPV6_LEAVE_GROUP + _ = syscall.IPV6_MAXHLIM + _ = syscall.IPV6_MAXOPTHDR + _ = syscall.IPV6_MAXPACKET + _ = syscall.IPV6_MAX_GROUP_SRC_FILTER + _ = syscall.IPV6_MAX_MEMBERSHIPS + _ = syscall.IPV6_MAX_SOCK_SRC_FILTER + _ = syscall.IPV6_MIN_MEMBERSHIPS + _ = syscall.IPV6_MMTU + _ = syscall.IPV6_MULTICAST_HOPS + _ = syscall.IPV6_MULTICAST_IF + _ = syscall.IPV6_MULTICAST_LOOP + _ = syscall.IPV6_PORTRANGE + _ = syscall.IPV6_PORTRANGE_DEFAULT + _ = syscall.IPV6_PORTRANGE_HIGH + _ = syscall.IPV6_PORTRANGE_LOW + _ = syscall.IPV6_RECVTCLASS + _ = syscall.IPV6_RTHDR_LOOSE + _ = syscall.IPV6_RTHDR_STRICT + _ = syscall.IPV6_RTHDR_TYPE_0 + _ = syscall.IPV6_SOCKOPT_RESERVED1 + _ = syscall.IPV6_TCLASS + _ = syscall.IPV6_UNICAST_HOPS + _ = syscall.IPV6_V6ONLY + _ = syscall.IPV6_VERSION + _ = syscall.IPV6_VERSION_MASK + _ = syscall.IP_ADD_MEMBERSHIP + _ = syscall.IP_ADD_SOURCE_MEMBERSHIP + _ = syscall.IP_BLOCK_SOURCE + _ = syscall.IP_BOUND_IF + _ = syscall.IP_DEFAULT_MULTICAST_LOOP + _ = syscall.IP_DEFAULT_MULTICAST_TTL + _ = syscall.IP_DF + _ = syscall.IP_DROP_MEMBERSHIP + _ = syscall.IP_DROP_SOURCE_MEMBERSHIP + _ = syscall.IP_DUMMYNET_CONFIGURE + _ = syscall.IP_DUMMYNET_DEL + _ = syscall.IP_DUMMYNET_FLUSH + _ = syscall.IP_DUMMYNET_GET + _ = syscall.IP_FAITH + _ = syscall.IP_FW_ADD + _ = syscall.IP_FW_DEL + _ = syscall.IP_FW_FLUSH + _ = syscall.IP_FW_GET + _ = syscall.IP_FW_RESETLOG + _ = syscall.IP_FW_ZERO + _ = syscall.IP_HDRINCL + _ = syscall.IP_IPSEC_POLICY + _ = syscall.IP_MAXPACKET + _ = syscall.IP_MAX_GROUP_SRC_FILTER + _ = syscall.IP_MAX_MEMBERSHIPS + _ = syscall.IP_MAX_SOCK_MUTE_FILTER + _ = syscall.IP_MAX_SOCK_SRC_FILTER + _ = syscall.IP_MF + _ = syscall.IP_MIN_MEMBERSHIPS + _ = syscall.IP_MSFILTER + _ = syscall.IP_MSS + _ = syscall.IP_MULTICAST_IF + _ = syscall.IP_MULTICAST_IFINDEX + _ = syscall.IP_MULTICAST_LOOP + _ = syscall.IP_MULTICAST_TTL + _ = syscall.IP_MULTICAST_VIF + _ = syscall.IP_NAT__XXX + _ = syscall.IP_OFFMASK + _ = syscall.IP_OLD_FW_ADD + _ = syscall.IP_OLD_FW_DEL + _ = syscall.IP_OLD_FW_FLUSH + _ = syscall.IP_OLD_FW_GET + _ = syscall.IP_OLD_FW_RESETLOG + _ = syscall.IP_OLD_FW_ZERO + _ = syscall.IP_OPTIONS + _ = syscall.IP_PKTINFO + _ = syscall.IP_PORTRANGE + _ = syscall.IP_PORTRANGE_DEFAULT + _ = syscall.IP_PORTRANGE_HIGH + _ = syscall.IP_PORTRANGE_LOW + _ = syscall.IP_RECVDSTADDR + _ = syscall.IP_RECVIF + _ = syscall.IP_RECVOPTS + _ = syscall.IP_RECVPKTINFO + _ = syscall.IP_RECVRETOPTS + _ = syscall.IP_RECVTTL + _ = syscall.IP_RETOPTS + _ = syscall.IP_RF + _ = syscall.IP_RSVP_OFF + _ = syscall.IP_RSVP_ON + _ = syscall.IP_RSVP_VIF_OFF + _ = syscall.IP_RSVP_VIF_ON + _ = syscall.IP_STRIPHDR + _ = syscall.IP_TOS + _ = syscall.IP_TRAFFIC_MGT_BACKGROUND + _ = syscall.IP_TTL + _ = syscall.IP_UNBLOCK_SOURCE + var _ syscall.IPv6MTUInfo + var _ syscall.IPv6Mreq + _ = syscall.ISIG + _ = syscall.ISTRIP + _ = syscall.IUTF8 + _ = syscall.IXANY + _ = syscall.IXOFF + _ = syscall.IXON + var _ syscall.IfData + var _ syscall.IfMsghdr + var _ syscall.IfaMsghdr + var _ syscall.IfmaMsghdr + var _ syscall.IfmaMsghdr2 + _ = syscall.ImplementsGetwd + var _ syscall.Inet4Pktinfo + var _ syscall.Inet6Pktinfo + var _ syscall.InterfaceAddrMessage + var _ syscall.InterfaceMessage + var _ syscall.InterfaceMulticastAddrMessage + var _ syscall.Iovec + _ = syscall.Issetugid + _ = syscall.Kevent + var _ syscall.Kevent_t + _ = syscall.Kill + _ = syscall.Kqueue + _ = syscall.LOCK_EX + _ = syscall.LOCK_NB + _ = syscall.LOCK_SH + _ = syscall.LOCK_UN + _ = syscall.Lchown + var _ syscall.Linger + _ = syscall.Link + _ = syscall.Listen + var _ syscall.Log2phys_t + _ = syscall.Lstat + _ = syscall.MADV_CAN_REUSE + _ = syscall.MADV_DONTNEED + _ = syscall.MADV_FREE + _ = syscall.MADV_FREE_REUSABLE + _ = syscall.MADV_FREE_REUSE + _ = syscall.MADV_NORMAL + _ = syscall.MADV_RANDOM + _ = syscall.MADV_SEQUENTIAL + _ = syscall.MADV_WILLNEED + _ = syscall.MADV_ZERO_WIRED_PAGES + _ = syscall.MAP_ANON + _ = syscall.MAP_COPY + _ = syscall.MAP_FILE + _ = syscall.MAP_FIXED + _ = syscall.MAP_HASSEMAPHORE + _ = syscall.MAP_JIT + _ = syscall.MAP_NOCACHE + _ = syscall.MAP_NOEXTEND + _ = syscall.MAP_NORESERVE + _ = syscall.MAP_PRIVATE + _ = syscall.MAP_RENAME + _ = syscall.MAP_RESERVED0080 + _ = syscall.MAP_SHARED + _ = syscall.MCL_CURRENT + _ = syscall.MCL_FUTURE + _ = syscall.MSG_CTRUNC + _ = syscall.MSG_DONTROUTE + _ = syscall.MSG_DONTWAIT + _ = syscall.MSG_EOF + _ = syscall.MSG_EOR + _ = syscall.MSG_FLUSH + _ = syscall.MSG_HAVEMORE + _ = syscall.MSG_HOLD + _ = syscall.MSG_NEEDSA + _ = syscall.MSG_OOB + _ = syscall.MSG_PEEK + _ = syscall.MSG_RCVMORE + _ = syscall.MSG_SEND + _ = syscall.MSG_TRUNC +} diff --git a/test/std/syscall/symbols_darwin_common_part05_test.go b/test/std/syscall/symbols_darwin_common_part05_test.go new file mode 100644 index 0000000000..ce5dd72326 --- /dev/null +++ b/test/std/syscall/symbols_darwin_common_part05_test.go @@ -0,0 +1,232 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build darwin + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_darwincommon_Part05(t *testing.T) { + _ = t + _ = syscall.MSG_WAITALL + _ = syscall.MSG_WAITSTREAM + _ = syscall.MS_ASYNC + _ = syscall.MS_DEACTIVATE + _ = syscall.MS_INVALIDATE + _ = syscall.MS_KILLPAGES + _ = syscall.MS_SYNC + _ = syscall.Mkdir + _ = syscall.Mkfifo + _ = syscall.Mknod + _ = syscall.Mlock + _ = syscall.Mlockall + _ = syscall.Mmap + _ = syscall.Mprotect + var _ syscall.Msghdr + _ = syscall.Munlock + _ = syscall.Munlockall + _ = syscall.Munmap + _ = syscall.NAME_MAX + _ = syscall.NET_RT_DUMP + _ = syscall.NET_RT_DUMP2 + _ = syscall.NET_RT_FLAGS + _ = syscall.NET_RT_IFLIST + _ = syscall.NET_RT_IFLIST2 + _ = syscall.NET_RT_MAXID + _ = syscall.NET_RT_STAT + _ = syscall.NET_RT_TRASH + _ = syscall.NOFLSH + _ = syscall.NOTE_ABSOLUTE + _ = syscall.NOTE_ATTRIB + _ = syscall.NOTE_CHILD + _ = syscall.NOTE_DELETE + _ = syscall.NOTE_EXEC + _ = syscall.NOTE_EXIT + _ = syscall.NOTE_EXITSTATUS + _ = syscall.NOTE_EXTEND + _ = syscall.NOTE_FFAND + _ = syscall.NOTE_FFCOPY + _ = syscall.NOTE_FFCTRLMASK + _ = syscall.NOTE_FFLAGSMASK + _ = syscall.NOTE_FFNOP + _ = syscall.NOTE_FFOR + _ = syscall.NOTE_FORK + _ = syscall.NOTE_LINK + _ = syscall.NOTE_LOWAT + _ = syscall.NOTE_NONE + _ = syscall.NOTE_NSECONDS + _ = syscall.NOTE_PCTRLMASK + _ = syscall.NOTE_PDATAMASK + _ = syscall.NOTE_REAP + _ = syscall.NOTE_RENAME + _ = syscall.NOTE_REVOKE + _ = syscall.NOTE_SECONDS + _ = syscall.NOTE_SIGNAL + _ = syscall.NOTE_TRACK + _ = syscall.NOTE_TRACKERR + _ = syscall.NOTE_TRIGGER + _ = syscall.NOTE_USECONDS + _ = syscall.NOTE_VM_ERROR + _ = syscall.NOTE_VM_PRESSURE + _ = syscall.NOTE_VM_PRESSURE_SUDDEN_TERMINATE + _ = syscall.NOTE_VM_PRESSURE_TERMINATE + _ = syscall.NOTE_WRITE + _ = syscall.NsecToTimespec + _ = syscall.NsecToTimeval + _ = syscall.OCRNL + _ = syscall.OFDEL + _ = syscall.OFILL + _ = syscall.ONLCR + _ = syscall.ONLRET + _ = syscall.ONOCR + _ = syscall.ONOEOT + _ = syscall.OPOST + _ = syscall.O_ACCMODE + _ = syscall.O_ALERT + _ = syscall.O_APPEND + _ = syscall.O_ASYNC + _ = syscall.O_CLOEXEC + _ = syscall.O_CREAT + _ = syscall.O_DIRECTORY + _ = syscall.O_DSYNC + _ = syscall.O_EVTONLY + _ = syscall.O_EXCL + _ = syscall.O_EXLOCK + _ = syscall.O_FSYNC + _ = syscall.O_NDELAY + _ = syscall.O_NOCTTY + _ = syscall.O_NOFOLLOW + _ = syscall.O_NONBLOCK + _ = syscall.O_POPUP + _ = syscall.O_RDONLY + _ = syscall.O_RDWR + _ = syscall.O_SHLOCK + _ = syscall.O_SYMLINK + _ = syscall.O_SYNC + _ = syscall.O_TRUNC + _ = syscall.O_WRONLY + _ = syscall.Open + _ = syscall.PARENB + _ = syscall.PARMRK + _ = syscall.PARODD + _ = syscall.PENDIN + _ = syscall.PRIO_PGRP + _ = syscall.PRIO_PROCESS + _ = syscall.PRIO_USER + _ = syscall.PROT_EXEC + _ = syscall.PROT_NONE + _ = syscall.PROT_READ + _ = syscall.PROT_WRITE + _ = syscall.PTRACE_CONT + _ = syscall.PTRACE_KILL + _ = syscall.PTRACE_TRACEME + _ = syscall.PT_ATTACH + _ = syscall.PT_ATTACHEXC + _ = syscall.PT_CONTINUE + _ = syscall.PT_DENY_ATTACH + _ = syscall.PT_DETACH + _ = syscall.PT_FIRSTMACH + _ = syscall.PT_FORCEQUOTA + _ = syscall.PT_KILL + _ = syscall.PT_READ_D + _ = syscall.PT_READ_I + _ = syscall.PT_READ_U + _ = syscall.PT_SIGEXC + _ = syscall.PT_STEP + _ = syscall.PT_THUPDATE + _ = syscall.PT_TRACE_ME + _ = syscall.PT_WRITE_D + _ = syscall.PT_WRITE_I + _ = syscall.PT_WRITE_U + _ = syscall.ParseDirent + _ = syscall.ParseRoutingMessage + _ = syscall.ParseRoutingSockaddr + _ = syscall.ParseSocketControlMessage + _ = syscall.ParseUnixRights + _ = syscall.Pathconf + _ = syscall.Pipe + _ = syscall.Pread + var _ syscall.ProcAttr + _ = syscall.PtraceAttach + _ = syscall.PtraceDetach + _ = syscall.Pwrite + _ = syscall.RLIMIT_AS + _ = syscall.RLIMIT_CORE + _ = syscall.RLIMIT_CPU + _ = syscall.RLIMIT_DATA + _ = syscall.RLIMIT_FSIZE + _ = syscall.RLIMIT_NOFILE + _ = syscall.RLIMIT_STACK + _ = syscall.RLIM_INFINITY + _ = syscall.RTAX_AUTHOR + _ = syscall.RTAX_BRD + _ = syscall.RTAX_DST + _ = syscall.RTAX_GATEWAY + _ = syscall.RTAX_GENMASK + _ = syscall.RTAX_IFA + _ = syscall.RTAX_IFP + _ = syscall.RTAX_MAX + _ = syscall.RTAX_NETMASK + _ = syscall.RTA_AUTHOR + _ = syscall.RTA_BRD + _ = syscall.RTA_DST + _ = syscall.RTA_GATEWAY + _ = syscall.RTA_GENMASK + _ = syscall.RTA_IFA + _ = syscall.RTA_IFP + _ = syscall.RTA_NETMASK + _ = syscall.RTF_BLACKHOLE + _ = syscall.RTF_BROADCAST + _ = syscall.RTF_CLONING + _ = syscall.RTF_CONDEMNED + _ = syscall.RTF_DELCLONE + _ = syscall.RTF_DONE + _ = syscall.RTF_DYNAMIC + _ = syscall.RTF_GATEWAY + _ = syscall.RTF_HOST + _ = syscall.RTF_IFREF + _ = syscall.RTF_IFSCOPE + _ = syscall.RTF_LLINFO + _ = syscall.RTF_LOCAL + _ = syscall.RTF_MODIFIED + _ = syscall.RTF_MULTICAST + _ = syscall.RTF_PINNED + _ = syscall.RTF_PRCLONING + _ = syscall.RTF_PROTO1 + _ = syscall.RTF_PROTO2 + _ = syscall.RTF_PROTO3 + _ = syscall.RTF_REJECT + _ = syscall.RTF_STATIC + _ = syscall.RTF_UP + _ = syscall.RTF_WASCLONED + _ = syscall.RTF_XRESOLVE + _ = syscall.RTM_ADD + _ = syscall.RTM_CHANGE + _ = syscall.RTM_DELADDR + _ = syscall.RTM_DELETE + _ = syscall.RTM_DELMADDR + _ = syscall.RTM_GET + _ = syscall.RTM_GET2 + _ = syscall.RTM_IFINFO + _ = syscall.RTM_IFINFO2 + _ = syscall.RTM_LOCK + _ = syscall.RTM_LOSING + _ = syscall.RTM_MISS + _ = syscall.RTM_NEWADDR + _ = syscall.RTM_NEWMADDR + _ = syscall.RTM_NEWMADDR2 + _ = syscall.RTM_OLDADD + _ = syscall.RTM_OLDDEL + _ = syscall.RTM_REDIRECT + _ = syscall.RTM_RESOLVE + _ = syscall.RTM_RTTUNIT + _ = syscall.RTM_VERSION + _ = syscall.RTV_EXPIRE + _ = syscall.RTV_HOPCOUNT + _ = syscall.RTV_MTU + _ = syscall.RTV_RPIPE + _ = syscall.RTV_RTT + _ = syscall.RTV_RTTVAR +} diff --git a/test/std/syscall/symbols_darwin_common_part06_test.go b/test/std/syscall/symbols_darwin_common_part06_test.go new file mode 100644 index 0000000000..2ca38d0374 --- /dev/null +++ b/test/std/syscall/symbols_darwin_common_part06_test.go @@ -0,0 +1,230 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build darwin + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_darwincommon_Part06(t *testing.T) { + _ = t + _ = syscall.RTV_SPIPE + _ = syscall.RTV_SSTHRESH + _ = syscall.RUSAGE_CHILDREN + _ = syscall.RUSAGE_SELF + var _ syscall.Radvisory_t + var _ syscall.RawConn + var _ syscall.RawSockaddr + var _ syscall.RawSockaddrAny + var _ syscall.RawSockaddrDatalink + var _ syscall.RawSockaddrInet4 + var _ syscall.RawSockaddrInet6 + var _ syscall.RawSockaddrUnix + _ = syscall.RawSyscall + _ = syscall.RawSyscall6 + _ = syscall.Read + _ = syscall.ReadDirent + _ = syscall.Readlink + _ = syscall.Recvfrom + _ = syscall.Recvmsg + _ = syscall.Rename + _ = syscall.Revoke + var _ syscall.Rlimit + _ = syscall.Rmdir + var _ syscall.RouteMessage + _ = syscall.RouteRIB + var _ syscall.RoutingMessage + var _ syscall.RtMetrics + var _ syscall.RtMsghdr + var _ syscall.Rusage + _ = syscall.SCM_CREDS + _ = syscall.SCM_RIGHTS + _ = syscall.SCM_TIMESTAMP + _ = syscall.SCM_TIMESTAMP_MONOTONIC + _ = syscall.SHUT_RD + _ = syscall.SHUT_RDWR + _ = syscall.SHUT_WR + _ = syscall.SIGABRT + _ = syscall.SIGALRM + _ = syscall.SIGBUS + _ = syscall.SIGCHLD + _ = syscall.SIGCONT + _ = syscall.SIGEMT + _ = syscall.SIGFPE + _ = syscall.SIGHUP + _ = syscall.SIGILL + _ = syscall.SIGINFO + _ = syscall.SIGINT + _ = syscall.SIGIO + _ = syscall.SIGIOT + _ = syscall.SIGKILL + _ = syscall.SIGPIPE + _ = syscall.SIGPROF + _ = syscall.SIGQUIT + _ = syscall.SIGSEGV + _ = syscall.SIGSTOP + _ = syscall.SIGSYS + _ = syscall.SIGTERM + _ = syscall.SIGTRAP + _ = syscall.SIGTSTP + _ = syscall.SIGTTIN + _ = syscall.SIGTTOU + _ = syscall.SIGURG + _ = syscall.SIGUSR1 + _ = syscall.SIGUSR2 + _ = syscall.SIGVTALRM + _ = syscall.SIGWINCH + _ = syscall.SIGXCPU + _ = syscall.SIGXFSZ + _ = syscall.SIOCADDMULTI + _ = syscall.SIOCAIFADDR + _ = syscall.SIOCARPIPLL + _ = syscall.SIOCATMARK + _ = syscall.SIOCAUTOADDR + _ = syscall.SIOCAUTONETMASK + _ = syscall.SIOCDELMULTI + _ = syscall.SIOCDIFADDR + _ = syscall.SIOCDIFPHYADDR + _ = syscall.SIOCGDRVSPEC + _ = syscall.SIOCGETVLAN + _ = syscall.SIOCGHIWAT + _ = syscall.SIOCGIFADDR + _ = syscall.SIOCGIFALTMTU + _ = syscall.SIOCGIFASYNCMAP + _ = syscall.SIOCGIFBOND + _ = syscall.SIOCGIFBRDADDR + _ = syscall.SIOCGIFCAP + _ = syscall.SIOCGIFCONF + _ = syscall.SIOCGIFDEVMTU + _ = syscall.SIOCGIFDSTADDR + _ = syscall.SIOCGIFFLAGS + _ = syscall.SIOCGIFGENERIC + _ = syscall.SIOCGIFKPI + _ = syscall.SIOCGIFMAC + _ = syscall.SIOCGIFMEDIA + _ = syscall.SIOCGIFMETRIC + _ = syscall.SIOCGIFMTU + _ = syscall.SIOCGIFNETMASK + _ = syscall.SIOCGIFPDSTADDR + _ = syscall.SIOCGIFPHYS + _ = syscall.SIOCGIFPSRCADDR + _ = syscall.SIOCGIFSTATUS + _ = syscall.SIOCGIFVLAN + _ = syscall.SIOCGIFWAKEFLAGS + _ = syscall.SIOCGLOWAT + _ = syscall.SIOCGPGRP + _ = syscall.SIOCIFCREATE + _ = syscall.SIOCIFCREATE2 + _ = syscall.SIOCIFDESTROY + _ = syscall.SIOCRSLVMULTI + _ = syscall.SIOCSDRVSPEC + _ = syscall.SIOCSETVLAN + _ = syscall.SIOCSHIWAT + _ = syscall.SIOCSIFADDR + _ = syscall.SIOCSIFALTMTU + _ = syscall.SIOCSIFASYNCMAP + _ = syscall.SIOCSIFBOND + _ = syscall.SIOCSIFBRDADDR + _ = syscall.SIOCSIFCAP + _ = syscall.SIOCSIFDSTADDR + _ = syscall.SIOCSIFFLAGS + _ = syscall.SIOCSIFGENERIC + _ = syscall.SIOCSIFKPI + _ = syscall.SIOCSIFLLADDR + _ = syscall.SIOCSIFMAC + _ = syscall.SIOCSIFMEDIA + _ = syscall.SIOCSIFMETRIC + _ = syscall.SIOCSIFMTU + _ = syscall.SIOCSIFNETMASK + _ = syscall.SIOCSIFPHYADDR + _ = syscall.SIOCSIFPHYS + _ = syscall.SIOCSIFVLAN + _ = syscall.SIOCSLOWAT + _ = syscall.SIOCSPGRP + _ = syscall.SOCK_DGRAM + _ = syscall.SOCK_MAXADDRLEN + _ = syscall.SOCK_RAW + _ = syscall.SOCK_RDM + _ = syscall.SOCK_SEQPACKET + _ = syscall.SOCK_STREAM + _ = syscall.SOL_SOCKET + _ = syscall.SOMAXCONN + _ = syscall.SO_ACCEPTCONN + _ = syscall.SO_BROADCAST + _ = syscall.SO_DEBUG + _ = syscall.SO_DONTROUTE + _ = syscall.SO_DONTTRUNC + _ = syscall.SO_ERROR + _ = syscall.SO_KEEPALIVE + _ = syscall.SO_LABEL + _ = syscall.SO_LINGER + _ = syscall.SO_LINGER_SEC + _ = syscall.SO_NKE + _ = syscall.SO_NOADDRERR + _ = syscall.SO_NOSIGPIPE + _ = syscall.SO_NOTIFYCONFLICT + _ = syscall.SO_NP_EXTENSIONS + _ = syscall.SO_NREAD + _ = syscall.SO_NWRITE + _ = syscall.SO_OOBINLINE + _ = syscall.SO_PEERLABEL + _ = syscall.SO_RANDOMPORT + _ = syscall.SO_RCVBUF + _ = syscall.SO_RCVLOWAT + _ = syscall.SO_RCVTIMEO + _ = syscall.SO_REUSEADDR + _ = syscall.SO_REUSEPORT + _ = syscall.SO_REUSESHAREUID + _ = syscall.SO_SNDBUF + _ = syscall.SO_SNDLOWAT + _ = syscall.SO_SNDTIMEO + _ = syscall.SO_TIMESTAMP + _ = syscall.SO_TIMESTAMP_MONOTONIC + _ = syscall.SO_TYPE + _ = syscall.SO_UPCALLCLOSEWAIT + _ = syscall.SO_USELOOPBACK + _ = syscall.SO_WANTMORE + _ = syscall.SO_WANTOOBFLAG + _ = syscall.SYS_ACCEPT + _ = syscall.SYS_ACCEPT_NOCANCEL + _ = syscall.SYS_ACCESS + _ = syscall.SYS_ACCESS_EXTENDED + _ = syscall.SYS_ACCT + _ = syscall.SYS_ADJTIME + _ = syscall.SYS_AIO_CANCEL + _ = syscall.SYS_AIO_ERROR + _ = syscall.SYS_AIO_FSYNC + _ = syscall.SYS_AIO_READ + _ = syscall.SYS_AIO_RETURN + _ = syscall.SYS_AIO_SUSPEND + _ = syscall.SYS_AIO_SUSPEND_NOCANCEL + _ = syscall.SYS_AIO_WRITE + _ = syscall.SYS_ATGETMSG + _ = syscall.SYS_ATPGETREQ + _ = syscall.SYS_ATPGETRSP + _ = syscall.SYS_ATPSNDREQ + _ = syscall.SYS_ATPSNDRSP + _ = syscall.SYS_ATPUTMSG + _ = syscall.SYS_ATSOCKET + _ = syscall.SYS_AUDIT + _ = syscall.SYS_AUDITCTL + _ = syscall.SYS_AUDITON + _ = syscall.SYS_AUDIT_SESSION_JOIN + _ = syscall.SYS_AUDIT_SESSION_PORT + _ = syscall.SYS_AUDIT_SESSION_SELF + _ = syscall.SYS_BIND + _ = syscall.SYS_BSDTHREAD_CREATE + _ = syscall.SYS_BSDTHREAD_REGISTER + _ = syscall.SYS_BSDTHREAD_TERMINATE + _ = syscall.SYS_CHDIR + _ = syscall.SYS_CHFLAGS + _ = syscall.SYS_CHMOD + _ = syscall.SYS_CHMOD_EXTENDED + _ = syscall.SYS_CHOWN + _ = syscall.SYS_CHROOT + _ = syscall.SYS_CHUD + _ = syscall.SYS_CLOSE + _ = syscall.SYS_CLOSE_NOCANCEL +} diff --git a/test/std/syscall/symbols_darwin_common_part07_test.go b/test/std/syscall/symbols_darwin_common_part07_test.go new file mode 100644 index 0000000000..6b2b1fc230 --- /dev/null +++ b/test/std/syscall/symbols_darwin_common_part07_test.go @@ -0,0 +1,233 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build darwin + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_darwincommon_Part07(t *testing.T) { + _ = t + _ = syscall.SYS_CONNECT + _ = syscall.SYS_CONNECT_NOCANCEL + _ = syscall.SYS_COPYFILE + _ = syscall.SYS_CSOPS + _ = syscall.SYS_DELETE + _ = syscall.SYS_DUP + _ = syscall.SYS_DUP2 + _ = syscall.SYS_EXCHANGEDATA + _ = syscall.SYS_EXECVE + _ = syscall.SYS_EXIT + _ = syscall.SYS_FCHDIR + _ = syscall.SYS_FCHFLAGS + _ = syscall.SYS_FCHMOD + _ = syscall.SYS_FCHMOD_EXTENDED + _ = syscall.SYS_FCHOWN + _ = syscall.SYS_FCNTL + _ = syscall.SYS_FCNTL_NOCANCEL + _ = syscall.SYS_FDATASYNC + _ = syscall.SYS_FFSCTL + _ = syscall.SYS_FGETATTRLIST + _ = syscall.SYS_FGETXATTR + _ = syscall.SYS_FHOPEN + _ = syscall.SYS_FILEPORT_MAKEFD + _ = syscall.SYS_FILEPORT_MAKEPORT + _ = syscall.SYS_FLISTXATTR + _ = syscall.SYS_FLOCK + _ = syscall.SYS_FORK + _ = syscall.SYS_FPATHCONF + _ = syscall.SYS_FREMOVEXATTR + _ = syscall.SYS_FSCTL + _ = syscall.SYS_FSETATTRLIST + _ = syscall.SYS_FSETXATTR + _ = syscall.SYS_FSGETPATH + _ = syscall.SYS_FSTAT + _ = syscall.SYS_FSTAT64 + _ = syscall.SYS_FSTAT64_EXTENDED + _ = syscall.SYS_FSTATFS + _ = syscall.SYS_FSTATFS64 + _ = syscall.SYS_FSTAT_EXTENDED + _ = syscall.SYS_FSYNC + _ = syscall.SYS_FSYNC_NOCANCEL + _ = syscall.SYS_FTRUNCATE + _ = syscall.SYS_FUTIMES + _ = syscall.SYS_GETATTRLIST + _ = syscall.SYS_GETAUDIT_ADDR + _ = syscall.SYS_GETAUID + _ = syscall.SYS_GETDIRENTRIES + _ = syscall.SYS_GETDIRENTRIES64 + _ = syscall.SYS_GETDIRENTRIESATTR + _ = syscall.SYS_GETDTABLESIZE + _ = syscall.SYS_GETEGID + _ = syscall.SYS_GETEUID + _ = syscall.SYS_GETFH + _ = syscall.SYS_GETFSSTAT + _ = syscall.SYS_GETFSSTAT64 + _ = syscall.SYS_GETGID + _ = syscall.SYS_GETGROUPS + _ = syscall.SYS_GETHOSTUUID + _ = syscall.SYS_GETITIMER + _ = syscall.SYS_GETLCID + _ = syscall.SYS_GETLOGIN + _ = syscall.SYS_GETPEERNAME + _ = syscall.SYS_GETPGID + _ = syscall.SYS_GETPGRP + _ = syscall.SYS_GETPID + _ = syscall.SYS_GETPPID + _ = syscall.SYS_GETPRIORITY + _ = syscall.SYS_GETRLIMIT + _ = syscall.SYS_GETRUSAGE + _ = syscall.SYS_GETSGROUPS + _ = syscall.SYS_GETSID + _ = syscall.SYS_GETSOCKNAME + _ = syscall.SYS_GETSOCKOPT + _ = syscall.SYS_GETTID + _ = syscall.SYS_GETTIMEOFDAY + _ = syscall.SYS_GETUID + _ = syscall.SYS_GETWGROUPS + _ = syscall.SYS_GETXATTR + _ = syscall.SYS_IDENTITYSVC + _ = syscall.SYS_INITGROUPS + _ = syscall.SYS_IOCTL + _ = syscall.SYS_IOPOLICYSYS + _ = syscall.SYS_ISSETUGID + _ = syscall.SYS_KDEBUG_TRACE + _ = syscall.SYS_KEVENT + _ = syscall.SYS_KEVENT64 + _ = syscall.SYS_KILL + _ = syscall.SYS_KQUEUE + _ = syscall.SYS_LCHOWN + _ = syscall.SYS_LINK + _ = syscall.SYS_LIO_LISTIO + _ = syscall.SYS_LISTEN + _ = syscall.SYS_LISTXATTR + _ = syscall.SYS_LSEEK + _ = syscall.SYS_LSTAT + _ = syscall.SYS_LSTAT64 + _ = syscall.SYS_LSTAT64_EXTENDED + _ = syscall.SYS_LSTAT_EXTENDED + _ = syscall.SYS_MADVISE + _ = syscall.SYS_MAXSYSCALL + _ = syscall.SYS_MINCORE + _ = syscall.SYS_MINHERIT + _ = syscall.SYS_MKDIR + _ = syscall.SYS_MKDIR_EXTENDED + _ = syscall.SYS_MKFIFO + _ = syscall.SYS_MKFIFO_EXTENDED + _ = syscall.SYS_MKNOD + _ = syscall.SYS_MLOCK + _ = syscall.SYS_MLOCKALL + _ = syscall.SYS_MMAP + _ = syscall.SYS_MODWATCH + _ = syscall.SYS_MOUNT + _ = syscall.SYS_MPROTECT + _ = syscall.SYS_MSGCTL + _ = syscall.SYS_MSGGET + _ = syscall.SYS_MSGRCV + _ = syscall.SYS_MSGRCV_NOCANCEL + _ = syscall.SYS_MSGSND + _ = syscall.SYS_MSGSND_NOCANCEL + _ = syscall.SYS_MSGSYS + _ = syscall.SYS_MSYNC + _ = syscall.SYS_MSYNC_NOCANCEL + _ = syscall.SYS_MUNLOCK + _ = syscall.SYS_MUNLOCKALL + _ = syscall.SYS_MUNMAP + _ = syscall.SYS_NFSCLNT + _ = syscall.SYS_NFSSVC + _ = syscall.SYS_OPEN + _ = syscall.SYS_OPEN_EXTENDED + _ = syscall.SYS_OPEN_NOCANCEL + _ = syscall.SYS_PATHCONF + _ = syscall.SYS_PID_HIBERNATE + _ = syscall.SYS_PID_RESUME + _ = syscall.SYS_PID_SHUTDOWN_SOCKETS + _ = syscall.SYS_PID_SUSPEND + _ = syscall.SYS_PIPE + _ = syscall.SYS_POLL + _ = syscall.SYS_POLL_NOCANCEL + _ = syscall.SYS_POSIX_SPAWN + _ = syscall.SYS_PREAD + _ = syscall.SYS_PREAD_NOCANCEL + _ = syscall.SYS_PROCESS_POLICY + _ = syscall.SYS_PROC_INFO + _ = syscall.SYS_PSYNCH_CVBROAD + _ = syscall.SYS_PSYNCH_CVCLRPREPOST + _ = syscall.SYS_PSYNCH_CVSIGNAL + _ = syscall.SYS_PSYNCH_CVWAIT + _ = syscall.SYS_PSYNCH_MUTEXDROP + _ = syscall.SYS_PSYNCH_MUTEXWAIT + _ = syscall.SYS_PSYNCH_RW_DOWNGRADE + _ = syscall.SYS_PSYNCH_RW_LONGRDLOCK + _ = syscall.SYS_PSYNCH_RW_RDLOCK + _ = syscall.SYS_PSYNCH_RW_UNLOCK + _ = syscall.SYS_PSYNCH_RW_UNLOCK2 + _ = syscall.SYS_PSYNCH_RW_UPGRADE + _ = syscall.SYS_PSYNCH_RW_WRLOCK + _ = syscall.SYS_PSYNCH_RW_YIELDWRLOCK + _ = syscall.SYS_PTRACE + _ = syscall.SYS_PWRITE + _ = syscall.SYS_PWRITE_NOCANCEL + _ = syscall.SYS_QUOTACTL + _ = syscall.SYS_READ + _ = syscall.SYS_READLINK + _ = syscall.SYS_READV + _ = syscall.SYS_READV_NOCANCEL + _ = syscall.SYS_READ_NOCANCEL + _ = syscall.SYS_REBOOT + _ = syscall.SYS_RECVFROM + _ = syscall.SYS_RECVFROM_NOCANCEL + _ = syscall.SYS_RECVMSG + _ = syscall.SYS_RECVMSG_NOCANCEL + _ = syscall.SYS_REMOVEXATTR + _ = syscall.SYS_RENAME + _ = syscall.SYS_REVOKE + _ = syscall.SYS_RMDIR + _ = syscall.SYS_SEARCHFS + _ = syscall.SYS_SELECT + _ = syscall.SYS_SELECT_NOCANCEL + _ = syscall.SYS_SEMCTL + _ = syscall.SYS_SEMGET + _ = syscall.SYS_SEMOP + _ = syscall.SYS_SEMSYS + _ = syscall.SYS_SEM_CLOSE + _ = syscall.SYS_SEM_DESTROY + _ = syscall.SYS_SEM_GETVALUE + _ = syscall.SYS_SEM_INIT + _ = syscall.SYS_SEM_OPEN + _ = syscall.SYS_SEM_POST + _ = syscall.SYS_SEM_TRYWAIT + _ = syscall.SYS_SEM_UNLINK + _ = syscall.SYS_SEM_WAIT + _ = syscall.SYS_SEM_WAIT_NOCANCEL + _ = syscall.SYS_SENDFILE + _ = syscall.SYS_SENDMSG + _ = syscall.SYS_SENDMSG_NOCANCEL + _ = syscall.SYS_SENDTO + _ = syscall.SYS_SENDTO_NOCANCEL + _ = syscall.SYS_SETATTRLIST + _ = syscall.SYS_SETAUDIT_ADDR + _ = syscall.SYS_SETAUID + _ = syscall.SYS_SETEGID + _ = syscall.SYS_SETEUID + _ = syscall.SYS_SETGID + _ = syscall.SYS_SETGROUPS + _ = syscall.SYS_SETITIMER + _ = syscall.SYS_SETLCID + _ = syscall.SYS_SETLOGIN + _ = syscall.SYS_SETPGID + _ = syscall.SYS_SETPRIORITY + _ = syscall.SYS_SETPRIVEXEC + _ = syscall.SYS_SETREGID + _ = syscall.SYS_SETREUID + _ = syscall.SYS_SETRLIMIT + _ = syscall.SYS_SETSGROUPS + _ = syscall.SYS_SETSID + _ = syscall.SYS_SETSOCKOPT + _ = syscall.SYS_SETTID + _ = syscall.SYS_SETTID_WITH_PID + _ = syscall.SYS_SETTIMEOFDAY + _ = syscall.SYS_SETUID +} diff --git a/test/std/syscall/symbols_darwin_common_part08_test.go b/test/std/syscall/symbols_darwin_common_part08_test.go new file mode 100644 index 0000000000..66009f40a7 --- /dev/null +++ b/test/std/syscall/symbols_darwin_common_part08_test.go @@ -0,0 +1,231 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build darwin + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_darwincommon_Part08(t *testing.T) { + _ = t + _ = syscall.SYS_SETWGROUPS + _ = syscall.SYS_SETXATTR + _ = syscall.SYS_SHARED_REGION_CHECK_NP + _ = syscall.SYS_SHARED_REGION_MAP_AND_SLIDE_NP + _ = syscall.SYS_SHMAT + _ = syscall.SYS_SHMCTL + _ = syscall.SYS_SHMDT + _ = syscall.SYS_SHMGET + _ = syscall.SYS_SHMSYS + _ = syscall.SYS_SHM_OPEN + _ = syscall.SYS_SHM_UNLINK + _ = syscall.SYS_SHUTDOWN + _ = syscall.SYS_SIGACTION + _ = syscall.SYS_SIGALTSTACK + _ = syscall.SYS_SIGPENDING + _ = syscall.SYS_SIGPROCMASK + _ = syscall.SYS_SIGRETURN + _ = syscall.SYS_SIGSUSPEND + _ = syscall.SYS_SIGSUSPEND_NOCANCEL + _ = syscall.SYS_SOCKET + _ = syscall.SYS_SOCKETPAIR + _ = syscall.SYS_STACK_SNAPSHOT + _ = syscall.SYS_STAT + _ = syscall.SYS_STAT64 + _ = syscall.SYS_STAT64_EXTENDED + _ = syscall.SYS_STATFS + _ = syscall.SYS_STATFS64 + _ = syscall.SYS_STAT_EXTENDED + _ = syscall.SYS_SWAPON + _ = syscall.SYS_SYMLINK + _ = syscall.SYS_SYNC + _ = syscall.SYS_SYSCALL + _ = syscall.SYS_THREAD_SELFID + _ = syscall.SYS_TRUNCATE + _ = syscall.SYS_UMASK + _ = syscall.SYS_UMASK_EXTENDED + _ = syscall.SYS_UNDELETE + _ = syscall.SYS_UNLINK + _ = syscall.SYS_UNMOUNT + _ = syscall.SYS_UTIMES + _ = syscall.SYS_VFORK + _ = syscall.SYS_VM_PRESSURE_MONITOR + _ = syscall.SYS_WAIT4 + _ = syscall.SYS_WAIT4_NOCANCEL + _ = syscall.SYS_WAITEVENT + _ = syscall.SYS_WAITID + _ = syscall.SYS_WAITID_NOCANCEL + _ = syscall.SYS_WATCHEVENT + _ = syscall.SYS_WORKQ_KERNRETURN + _ = syscall.SYS_WORKQ_OPEN + _ = syscall.SYS_WRITE + _ = syscall.SYS_WRITEV + _ = syscall.SYS_WRITEV_NOCANCEL + _ = syscall.SYS_WRITE_NOCANCEL + _ = syscall.SYS___DISABLE_THREADSIGNAL + _ = syscall.SYS___MAC_EXECVE + _ = syscall.SYS___MAC_GETFSSTAT + _ = syscall.SYS___MAC_GET_FD + _ = syscall.SYS___MAC_GET_FILE + _ = syscall.SYS___MAC_GET_LCID + _ = syscall.SYS___MAC_GET_LCTX + _ = syscall.SYS___MAC_GET_LINK + _ = syscall.SYS___MAC_GET_MOUNT + _ = syscall.SYS___MAC_GET_PID + _ = syscall.SYS___MAC_GET_PROC + _ = syscall.SYS___MAC_MOUNT + _ = syscall.SYS___MAC_SET_FD + _ = syscall.SYS___MAC_SET_FILE + _ = syscall.SYS___MAC_SET_LCTX + _ = syscall.SYS___MAC_SET_LINK + _ = syscall.SYS___MAC_SET_PROC + _ = syscall.SYS___MAC_SYSCALL + _ = syscall.SYS___OLD_SEMWAIT_SIGNAL + _ = syscall.SYS___OLD_SEMWAIT_SIGNAL_NOCANCEL + _ = syscall.SYS___PTHREAD_CANCELED + _ = syscall.SYS___PTHREAD_CHDIR + _ = syscall.SYS___PTHREAD_FCHDIR + _ = syscall.SYS___PTHREAD_KILL + _ = syscall.SYS___PTHREAD_MARKCANCEL + _ = syscall.SYS___PTHREAD_SIGMASK + _ = syscall.SYS___SEMWAIT_SIGNAL + _ = syscall.SYS___SEMWAIT_SIGNAL_NOCANCEL + _ = syscall.SYS___SIGWAIT + _ = syscall.SYS___SIGWAIT_NOCANCEL + _ = syscall.SYS___SYSCTL + _ = syscall.S_IEXEC + _ = syscall.S_IFBLK + _ = syscall.S_IFCHR + _ = syscall.S_IFDIR + _ = syscall.S_IFIFO + _ = syscall.S_IFLNK + _ = syscall.S_IFMT + _ = syscall.S_IFREG + _ = syscall.S_IFSOCK + _ = syscall.S_IFWHT + _ = syscall.S_IREAD + _ = syscall.S_IRGRP + _ = syscall.S_IROTH + _ = syscall.S_IRUSR + _ = syscall.S_IRWXG + _ = syscall.S_IRWXO + _ = syscall.S_IRWXU + _ = syscall.S_ISGID + _ = syscall.S_ISTXT + _ = syscall.S_ISUID + _ = syscall.S_ISVTX + _ = syscall.S_IWGRP + _ = syscall.S_IWOTH + _ = syscall.S_IWRITE + _ = syscall.S_IWUSR + _ = syscall.S_IXGRP + _ = syscall.S_IXOTH + _ = syscall.S_IXUSR + _ = syscall.Seek + _ = syscall.Select + _ = syscall.Sendfile + _ = syscall.Sendmsg + _ = syscall.SendmsgN + _ = syscall.Sendto + _ = syscall.SetBpf + _ = syscall.SetBpfBuflen + _ = syscall.SetBpfDatalink + _ = syscall.SetBpfHeadercmpl + _ = syscall.SetBpfImmediate + _ = syscall.SetBpfInterface + _ = syscall.SetBpfPromisc + _ = syscall.SetBpfTimeout + _ = syscall.SetKevent + _ = syscall.SetNonblock + _ = syscall.Setegid + _ = syscall.Setenv + _ = syscall.Seteuid + _ = syscall.Setgid + _ = syscall.Setgroups + _ = syscall.Setlogin + _ = syscall.Setpgid + _ = syscall.Setpriority + _ = syscall.Setprivexec + _ = syscall.Setregid + _ = syscall.Setreuid + _ = syscall.Setrlimit + _ = syscall.Setsid + _ = syscall.SetsockoptByte + _ = syscall.SetsockoptICMPv6Filter + _ = syscall.SetsockoptIPMreq + _ = syscall.SetsockoptIPv6Mreq + _ = syscall.SetsockoptInet4Addr + _ = syscall.SetsockoptInt + _ = syscall.SetsockoptLinger + _ = syscall.SetsockoptString + _ = syscall.SetsockoptTimeval + _ = syscall.Settimeofday + _ = syscall.Setuid + _ = syscall.Shutdown + var _ syscall.Signal + _ = syscall.SizeofBpfHdr + _ = syscall.SizeofBpfInsn + _ = syscall.SizeofBpfProgram + _ = syscall.SizeofBpfStat + _ = syscall.SizeofBpfVersion + _ = syscall.SizeofCmsghdr + _ = syscall.SizeofICMPv6Filter + _ = syscall.SizeofIPMreq + _ = syscall.SizeofIPv6MTUInfo + _ = syscall.SizeofIPv6Mreq + _ = syscall.SizeofIfData + _ = syscall.SizeofIfMsghdr + _ = syscall.SizeofIfaMsghdr + _ = syscall.SizeofIfmaMsghdr + _ = syscall.SizeofIfmaMsghdr2 + _ = syscall.SizeofInet4Pktinfo + _ = syscall.SizeofInet6Pktinfo + _ = syscall.SizeofLinger + _ = syscall.SizeofMsghdr + _ = syscall.SizeofRtMetrics + _ = syscall.SizeofRtMsghdr + _ = syscall.SizeofSockaddrAny + _ = syscall.SizeofSockaddrDatalink + _ = syscall.SizeofSockaddrInet4 + _ = syscall.SizeofSockaddrInet6 + _ = syscall.SizeofSockaddrUnix + _ = syscall.SlicePtrFromStrings + var _ syscall.Sockaddr + var _ syscall.SockaddrDatalink + var _ syscall.SockaddrInet4 + var _ syscall.SockaddrInet6 + var _ syscall.SockaddrUnix + _ = syscall.Socket + var _ syscall.SocketControlMessage + _ = syscall.SocketDisableIPv6 + _ = syscall.Socketpair + _ = syscall.StartProcess + _ = syscall.Stat + var _ syscall.Stat_t + _ = syscall.Statfs + var _ syscall.Statfs_t + _ = syscall.Stderr + _ = syscall.Stdin + _ = syscall.Stdout + _ = syscall.StringBytePtr + _ = syscall.StringByteSlice + _ = syscall.StringSlicePtr + _ = syscall.Symlink + _ = syscall.Sync + var _ syscall.SysProcAttr + _ = syscall.Syscall + _ = syscall.Syscall6 + _ = syscall.Syscall9 + _ = syscall.Sysctl + _ = syscall.SysctlUint32 + _ = syscall.TCIFLUSH + _ = syscall.TCIOFLUSH + _ = syscall.TCOFLUSH + _ = syscall.TCP_CONNECTIONTIMEOUT + _ = syscall.TCP_KEEPALIVE + _ = syscall.TCP_MAXHLEN + _ = syscall.TCP_MAXOLEN + _ = syscall.TCP_MAXSEG +} diff --git a/test/std/syscall/symbols_darwin_common_part09_test.go b/test/std/syscall/symbols_darwin_common_part09_test.go new file mode 100644 index 0000000000..6e70ab32ba --- /dev/null +++ b/test/std/syscall/symbols_darwin_common_part09_test.go @@ -0,0 +1,141 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build darwin + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_darwincommon_Part09(t *testing.T) { + _ = t + _ = syscall.TCP_MAXWIN + _ = syscall.TCP_MAX_SACK + _ = syscall.TCP_MAX_WINSHIFT + _ = syscall.TCP_MINMSS + _ = syscall.TCP_MSS + _ = syscall.TCP_NODELAY + _ = syscall.TCP_NOOPT + _ = syscall.TCP_NOPUSH + _ = syscall.TCP_RXT_CONNDROPTIME + _ = syscall.TCP_RXT_FINDROP + _ = syscall.TCSAFLUSH + _ = syscall.TIOCCBRK + _ = syscall.TIOCCDTR + _ = syscall.TIOCCONS + _ = syscall.TIOCDCDTIMESTAMP + _ = syscall.TIOCDRAIN + _ = syscall.TIOCDSIMICROCODE + _ = syscall.TIOCEXCL + _ = syscall.TIOCEXT + _ = syscall.TIOCFLUSH + _ = syscall.TIOCGDRAINWAIT + _ = syscall.TIOCGETA + _ = syscall.TIOCGETD + _ = syscall.TIOCGPGRP + _ = syscall.TIOCGWINSZ + _ = syscall.TIOCIXOFF + _ = syscall.TIOCIXON + _ = syscall.TIOCMBIC + _ = syscall.TIOCMBIS + _ = syscall.TIOCMGDTRWAIT + _ = syscall.TIOCMGET + _ = syscall.TIOCMODG + _ = syscall.TIOCMODS + _ = syscall.TIOCMSDTRWAIT + _ = syscall.TIOCMSET + _ = syscall.TIOCM_CAR + _ = syscall.TIOCM_CD + _ = syscall.TIOCM_CTS + _ = syscall.TIOCM_DSR + _ = syscall.TIOCM_DTR + _ = syscall.TIOCM_LE + _ = syscall.TIOCM_RI + _ = syscall.TIOCM_RNG + _ = syscall.TIOCM_RTS + _ = syscall.TIOCM_SR + _ = syscall.TIOCM_ST + _ = syscall.TIOCNOTTY + _ = syscall.TIOCNXCL + _ = syscall.TIOCOUTQ + _ = syscall.TIOCPKT + _ = syscall.TIOCPKT_DATA + _ = syscall.TIOCPKT_DOSTOP + _ = syscall.TIOCPKT_FLUSHREAD + _ = syscall.TIOCPKT_FLUSHWRITE + _ = syscall.TIOCPKT_IOCTL + _ = syscall.TIOCPKT_NOSTOP + _ = syscall.TIOCPKT_START + _ = syscall.TIOCPKT_STOP + _ = syscall.TIOCPTYGNAME + _ = syscall.TIOCPTYGRANT + _ = syscall.TIOCPTYUNLK + _ = syscall.TIOCREMOTE + _ = syscall.TIOCSBRK + _ = syscall.TIOCSCONS + _ = syscall.TIOCSCTTY + _ = syscall.TIOCSDRAINWAIT + _ = syscall.TIOCSDTR + _ = syscall.TIOCSETA + _ = syscall.TIOCSETAF + _ = syscall.TIOCSETAW + _ = syscall.TIOCSETD + _ = syscall.TIOCSIG + _ = syscall.TIOCSPGRP + _ = syscall.TIOCSTART + _ = syscall.TIOCSTAT + _ = syscall.TIOCSTI + _ = syscall.TIOCSTOP + _ = syscall.TIOCSWINSZ + _ = syscall.TIOCTIMESTAMP + _ = syscall.TIOCUCNTL + _ = syscall.TOSTOP + var _ syscall.Termios + var _ syscall.Timespec + _ = syscall.TimespecToNsec + var _ syscall.Timeval + var _ syscall.Timeval32 + _ = syscall.TimevalToNsec + _ = syscall.Truncate + _ = syscall.Umask + _ = syscall.Undelete + _ = syscall.UnixRights + _ = syscall.Unlink + _ = syscall.Unmount + _ = syscall.Unsetenv + _ = syscall.Utimes + _ = syscall.UtimesNano + _ = syscall.VDISCARD + _ = syscall.VDSUSP + _ = syscall.VEOF + _ = syscall.VEOL + _ = syscall.VEOL2 + _ = syscall.VERASE + _ = syscall.VINTR + _ = syscall.VKILL + _ = syscall.VLNEXT + _ = syscall.VMIN + _ = syscall.VQUIT + _ = syscall.VREPRINT + _ = syscall.VSTART + _ = syscall.VSTATUS + _ = syscall.VSTOP + _ = syscall.VSUSP + _ = syscall.VT0 + _ = syscall.VT1 + _ = syscall.VTDLY + _ = syscall.VTIME + _ = syscall.VWERASE + _ = syscall.WCONTINUED + _ = syscall.WCOREFLAG + _ = syscall.WEXITED + _ = syscall.WNOHANG + _ = syscall.WNOWAIT + _ = syscall.WORDSIZE + _ = syscall.WSTOPPED + _ = syscall.WUNTRACED + _ = syscall.Wait4 + var _ syscall.WaitStatus + _ = syscall.Write +} diff --git a/test/std/syscall/symbols_linux_amd64_part01_test.go b/test/std/syscall/symbols_linux_amd64_part01_test.go new file mode 100644 index 0000000000..6bbdc64fe4 --- /dev/null +++ b/test/std/syscall/symbols_linux_amd64_part01_test.go @@ -0,0 +1,88 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build linux && amd64 + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_linuxamd64_Part01(t *testing.T) { + _ = t + _ = syscall.ARPHRD_IEEE802154_PHY + _ = syscall.Dup2 + _ = syscall.EPOLL_NONBLOCK + _ = syscall.Ioperm + _ = syscall.Iopl + _ = syscall.MAP_32BIT + _ = syscall.PTRACE_ARCH_PRCTL + _ = syscall.PTRACE_GETFPREGS + _ = syscall.PTRACE_GETFPXREGS + _ = syscall.PTRACE_GET_THREAD_AREA + _ = syscall.PTRACE_OLDSETOPTIONS + _ = syscall.PTRACE_SETFPREGS + _ = syscall.PTRACE_SETFPXREGS + _ = syscall.PTRACE_SET_THREAD_AREA + _ = syscall.PTRACE_SINGLEBLOCK + _ = syscall.PTRACE_SYSEMU + _ = syscall.PTRACE_SYSEMU_SINGLESTEP + _ = syscall.SYS_ACCESS + _ = syscall.SYS_AFS_SYSCALL + _ = syscall.SYS_ALARM + _ = syscall.SYS_ARCH_PRCTL + _ = syscall.SYS_CHMOD + _ = syscall.SYS_CHOWN + _ = syscall.SYS_CREAT + _ = syscall.SYS_CREATE_MODULE + _ = syscall.SYS_DUP2 + _ = syscall.SYS_EPOLL_CREATE + _ = syscall.SYS_EPOLL_CTL_OLD + _ = syscall.SYS_EPOLL_WAIT + _ = syscall.SYS_EPOLL_WAIT_OLD + _ = syscall.SYS_EVENTFD + _ = syscall.SYS_FORK + _ = syscall.SYS_FUTIMESAT + _ = syscall.SYS_GETDENTS + _ = syscall.SYS_GETPGRP + _ = syscall.SYS_GETPMSG + _ = syscall.SYS_GET_KERNEL_SYMS + _ = syscall.SYS_GET_THREAD_AREA + _ = syscall.SYS_INOTIFY_INIT + _ = syscall.SYS_IOPERM + _ = syscall.SYS_IOPL + _ = syscall.SYS_LCHOWN + _ = syscall.SYS_LINK + _ = syscall.SYS_LSTAT + _ = syscall.SYS_MKDIR + _ = syscall.SYS_MKNOD + _ = syscall.SYS_MODIFY_LDT + _ = syscall.SYS_NEWFSTATAT + _ = syscall.SYS_OPEN + _ = syscall.SYS_PAUSE + _ = syscall.SYS_PIPE + _ = syscall.SYS_POLL + _ = syscall.SYS_PUTPMSG + _ = syscall.SYS_QUERY_MODULE + _ = syscall.SYS_READLINK + _ = syscall.SYS_RENAME + _ = syscall.SYS_RMDIR + _ = syscall.SYS_SECURITY + _ = syscall.SYS_SELECT + _ = syscall.SYS_SET_THREAD_AREA + _ = syscall.SYS_SIGNALFD + _ = syscall.SYS_STAT + _ = syscall.SYS_SYMLINK + _ = syscall.SYS_SYSFS + _ = syscall.SYS_TIME + _ = syscall.SYS_TUXCALL + _ = syscall.SYS_UNLINK + _ = syscall.SYS_USELIB + _ = syscall.SYS_USTAT + _ = syscall.SYS_UTIME + _ = syscall.SYS_UTIMES + _ = syscall.SYS_VFORK + _ = syscall.SYS_VSERVER + _ = syscall.SYS__SYSCTL + _ = syscall.Ustat +} diff --git a/test/std/syscall/symbols_linux_arm64_part01_test.go b/test/std/syscall/symbols_linux_arm64_part01_test.go new file mode 100644 index 0000000000..05ad0509ca --- /dev/null +++ b/test/std/syscall/symbols_linux_arm64_part01_test.go @@ -0,0 +1,232 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build linux && arm64 + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_linuxarm64_Part01(t *testing.T) { + _ = t + _ = syscall.AF_NFC + _ = syscall.AF_VSOCK + _ = syscall.ARPHRD_CAIF + _ = syscall.ARPHRD_CAN + _ = syscall.ARPHRD_IEEE802154_MONITOR + _ = syscall.ARPHRD_IP6GRE + _ = syscall.ARPHRD_NETLINK + _ = syscall.ARPHRD_PHONET + _ = syscall.ARPHRD_PHONET_PIPE + _ = syscall.BPF_MOD + _ = syscall.BPF_XOR + _ = syscall.CFLUSH + _ = syscall.CSIGNAL + _ = syscall.CSTART + _ = syscall.CSTATUS + _ = syscall.CSTOP + _ = syscall.CSUSP + _ = syscall.EHWPOISON + _ = syscall.ENCODING_DEFAULT + _ = syscall.ENCODING_FM_MARK + _ = syscall.ENCODING_FM_SPACE + _ = syscall.ENCODING_MANCHESTER + _ = syscall.ENCODING_NRZ + _ = syscall.ENCODING_NRZI + _ = syscall.EPOLLWAKEUP + _ = syscall.ETH_P_8021AD + _ = syscall.ETH_P_8021AH + _ = syscall.ETH_P_802_3_MIN + _ = syscall.ETH_P_802_EX1 + _ = syscall.ETH_P_AF_IUCV + _ = syscall.ETH_P_BATMAN + _ = syscall.ETH_P_CANFD + _ = syscall.ETH_P_MVRP + _ = syscall.ETH_P_PRP + _ = syscall.ETH_P_QINQ1 + _ = syscall.ETH_P_QINQ2 + _ = syscall.ETH_P_QINQ3 + _ = syscall.ETH_P_TDLS + _ = syscall.EXTA + _ = syscall.EXTB + _ = syscall.EXTPROC + _ = syscall.Fstatat + _ = syscall.IFF_802_1Q_VLAN + _ = syscall.IFF_ATTACH_QUEUE + _ = syscall.IFF_BONDING + _ = syscall.IFF_BRIDGE_PORT + _ = syscall.IFF_DETACH_QUEUE + _ = syscall.IFF_DISABLE_NETPOLL + _ = syscall.IFF_DONT_BRIDGE + _ = syscall.IFF_DORMANT + _ = syscall.IFF_EBRIDGE + _ = syscall.IFF_ECHO + _ = syscall.IFF_ISATAP + _ = syscall.IFF_LIVE_ADDR_CHANGE + _ = syscall.IFF_LOWER_UP + _ = syscall.IFF_MACVLAN + _ = syscall.IFF_MACVLAN_PORT + _ = syscall.IFF_MASTER_8023AD + _ = syscall.IFF_MASTER_ALB + _ = syscall.IFF_MASTER_ARPMON + _ = syscall.IFF_MULTI_QUEUE + _ = syscall.IFF_NOFILTER + _ = syscall.IFF_OVS_DATAPATH + _ = syscall.IFF_PERSIST + _ = syscall.IFF_SLAVE_INACTIVE + _ = syscall.IFF_SLAVE_NEEDARP + _ = syscall.IFF_SUPP_NOFCS + _ = syscall.IFF_TEAM_PORT + _ = syscall.IFF_TX_SKB_SHARING + _ = syscall.IFF_UNICAST_FLT + _ = syscall.IFF_VOLATILE + _ = syscall.IFF_WAN_HDLC + _ = syscall.IFF_XMIT_DST_RELEASE + _ = syscall.IPPROTO_BEETPH + _ = syscall.IPPROTO_MH + _ = syscall.IP_MULTICAST_ALL + _ = syscall.IP_UNICAST_IF + _ = syscall.MADV_DODUMP + _ = syscall.MADV_DONTDUMP + _ = syscall.MAP_HUGE_MASK + _ = syscall.MAP_HUGE_SHIFT + _ = syscall.NETLINK_CRYPTO + _ = syscall.NETLINK_RDMA + _ = syscall.NETLINK_RX_RING + _ = syscall.NETLINK_SOCK_DIAG + _ = syscall.NETLINK_TX_RING + _ = syscall.NLM_F_DUMP_INTR + _ = syscall.O_PATH + _ = syscall.O_TMPFILE + _ = syscall.PACKET_AUXDATA + _ = syscall.PACKET_COPY_THRESH + _ = syscall.PACKET_FANOUT + _ = syscall.PACKET_FANOUT_CPU + _ = syscall.PACKET_FANOUT_FLAG_DEFRAG + _ = syscall.PACKET_FANOUT_FLAG_ROLLOVER + _ = syscall.PACKET_FANOUT_HASH + _ = syscall.PACKET_FANOUT_LB + _ = syscall.PACKET_FANOUT_RND + _ = syscall.PACKET_FANOUT_ROLLOVER + _ = syscall.PACKET_HDRLEN + _ = syscall.PACKET_LOSS + _ = syscall.PACKET_MR_UNICAST + _ = syscall.PACKET_ORIGDEV + _ = syscall.PACKET_RESERVE + _ = syscall.PACKET_TIMESTAMP + _ = syscall.PACKET_TX_HAS_OFF + _ = syscall.PACKET_TX_RING + _ = syscall.PACKET_TX_TIMESTAMP + _ = syscall.PACKET_VERSION + _ = syscall.PACKET_VNET_HDR + _ = syscall.PARITY_CRC16_PR0 + _ = syscall.PARITY_CRC16_PR0_CCITT + _ = syscall.PARITY_CRC16_PR1 + _ = syscall.PARITY_CRC16_PR1_CCITT + _ = syscall.PARITY_CRC32_PR0_CCITT + _ = syscall.PARITY_CRC32_PR1_CCITT + _ = syscall.PARITY_DEFAULT + _ = syscall.PARITY_NONE + _ = syscall.PR_GET_CHILD_SUBREAPER + _ = syscall.PR_GET_NO_NEW_PRIVS + _ = syscall.PR_GET_TID_ADDRESS + _ = syscall.PR_SET_CHILD_SUBREAPER + _ = syscall.PR_SET_MM + _ = syscall.PR_SET_MM_ARG_END + _ = syscall.PR_SET_MM_ARG_START + _ = syscall.PR_SET_MM_AUXV + _ = syscall.PR_SET_MM_BRK + _ = syscall.PR_SET_MM_END_CODE + _ = syscall.PR_SET_MM_END_DATA + _ = syscall.PR_SET_MM_ENV_END + _ = syscall.PR_SET_MM_ENV_START + _ = syscall.PR_SET_MM_EXE_FILE + _ = syscall.PR_SET_MM_START_BRK + _ = syscall.PR_SET_MM_START_CODE + _ = syscall.PR_SET_MM_START_DATA + _ = syscall.PR_SET_MM_START_STACK + _ = syscall.PR_SET_NO_NEW_PRIVS + _ = syscall.PR_SET_PTRACER_ANY + _ = syscall.PTRACE_EVENT_SECCOMP + _ = syscall.PTRACE_EVENT_STOP + _ = syscall.PTRACE_GETSIGMASK + _ = syscall.PTRACE_INTERRUPT + _ = syscall.PTRACE_LISTEN + _ = syscall.PTRACE_O_EXITKILL + _ = syscall.PTRACE_O_TRACESECCOMP + _ = syscall.PTRACE_PEEKSIGINFO + _ = syscall.PTRACE_PEEKSIGINFO_SHARED + _ = syscall.PTRACE_SEIZE + _ = syscall.PTRACE_SETSIGMASK + _ = syscall.RTAX_QUICKACK + _ = syscall.RTM_DELMDB + _ = syscall.RTM_GETMDB + _ = syscall.RTM_GETNETCONF + _ = syscall.RTM_NEWMDB + _ = syscall.RTM_NEWNETCONF + _ = syscall.RTPROT_MROUTED + _ = syscall.SCM_WIFI_STATUS + _ = syscall.SO_BUSY_POLL + _ = syscall.SO_GET_FILTER + _ = syscall.SO_LOCK_FILTER + _ = syscall.SO_MAX_PACING_RATE + _ = syscall.SO_NOFCS + _ = syscall.SO_PEEK_OFF + _ = syscall.SO_REUSEPORT + _ = syscall.SO_SELECT_ERR_QUEUE + _ = syscall.SO_WIFI_STATUS + _ = syscall.SYS_ARCH_SPECIFIC_SYSCALL + _ = syscall.SYS_BPF + _ = syscall.SYS_CLOCK_ADJTIME + _ = syscall.SYS_EXECVEAT + _ = syscall.SYS_FINIT_MODULE + _ = syscall.SYS_FSTATAT + _ = syscall.SYS_GETCPU + _ = syscall.SYS_GETRANDOM + _ = syscall.SYS_KCMP + _ = syscall.SYS_MEMFD_CREATE + _ = syscall.SYS_NAME_TO_HANDLE_AT + _ = syscall.SYS_OPEN_BY_HANDLE_AT + _ = syscall.SYS_PROCESS_VM_READV + _ = syscall.SYS_PROCESS_VM_WRITEV + _ = syscall.SYS_RENAMEAT2 + _ = syscall.SYS_SCHED_GETATTR + _ = syscall.SYS_SCHED_SETATTR + _ = syscall.SYS_SECCOMP + _ = syscall.SYS_SENDMMSG + _ = syscall.SYS_SETNS + _ = syscall.SYS_SYNCFS + _ = syscall.SYS_SYNC_FILE_RANGE2 + _ = syscall.TCFLSH + _ = syscall.TCP_COOKIE_IN_ALWAYS + _ = syscall.TCP_COOKIE_MAX + _ = syscall.TCP_COOKIE_MIN + _ = syscall.TCP_COOKIE_OUT_NEVER + _ = syscall.TCP_COOKIE_PAIR_SIZE + _ = syscall.TCP_COOKIE_TRANSACTIONS + _ = syscall.TCP_FASTOPEN + _ = syscall.TCP_MSS_DEFAULT + _ = syscall.TCP_MSS_DESIRED + _ = syscall.TCP_QUEUE_SEQ + _ = syscall.TCP_REPAIR + _ = syscall.TCP_REPAIR_OPTIONS + _ = syscall.TCP_REPAIR_QUEUE + _ = syscall.TCP_S_DATA_IN + _ = syscall.TCP_S_DATA_OUT + _ = syscall.TCP_THIN_DUPACK + _ = syscall.TCP_THIN_LINEAR_TIMEOUTS + _ = syscall.TCP_TIMESTAMP + _ = syscall.TCP_USER_TIMEOUT + _ = syscall.TCSAFLUSH + _ = syscall.TIOCGEXCL + _ = syscall.TIOCGPKT + _ = syscall.TIOCGPTLCK + _ = syscall.TIOCVHANGUP + _ = syscall.TUNGETFILTER + _ = syscall.TUNSETIFINDEX + _ = syscall.TUNSETQUEUE + _ = syscall.VT0 + _ = syscall.VT1 + _ = syscall.VTDLY +} diff --git a/test/std/syscall/symbols_linux_common_part01_test.go b/test/std/syscall/symbols_linux_common_part01_test.go new file mode 100644 index 0000000000..db96dd5acd --- /dev/null +++ b/test/std/syscall/symbols_linux_common_part01_test.go @@ -0,0 +1,233 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build linux + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_linuxcommon_Part01(t *testing.T) { + _ = t + _ = syscall.AF_ALG + _ = syscall.AF_APPLETALK + _ = syscall.AF_ASH + _ = syscall.AF_ATMPVC + _ = syscall.AF_ATMSVC + _ = syscall.AF_AX25 + _ = syscall.AF_BLUETOOTH + _ = syscall.AF_BRIDGE + _ = syscall.AF_CAIF + _ = syscall.AF_CAN + _ = syscall.AF_DECnet + _ = syscall.AF_ECONET + _ = syscall.AF_FILE + _ = syscall.AF_IEEE802154 + _ = syscall.AF_INET + _ = syscall.AF_INET6 + _ = syscall.AF_IPX + _ = syscall.AF_IRDA + _ = syscall.AF_ISDN + _ = syscall.AF_IUCV + _ = syscall.AF_KEY + _ = syscall.AF_LLC + _ = syscall.AF_LOCAL + _ = syscall.AF_MAX + _ = syscall.AF_NETBEUI + _ = syscall.AF_NETLINK + _ = syscall.AF_NETROM + _ = syscall.AF_PACKET + _ = syscall.AF_PHONET + _ = syscall.AF_PPPOX + _ = syscall.AF_RDS + _ = syscall.AF_ROSE + _ = syscall.AF_ROUTE + _ = syscall.AF_RXRPC + _ = syscall.AF_SECURITY + _ = syscall.AF_SNA + _ = syscall.AF_TIPC + _ = syscall.AF_UNIX + _ = syscall.AF_UNSPEC + _ = syscall.AF_WANPIPE + _ = syscall.AF_X25 + _ = syscall.ARPHRD_ADAPT + _ = syscall.ARPHRD_APPLETLK + _ = syscall.ARPHRD_ARCNET + _ = syscall.ARPHRD_ASH + _ = syscall.ARPHRD_ATM + _ = syscall.ARPHRD_AX25 + _ = syscall.ARPHRD_BIF + _ = syscall.ARPHRD_CHAOS + _ = syscall.ARPHRD_CISCO + _ = syscall.ARPHRD_CSLIP + _ = syscall.ARPHRD_CSLIP6 + _ = syscall.ARPHRD_DDCMP + _ = syscall.ARPHRD_DLCI + _ = syscall.ARPHRD_ECONET + _ = syscall.ARPHRD_EETHER + _ = syscall.ARPHRD_ETHER + _ = syscall.ARPHRD_EUI64 + _ = syscall.ARPHRD_FCAL + _ = syscall.ARPHRD_FCFABRIC + _ = syscall.ARPHRD_FCPL + _ = syscall.ARPHRD_FCPP + _ = syscall.ARPHRD_FDDI + _ = syscall.ARPHRD_FRAD + _ = syscall.ARPHRD_HDLC + _ = syscall.ARPHRD_HIPPI + _ = syscall.ARPHRD_HWX25 + _ = syscall.ARPHRD_IEEE1394 + _ = syscall.ARPHRD_IEEE802 + _ = syscall.ARPHRD_IEEE80211 + _ = syscall.ARPHRD_IEEE80211_PRISM + _ = syscall.ARPHRD_IEEE80211_RADIOTAP + _ = syscall.ARPHRD_IEEE802154 + _ = syscall.ARPHRD_IEEE802_TR + _ = syscall.ARPHRD_INFINIBAND + _ = syscall.ARPHRD_IPDDP + _ = syscall.ARPHRD_IPGRE + _ = syscall.ARPHRD_IRDA + _ = syscall.ARPHRD_LAPB + _ = syscall.ARPHRD_LOCALTLK + _ = syscall.ARPHRD_LOOPBACK + _ = syscall.ARPHRD_METRICOM + _ = syscall.ARPHRD_NETROM + _ = syscall.ARPHRD_NONE + _ = syscall.ARPHRD_PIMREG + _ = syscall.ARPHRD_PPP + _ = syscall.ARPHRD_PRONET + _ = syscall.ARPHRD_RAWHDLC + _ = syscall.ARPHRD_ROSE + _ = syscall.ARPHRD_RSRVD + _ = syscall.ARPHRD_SIT + _ = syscall.ARPHRD_SKIP + _ = syscall.ARPHRD_SLIP + _ = syscall.ARPHRD_SLIP6 + _ = syscall.ARPHRD_TUNNEL + _ = syscall.ARPHRD_TUNNEL6 + _ = syscall.ARPHRD_VOID + _ = syscall.ARPHRD_X25 + _ = syscall.Accept + _ = syscall.Accept4 + _ = syscall.Access + _ = syscall.Acct + _ = syscall.Adjtimex + _ = syscall.AllThreadsSyscall + _ = syscall.AllThreadsSyscall6 + _ = syscall.AttachLsf + _ = syscall.B0 + _ = syscall.B1000000 + _ = syscall.B110 + _ = syscall.B115200 + _ = syscall.B1152000 + _ = syscall.B1200 + _ = syscall.B134 + _ = syscall.B150 + _ = syscall.B1500000 + _ = syscall.B1800 + _ = syscall.B19200 + _ = syscall.B200 + _ = syscall.B2000000 + _ = syscall.B230400 + _ = syscall.B2400 + _ = syscall.B2500000 + _ = syscall.B300 + _ = syscall.B3000000 + _ = syscall.B3500000 + _ = syscall.B38400 + _ = syscall.B4000000 + _ = syscall.B460800 + _ = syscall.B4800 + _ = syscall.B50 + _ = syscall.B500000 + _ = syscall.B57600 + _ = syscall.B576000 + _ = syscall.B600 + _ = syscall.B75 + _ = syscall.B921600 + _ = syscall.B9600 + _ = syscall.BPF_A + _ = syscall.BPF_ABS + _ = syscall.BPF_ADD + _ = syscall.BPF_ALU + _ = syscall.BPF_AND + _ = syscall.BPF_B + _ = syscall.BPF_DIV + _ = syscall.BPF_H + _ = syscall.BPF_IMM + _ = syscall.BPF_IND + _ = syscall.BPF_JA + _ = syscall.BPF_JEQ + _ = syscall.BPF_JGE + _ = syscall.BPF_JGT + _ = syscall.BPF_JMP + _ = syscall.BPF_JSET + _ = syscall.BPF_K + _ = syscall.BPF_LD + _ = syscall.BPF_LDX + _ = syscall.BPF_LEN + _ = syscall.BPF_LSH + _ = syscall.BPF_MAJOR_VERSION + _ = syscall.BPF_MAXINSNS + _ = syscall.BPF_MEM + _ = syscall.BPF_MEMWORDS + _ = syscall.BPF_MINOR_VERSION + _ = syscall.BPF_MISC + _ = syscall.BPF_MSH + _ = syscall.BPF_MUL + _ = syscall.BPF_NEG + _ = syscall.BPF_OR + _ = syscall.BPF_RET + _ = syscall.BPF_RSH + _ = syscall.BPF_ST + _ = syscall.BPF_STX + _ = syscall.BPF_SUB + _ = syscall.BPF_TAX + _ = syscall.BPF_TXA + _ = syscall.BPF_W + _ = syscall.BPF_X + _ = syscall.BRKINT + _ = syscall.Bind + _ = syscall.BindToDevice + _ = syscall.BytePtrFromString + _ = syscall.ByteSliceFromString + _ = syscall.CLOCAL + _ = syscall.CLONE_CHILD_CLEARTID + _ = syscall.CLONE_CHILD_SETTID + _ = syscall.CLONE_CLEAR_SIGHAND + _ = syscall.CLONE_DETACHED + _ = syscall.CLONE_FILES + _ = syscall.CLONE_FS + _ = syscall.CLONE_INTO_CGROUP + _ = syscall.CLONE_IO + _ = syscall.CLONE_NEWCGROUP + _ = syscall.CLONE_NEWIPC + _ = syscall.CLONE_NEWNET + _ = syscall.CLONE_NEWNS + _ = syscall.CLONE_NEWPID + _ = syscall.CLONE_NEWTIME + _ = syscall.CLONE_NEWUSER + _ = syscall.CLONE_NEWUTS + _ = syscall.CLONE_PARENT + _ = syscall.CLONE_PARENT_SETTID + _ = syscall.CLONE_PIDFD + _ = syscall.CLONE_PTRACE + _ = syscall.CLONE_SETTLS + _ = syscall.CLONE_SIGHAND + _ = syscall.CLONE_SYSVSEM + _ = syscall.CLONE_THREAD + _ = syscall.CLONE_UNTRACED + _ = syscall.CLONE_VFORK + _ = syscall.CLONE_VM + _ = syscall.CREAD + _ = syscall.CS5 + _ = syscall.CS6 + _ = syscall.CS7 + _ = syscall.CS8 + _ = syscall.CSIZE + _ = syscall.CSTOPB + _ = syscall.Chdir + _ = syscall.Chmod + _ = syscall.Chown +} diff --git a/test/std/syscall/symbols_linux_common_part02_test.go b/test/std/syscall/symbols_linux_common_part02_test.go new file mode 100644 index 0000000000..44f8f31b00 --- /dev/null +++ b/test/std/syscall/symbols_linux_common_part02_test.go @@ -0,0 +1,231 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build linux + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_linuxcommon_Part02(t *testing.T) { + _ = t + _ = syscall.Chroot + _ = syscall.Clearenv + _ = syscall.Close + _ = syscall.CloseOnExec + _ = syscall.CmsgLen + _ = syscall.CmsgSpace + var _ syscall.Cmsghdr + var _ syscall.Conn + _ = syscall.Connect + _ = syscall.Creat + var _ syscall.Credential + _ = syscall.DT_BLK + _ = syscall.DT_CHR + _ = syscall.DT_DIR + _ = syscall.DT_FIFO + _ = syscall.DT_LNK + _ = syscall.DT_REG + _ = syscall.DT_SOCK + _ = syscall.DT_UNKNOWN + _ = syscall.DT_WHT + _ = syscall.DetachLsf + var _ syscall.Dirent + _ = syscall.Dup + _ = syscall.Dup3 + _ = syscall.E2BIG + _ = syscall.EACCES + _ = syscall.EADDRINUSE + _ = syscall.EADDRNOTAVAIL + _ = syscall.EADV + _ = syscall.EAFNOSUPPORT + _ = syscall.EAGAIN + _ = syscall.EALREADY + _ = syscall.EBADE + _ = syscall.EBADF + _ = syscall.EBADFD + _ = syscall.EBADMSG + _ = syscall.EBADR + _ = syscall.EBADRQC + _ = syscall.EBADSLT + _ = syscall.EBFONT + _ = syscall.EBUSY + _ = syscall.ECANCELED + _ = syscall.ECHILD + _ = syscall.ECHO + _ = syscall.ECHOCTL + _ = syscall.ECHOE + _ = syscall.ECHOK + _ = syscall.ECHOKE + _ = syscall.ECHONL + _ = syscall.ECHOPRT + _ = syscall.ECHRNG + _ = syscall.ECOMM + _ = syscall.ECONNABORTED + _ = syscall.ECONNREFUSED + _ = syscall.ECONNRESET + _ = syscall.EDEADLK + _ = syscall.EDEADLOCK + _ = syscall.EDESTADDRREQ + _ = syscall.EDOM + _ = syscall.EDOTDOT + _ = syscall.EDQUOT + _ = syscall.EEXIST + _ = syscall.EFAULT + _ = syscall.EFBIG + _ = syscall.EHOSTDOWN + _ = syscall.EHOSTUNREACH + _ = syscall.EIDRM + _ = syscall.EILSEQ + _ = syscall.EINPROGRESS + _ = syscall.EINTR + _ = syscall.EINVAL + _ = syscall.EIO + _ = syscall.EISCONN + _ = syscall.EISDIR + _ = syscall.EISNAM + _ = syscall.EKEYEXPIRED + _ = syscall.EKEYREJECTED + _ = syscall.EKEYREVOKED + _ = syscall.EL2HLT + _ = syscall.EL2NSYNC + _ = syscall.EL3HLT + _ = syscall.EL3RST + _ = syscall.ELIBACC + _ = syscall.ELIBBAD + _ = syscall.ELIBEXEC + _ = syscall.ELIBMAX + _ = syscall.ELIBSCN + _ = syscall.ELNRNG + _ = syscall.ELOOP + _ = syscall.EMEDIUMTYPE + _ = syscall.EMFILE + _ = syscall.EMLINK + _ = syscall.EMSGSIZE + _ = syscall.EMULTIHOP + _ = syscall.ENAMETOOLONG + _ = syscall.ENAVAIL + _ = syscall.ENETDOWN + _ = syscall.ENETRESET + _ = syscall.ENETUNREACH + _ = syscall.ENFILE + _ = syscall.ENOANO + _ = syscall.ENOBUFS + _ = syscall.ENOCSI + _ = syscall.ENODATA + _ = syscall.ENODEV + _ = syscall.ENOENT + _ = syscall.ENOEXEC + _ = syscall.ENOKEY + _ = syscall.ENOLCK + _ = syscall.ENOLINK + _ = syscall.ENOMEDIUM + _ = syscall.ENOMEM + _ = syscall.ENOMSG + _ = syscall.ENONET + _ = syscall.ENOPKG + _ = syscall.ENOPROTOOPT + _ = syscall.ENOSPC + _ = syscall.ENOSR + _ = syscall.ENOSTR + _ = syscall.ENOSYS + _ = syscall.ENOTBLK + _ = syscall.ENOTCONN + _ = syscall.ENOTDIR + _ = syscall.ENOTEMPTY + _ = syscall.ENOTNAM + _ = syscall.ENOTRECOVERABLE + _ = syscall.ENOTSOCK + _ = syscall.ENOTSUP + _ = syscall.ENOTTY + _ = syscall.ENOTUNIQ + _ = syscall.ENXIO + _ = syscall.EOPNOTSUPP + _ = syscall.EOVERFLOW + _ = syscall.EOWNERDEAD + _ = syscall.EPERM + _ = syscall.EPFNOSUPPORT + _ = syscall.EPIPE + _ = syscall.EPOLLERR + _ = syscall.EPOLLET + _ = syscall.EPOLLHUP + _ = syscall.EPOLLIN + _ = syscall.EPOLLMSG + _ = syscall.EPOLLONESHOT + _ = syscall.EPOLLOUT + _ = syscall.EPOLLPRI + _ = syscall.EPOLLRDBAND + _ = syscall.EPOLLRDHUP + _ = syscall.EPOLLRDNORM + _ = syscall.EPOLLWRBAND + _ = syscall.EPOLLWRNORM + _ = syscall.EPOLL_CLOEXEC + _ = syscall.EPOLL_CTL_ADD + _ = syscall.EPOLL_CTL_DEL + _ = syscall.EPOLL_CTL_MOD + _ = syscall.EPROTO + _ = syscall.EPROTONOSUPPORT + _ = syscall.EPROTOTYPE + _ = syscall.ERANGE + _ = syscall.EREMCHG + _ = syscall.EREMOTE + _ = syscall.EREMOTEIO + _ = syscall.ERESTART + _ = syscall.ERFKILL + _ = syscall.EROFS + _ = syscall.ESHUTDOWN + _ = syscall.ESOCKTNOSUPPORT + _ = syscall.ESPIPE + _ = syscall.ESRCH + _ = syscall.ESRMNT + _ = syscall.ESTALE + _ = syscall.ESTRPIPE + _ = syscall.ETH_P_1588 + _ = syscall.ETH_P_8021Q + _ = syscall.ETH_P_802_2 + _ = syscall.ETH_P_802_3 + _ = syscall.ETH_P_AARP + _ = syscall.ETH_P_ALL + _ = syscall.ETH_P_AOE + _ = syscall.ETH_P_ARCNET + _ = syscall.ETH_P_ARP + _ = syscall.ETH_P_ATALK + _ = syscall.ETH_P_ATMFATE + _ = syscall.ETH_P_ATMMPOA + _ = syscall.ETH_P_AX25 + _ = syscall.ETH_P_BPQ + _ = syscall.ETH_P_CAIF + _ = syscall.ETH_P_CAN + _ = syscall.ETH_P_CONTROL + _ = syscall.ETH_P_CUST + _ = syscall.ETH_P_DDCMP + _ = syscall.ETH_P_DEC + _ = syscall.ETH_P_DIAG + _ = syscall.ETH_P_DNA_DL + _ = syscall.ETH_P_DNA_RC + _ = syscall.ETH_P_DNA_RT + _ = syscall.ETH_P_DSA + _ = syscall.ETH_P_ECONET + _ = syscall.ETH_P_EDSA + _ = syscall.ETH_P_FCOE + _ = syscall.ETH_P_FIP + _ = syscall.ETH_P_HDLC + _ = syscall.ETH_P_IEEE802154 + _ = syscall.ETH_P_IEEEPUP + _ = syscall.ETH_P_IEEEPUPAT + _ = syscall.ETH_P_IP + _ = syscall.ETH_P_IPV6 + _ = syscall.ETH_P_IPX + _ = syscall.ETH_P_IRDA + _ = syscall.ETH_P_LAT + _ = syscall.ETH_P_LINK_CTL + _ = syscall.ETH_P_LOCALTALK + _ = syscall.ETH_P_LOOP + _ = syscall.ETH_P_MOBITEX + _ = syscall.ETH_P_MPLS_MC + _ = syscall.ETH_P_MPLS_UC + _ = syscall.ETH_P_PAE + _ = syscall.ETH_P_PAUSE + _ = syscall.ETH_P_PHONET +} diff --git a/test/std/syscall/symbols_linux_common_part03_test.go b/test/std/syscall/symbols_linux_common_part03_test.go new file mode 100644 index 0000000000..14dab05f7a --- /dev/null +++ b/test/std/syscall/symbols_linux_common_part03_test.go @@ -0,0 +1,229 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build linux + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_linuxcommon_Part03(t *testing.T) { + _ = t + _ = syscall.ETH_P_PPPTALK + _ = syscall.ETH_P_PPP_DISC + _ = syscall.ETH_P_PPP_MP + _ = syscall.ETH_P_PPP_SES + _ = syscall.ETH_P_PUP + _ = syscall.ETH_P_PUPAT + _ = syscall.ETH_P_RARP + _ = syscall.ETH_P_SCA + _ = syscall.ETH_P_SLOW + _ = syscall.ETH_P_SNAP + _ = syscall.ETH_P_TEB + _ = syscall.ETH_P_TIPC + _ = syscall.ETH_P_TRAILER + _ = syscall.ETH_P_TR_802_2 + _ = syscall.ETH_P_WAN_PPP + _ = syscall.ETH_P_WCCP + _ = syscall.ETH_P_X25 + _ = syscall.ETIME + _ = syscall.ETIMEDOUT + _ = syscall.ETOOMANYREFS + _ = syscall.ETXTBSY + _ = syscall.EUCLEAN + _ = syscall.EUNATCH + _ = syscall.EUSERS + _ = syscall.EWOULDBLOCK + _ = syscall.EXDEV + _ = syscall.EXFULL + _ = syscall.Environ + _ = syscall.EpollCreate + _ = syscall.EpollCreate1 + _ = syscall.EpollCtl + var _ syscall.EpollEvent + _ = syscall.EpollWait + var _ syscall.Errno + _ = syscall.Exec + _ = syscall.Exit + _ = syscall.FD_CLOEXEC + _ = syscall.FD_SETSIZE + _ = syscall.FLUSHO + _ = syscall.F_DUPFD + _ = syscall.F_DUPFD_CLOEXEC + _ = syscall.F_EXLCK + _ = syscall.F_GETFD + _ = syscall.F_GETFL + _ = syscall.F_GETLEASE + _ = syscall.F_GETLK + _ = syscall.F_GETLK64 + _ = syscall.F_GETOWN + _ = syscall.F_GETOWN_EX + _ = syscall.F_GETPIPE_SZ + _ = syscall.F_GETSIG + _ = syscall.F_LOCK + _ = syscall.F_NOTIFY + _ = syscall.F_OK + _ = syscall.F_RDLCK + _ = syscall.F_SETFD + _ = syscall.F_SETFL + _ = syscall.F_SETLEASE + _ = syscall.F_SETLK + _ = syscall.F_SETLK64 + _ = syscall.F_SETLKW + _ = syscall.F_SETLKW64 + _ = syscall.F_SETOWN + _ = syscall.F_SETOWN_EX + _ = syscall.F_SETPIPE_SZ + _ = syscall.F_SETSIG + _ = syscall.F_SHLCK + _ = syscall.F_TEST + _ = syscall.F_TLOCK + _ = syscall.F_ULOCK + _ = syscall.F_UNLCK + _ = syscall.F_WRLCK + _ = syscall.Faccessat + _ = syscall.Fallocate + _ = syscall.Fchdir + _ = syscall.Fchmod + _ = syscall.Fchmodat + _ = syscall.Fchown + _ = syscall.Fchownat + _ = syscall.FcntlFlock + var _ syscall.FdSet + _ = syscall.Fdatasync + _ = syscall.Flock + var _ syscall.Flock_t + _ = syscall.ForkExec + _ = syscall.ForkLock + var _ syscall.Fsid + _ = syscall.Fstat + _ = syscall.Fstatfs + _ = syscall.Fsync + _ = syscall.Ftruncate + _ = syscall.Futimes + _ = syscall.Futimesat + _ = syscall.Getcwd + _ = syscall.Getdents + _ = syscall.Getegid + _ = syscall.Getenv + _ = syscall.Geteuid + _ = syscall.Getgid + _ = syscall.Getgroups + _ = syscall.Getpagesize + _ = syscall.Getpeername + _ = syscall.Getpgid + _ = syscall.Getpgrp + _ = syscall.Getpid + _ = syscall.Getppid + _ = syscall.Getpriority + _ = syscall.Getrlimit + _ = syscall.Getrusage + _ = syscall.Getsockname + _ = syscall.GetsockoptICMPv6Filter + _ = syscall.GetsockoptIPMreq + _ = syscall.GetsockoptIPMreqn + _ = syscall.GetsockoptIPv6MTUInfo + _ = syscall.GetsockoptIPv6Mreq + _ = syscall.GetsockoptInet4Addr + _ = syscall.GetsockoptInt + _ = syscall.GetsockoptUcred + _ = syscall.Gettid + _ = syscall.Gettimeofday + _ = syscall.Getuid + _ = syscall.Getwd + _ = syscall.Getxattr + _ = syscall.HUPCL + _ = syscall.ICANON + _ = syscall.ICMPV6_FILTER + var _ syscall.ICMPv6Filter + _ = syscall.ICRNL + _ = syscall.IEXTEN + _ = syscall.IFA_ADDRESS + _ = syscall.IFA_ANYCAST + _ = syscall.IFA_BROADCAST + _ = syscall.IFA_CACHEINFO + _ = syscall.IFA_F_DADFAILED + _ = syscall.IFA_F_DEPRECATED + _ = syscall.IFA_F_HOMEADDRESS + _ = syscall.IFA_F_NODAD + _ = syscall.IFA_F_OPTIMISTIC + _ = syscall.IFA_F_PERMANENT + _ = syscall.IFA_F_SECONDARY + _ = syscall.IFA_F_TEMPORARY + _ = syscall.IFA_F_TENTATIVE + _ = syscall.IFA_LABEL + _ = syscall.IFA_LOCAL + _ = syscall.IFA_MAX + _ = syscall.IFA_MULTICAST + _ = syscall.IFA_UNSPEC + _ = syscall.IFF_ALLMULTI + _ = syscall.IFF_AUTOMEDIA + _ = syscall.IFF_BROADCAST + _ = syscall.IFF_DEBUG + _ = syscall.IFF_DYNAMIC + _ = syscall.IFF_LOOPBACK + _ = syscall.IFF_MASTER + _ = syscall.IFF_MULTICAST + _ = syscall.IFF_NOARP + _ = syscall.IFF_NOTRAILERS + _ = syscall.IFF_NO_PI + _ = syscall.IFF_ONE_QUEUE + _ = syscall.IFF_POINTOPOINT + _ = syscall.IFF_PORTSEL + _ = syscall.IFF_PROMISC + _ = syscall.IFF_RUNNING + _ = syscall.IFF_SLAVE + _ = syscall.IFF_TAP + _ = syscall.IFF_TUN + _ = syscall.IFF_TUN_EXCL + _ = syscall.IFF_UP + _ = syscall.IFF_VNET_HDR + _ = syscall.IFLA_ADDRESS + _ = syscall.IFLA_BROADCAST + _ = syscall.IFLA_COST + _ = syscall.IFLA_IFALIAS + _ = syscall.IFLA_IFNAME + _ = syscall.IFLA_LINK + _ = syscall.IFLA_LINKINFO + _ = syscall.IFLA_LINKMODE + _ = syscall.IFLA_MAP + _ = syscall.IFLA_MASTER + _ = syscall.IFLA_MAX + _ = syscall.IFLA_MTU + _ = syscall.IFLA_NET_NS_PID + _ = syscall.IFLA_OPERSTATE + _ = syscall.IFLA_PRIORITY + _ = syscall.IFLA_PROTINFO + _ = syscall.IFLA_QDISC + _ = syscall.IFLA_STATS + _ = syscall.IFLA_TXQLEN + _ = syscall.IFLA_UNSPEC + _ = syscall.IFLA_WEIGHT + _ = syscall.IFLA_WIRELESS + _ = syscall.IFNAMSIZ + _ = syscall.IGNBRK + _ = syscall.IGNCR + _ = syscall.IGNPAR + _ = syscall.IMAXBEL + _ = syscall.INLCR + _ = syscall.INPCK + _ = syscall.IN_ACCESS + _ = syscall.IN_ALL_EVENTS + _ = syscall.IN_ATTRIB + _ = syscall.IN_CLASSA_HOST + _ = syscall.IN_CLASSA_MAX + _ = syscall.IN_CLASSA_NET + _ = syscall.IN_CLASSA_NSHIFT + _ = syscall.IN_CLASSB_HOST + _ = syscall.IN_CLASSB_MAX + _ = syscall.IN_CLASSB_NET + _ = syscall.IN_CLASSB_NSHIFT + _ = syscall.IN_CLASSC_HOST + _ = syscall.IN_CLASSC_NET + _ = syscall.IN_CLASSC_NSHIFT + _ = syscall.IN_CLOEXEC + _ = syscall.IN_CLOSE + _ = syscall.IN_CLOSE_NOWRITE + _ = syscall.IN_CLOSE_WRITE +} diff --git a/test/std/syscall/symbols_linux_common_part04_test.go b/test/std/syscall/symbols_linux_common_part04_test.go new file mode 100644 index 0000000000..fc0ed10d29 --- /dev/null +++ b/test/std/syscall/symbols_linux_common_part04_test.go @@ -0,0 +1,232 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build linux + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_linuxcommon_Part04(t *testing.T) { + _ = t + _ = syscall.IN_CREATE + _ = syscall.IN_DELETE + _ = syscall.IN_DELETE_SELF + _ = syscall.IN_DONT_FOLLOW + _ = syscall.IN_EXCL_UNLINK + _ = syscall.IN_IGNORED + _ = syscall.IN_ISDIR + _ = syscall.IN_LOOPBACKNET + _ = syscall.IN_MASK_ADD + _ = syscall.IN_MODIFY + _ = syscall.IN_MOVE + _ = syscall.IN_MOVED_FROM + _ = syscall.IN_MOVED_TO + _ = syscall.IN_MOVE_SELF + _ = syscall.IN_NONBLOCK + _ = syscall.IN_ONESHOT + _ = syscall.IN_ONLYDIR + _ = syscall.IN_OPEN + _ = syscall.IN_Q_OVERFLOW + _ = syscall.IN_UNMOUNT + var _ syscall.IPMreq + var _ syscall.IPMreqn + _ = syscall.IPPROTO_AH + _ = syscall.IPPROTO_COMP + _ = syscall.IPPROTO_DCCP + _ = syscall.IPPROTO_DSTOPTS + _ = syscall.IPPROTO_EGP + _ = syscall.IPPROTO_ENCAP + _ = syscall.IPPROTO_ESP + _ = syscall.IPPROTO_FRAGMENT + _ = syscall.IPPROTO_GRE + _ = syscall.IPPROTO_HOPOPTS + _ = syscall.IPPROTO_ICMP + _ = syscall.IPPROTO_ICMPV6 + _ = syscall.IPPROTO_IDP + _ = syscall.IPPROTO_IGMP + _ = syscall.IPPROTO_IP + _ = syscall.IPPROTO_IPIP + _ = syscall.IPPROTO_IPV6 + _ = syscall.IPPROTO_MTP + _ = syscall.IPPROTO_NONE + _ = syscall.IPPROTO_PIM + _ = syscall.IPPROTO_PUP + _ = syscall.IPPROTO_RAW + _ = syscall.IPPROTO_ROUTING + _ = syscall.IPPROTO_RSVP + _ = syscall.IPPROTO_SCTP + _ = syscall.IPPROTO_TCP + _ = syscall.IPPROTO_TP + _ = syscall.IPPROTO_UDP + _ = syscall.IPPROTO_UDPLITE + _ = syscall.IPV6_2292DSTOPTS + _ = syscall.IPV6_2292HOPLIMIT + _ = syscall.IPV6_2292HOPOPTS + _ = syscall.IPV6_2292PKTINFO + _ = syscall.IPV6_2292PKTOPTIONS + _ = syscall.IPV6_2292RTHDR + _ = syscall.IPV6_ADDRFORM + _ = syscall.IPV6_ADD_MEMBERSHIP + _ = syscall.IPV6_AUTHHDR + _ = syscall.IPV6_CHECKSUM + _ = syscall.IPV6_DROP_MEMBERSHIP + _ = syscall.IPV6_DSTOPTS + _ = syscall.IPV6_HOPLIMIT + _ = syscall.IPV6_HOPOPTS + _ = syscall.IPV6_IPSEC_POLICY + _ = syscall.IPV6_JOIN_ANYCAST + _ = syscall.IPV6_JOIN_GROUP + _ = syscall.IPV6_LEAVE_ANYCAST + _ = syscall.IPV6_LEAVE_GROUP + _ = syscall.IPV6_MTU + _ = syscall.IPV6_MTU_DISCOVER + _ = syscall.IPV6_MULTICAST_HOPS + _ = syscall.IPV6_MULTICAST_IF + _ = syscall.IPV6_MULTICAST_LOOP + _ = syscall.IPV6_NEXTHOP + _ = syscall.IPV6_PKTINFO + _ = syscall.IPV6_PMTUDISC_DO + _ = syscall.IPV6_PMTUDISC_DONT + _ = syscall.IPV6_PMTUDISC_PROBE + _ = syscall.IPV6_PMTUDISC_WANT + _ = syscall.IPV6_RECVDSTOPTS + _ = syscall.IPV6_RECVERR + _ = syscall.IPV6_RECVHOPLIMIT + _ = syscall.IPV6_RECVHOPOPTS + _ = syscall.IPV6_RECVPKTINFO + _ = syscall.IPV6_RECVRTHDR + _ = syscall.IPV6_RECVTCLASS + _ = syscall.IPV6_ROUTER_ALERT + _ = syscall.IPV6_RTHDR + _ = syscall.IPV6_RTHDRDSTOPTS + _ = syscall.IPV6_RTHDR_LOOSE + _ = syscall.IPV6_RTHDR_STRICT + _ = syscall.IPV6_RTHDR_TYPE_0 + _ = syscall.IPV6_RXDSTOPTS + _ = syscall.IPV6_RXHOPOPTS + _ = syscall.IPV6_TCLASS + _ = syscall.IPV6_UNICAST_HOPS + _ = syscall.IPV6_V6ONLY + _ = syscall.IPV6_XFRM_POLICY + _ = syscall.IP_ADD_MEMBERSHIP + _ = syscall.IP_ADD_SOURCE_MEMBERSHIP + _ = syscall.IP_BLOCK_SOURCE + _ = syscall.IP_DEFAULT_MULTICAST_LOOP + _ = syscall.IP_DEFAULT_MULTICAST_TTL + _ = syscall.IP_DF + _ = syscall.IP_DROP_MEMBERSHIP + _ = syscall.IP_DROP_SOURCE_MEMBERSHIP + _ = syscall.IP_FREEBIND + _ = syscall.IP_HDRINCL + _ = syscall.IP_IPSEC_POLICY + _ = syscall.IP_MAXPACKET + _ = syscall.IP_MAX_MEMBERSHIPS + _ = syscall.IP_MF + _ = syscall.IP_MINTTL + _ = syscall.IP_MSFILTER + _ = syscall.IP_MSS + _ = syscall.IP_MTU + _ = syscall.IP_MTU_DISCOVER + _ = syscall.IP_MULTICAST_IF + _ = syscall.IP_MULTICAST_LOOP + _ = syscall.IP_MULTICAST_TTL + _ = syscall.IP_OFFMASK + _ = syscall.IP_OPTIONS + _ = syscall.IP_ORIGDSTADDR + _ = syscall.IP_PASSSEC + _ = syscall.IP_PKTINFO + _ = syscall.IP_PKTOPTIONS + _ = syscall.IP_PMTUDISC + _ = syscall.IP_PMTUDISC_DO + _ = syscall.IP_PMTUDISC_DONT + _ = syscall.IP_PMTUDISC_PROBE + _ = syscall.IP_PMTUDISC_WANT + _ = syscall.IP_RECVERR + _ = syscall.IP_RECVOPTS + _ = syscall.IP_RECVORIGDSTADDR + _ = syscall.IP_RECVRETOPTS + _ = syscall.IP_RECVTOS + _ = syscall.IP_RECVTTL + _ = syscall.IP_RETOPTS + _ = syscall.IP_RF + _ = syscall.IP_ROUTER_ALERT + _ = syscall.IP_TOS + _ = syscall.IP_TRANSPARENT + _ = syscall.IP_TTL + _ = syscall.IP_UNBLOCK_SOURCE + _ = syscall.IP_XFRM_POLICY + var _ syscall.IPv6MTUInfo + var _ syscall.IPv6Mreq + _ = syscall.ISIG + _ = syscall.ISTRIP + _ = syscall.IUCLC + _ = syscall.IUTF8 + _ = syscall.IXANY + _ = syscall.IXOFF + _ = syscall.IXON + var _ syscall.IfAddrmsg + var _ syscall.IfInfomsg + _ = syscall.ImplementsGetwd + var _ syscall.Inet4Pktinfo + var _ syscall.Inet6Pktinfo + _ = syscall.InotifyAddWatch + var _ syscall.InotifyEvent + _ = syscall.InotifyInit + _ = syscall.InotifyInit1 + _ = syscall.InotifyRmWatch + var _ syscall.Iovec + _ = syscall.Kill + _ = syscall.Klogctl + _ = syscall.LINUX_REBOOT_CMD_CAD_OFF + _ = syscall.LINUX_REBOOT_CMD_CAD_ON + _ = syscall.LINUX_REBOOT_CMD_HALT + _ = syscall.LINUX_REBOOT_CMD_KEXEC + _ = syscall.LINUX_REBOOT_CMD_POWER_OFF + _ = syscall.LINUX_REBOOT_CMD_RESTART + _ = syscall.LINUX_REBOOT_CMD_RESTART2 + _ = syscall.LINUX_REBOOT_CMD_SW_SUSPEND + _ = syscall.LINUX_REBOOT_MAGIC1 + _ = syscall.LINUX_REBOOT_MAGIC2 + _ = syscall.LOCK_EX + _ = syscall.LOCK_NB + _ = syscall.LOCK_SH + _ = syscall.LOCK_UN + _ = syscall.Lchown + var _ syscall.Linger + _ = syscall.Link + _ = syscall.Listen + _ = syscall.Listxattr + _ = syscall.LsfJump + _ = syscall.LsfSocket + _ = syscall.LsfStmt + _ = syscall.Lstat + _ = syscall.MADV_DOFORK + _ = syscall.MADV_DONTFORK + _ = syscall.MADV_DONTNEED + _ = syscall.MADV_HUGEPAGE + _ = syscall.MADV_HWPOISON + _ = syscall.MADV_MERGEABLE + _ = syscall.MADV_NOHUGEPAGE + _ = syscall.MADV_NORMAL + _ = syscall.MADV_RANDOM + _ = syscall.MADV_REMOVE + _ = syscall.MADV_SEQUENTIAL + _ = syscall.MADV_UNMERGEABLE + _ = syscall.MADV_WILLNEED + _ = syscall.MAP_ANON + _ = syscall.MAP_ANONYMOUS + _ = syscall.MAP_DENYWRITE + _ = syscall.MAP_EXECUTABLE + _ = syscall.MAP_FILE + _ = syscall.MAP_FIXED + _ = syscall.MAP_GROWSDOWN + _ = syscall.MAP_HUGETLB + _ = syscall.MAP_LOCKED + _ = syscall.MAP_NONBLOCK + _ = syscall.MAP_NORESERVE + _ = syscall.MAP_POPULATE + _ = syscall.MAP_PRIVATE + _ = syscall.MAP_SHARED +} diff --git a/test/std/syscall/symbols_linux_common_part05_test.go b/test/std/syscall/symbols_linux_common_part05_test.go new file mode 100644 index 0000000000..906456fb10 --- /dev/null +++ b/test/std/syscall/symbols_linux_common_part05_test.go @@ -0,0 +1,232 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build linux + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_linuxcommon_Part05(t *testing.T) { + _ = t + _ = syscall.MAP_STACK + _ = syscall.MAP_TYPE + _ = syscall.MCL_CURRENT + _ = syscall.MCL_FUTURE + _ = syscall.MNT_DETACH + _ = syscall.MNT_EXPIRE + _ = syscall.MNT_FORCE + _ = syscall.MSG_CMSG_CLOEXEC + _ = syscall.MSG_CONFIRM + _ = syscall.MSG_CTRUNC + _ = syscall.MSG_DONTROUTE + _ = syscall.MSG_DONTWAIT + _ = syscall.MSG_EOR + _ = syscall.MSG_ERRQUEUE + _ = syscall.MSG_FASTOPEN + _ = syscall.MSG_FIN + _ = syscall.MSG_MORE + _ = syscall.MSG_NOSIGNAL + _ = syscall.MSG_OOB + _ = syscall.MSG_PEEK + _ = syscall.MSG_PROXY + _ = syscall.MSG_RST + _ = syscall.MSG_SYN + _ = syscall.MSG_TRUNC + _ = syscall.MSG_TRYHARD + _ = syscall.MSG_WAITALL + _ = syscall.MSG_WAITFORONE + _ = syscall.MS_ACTIVE + _ = syscall.MS_ASYNC + _ = syscall.MS_BIND + _ = syscall.MS_DIRSYNC + _ = syscall.MS_INVALIDATE + _ = syscall.MS_I_VERSION + _ = syscall.MS_KERNMOUNT + _ = syscall.MS_MANDLOCK + _ = syscall.MS_MGC_MSK + _ = syscall.MS_MGC_VAL + _ = syscall.MS_MOVE + _ = syscall.MS_NOATIME + _ = syscall.MS_NODEV + _ = syscall.MS_NODIRATIME + _ = syscall.MS_NOEXEC + _ = syscall.MS_NOSUID + _ = syscall.MS_NOUSER + _ = syscall.MS_POSIXACL + _ = syscall.MS_PRIVATE + _ = syscall.MS_RDONLY + _ = syscall.MS_REC + _ = syscall.MS_RELATIME + _ = syscall.MS_REMOUNT + _ = syscall.MS_RMT_MASK + _ = syscall.MS_SHARED + _ = syscall.MS_SILENT + _ = syscall.MS_SLAVE + _ = syscall.MS_STRICTATIME + _ = syscall.MS_SYNC + _ = syscall.MS_SYNCHRONOUS + _ = syscall.MS_UNBINDABLE + _ = syscall.Madvise + _ = syscall.Mkdir + _ = syscall.Mkdirat + _ = syscall.Mkfifo + _ = syscall.Mknod + _ = syscall.Mknodat + _ = syscall.Mlock + _ = syscall.Mlockall + _ = syscall.Mmap + _ = syscall.Mount + _ = syscall.Mprotect + var _ syscall.Msghdr + _ = syscall.Munlock + _ = syscall.Munlockall + _ = syscall.Munmap + _ = syscall.NAME_MAX + _ = syscall.NETLINK_ADD_MEMBERSHIP + _ = syscall.NETLINK_AUDIT + _ = syscall.NETLINK_BROADCAST_ERROR + _ = syscall.NETLINK_CONNECTOR + _ = syscall.NETLINK_DNRTMSG + _ = syscall.NETLINK_DROP_MEMBERSHIP + _ = syscall.NETLINK_ECRYPTFS + _ = syscall.NETLINK_FIB_LOOKUP + _ = syscall.NETLINK_FIREWALL + _ = syscall.NETLINK_GENERIC + _ = syscall.NETLINK_INET_DIAG + _ = syscall.NETLINK_IP6_FW + _ = syscall.NETLINK_ISCSI + _ = syscall.NETLINK_KOBJECT_UEVENT + _ = syscall.NETLINK_NETFILTER + _ = syscall.NETLINK_NFLOG + _ = syscall.NETLINK_NO_ENOBUFS + _ = syscall.NETLINK_PKTINFO + _ = syscall.NETLINK_ROUTE + _ = syscall.NETLINK_SCSITRANSPORT + _ = syscall.NETLINK_SELINUX + _ = syscall.NETLINK_UNUSED + _ = syscall.NETLINK_USERSOCK + _ = syscall.NETLINK_XFRM + _ = syscall.NLA_ALIGNTO + _ = syscall.NLA_F_NESTED + _ = syscall.NLA_F_NET_BYTEORDER + _ = syscall.NLA_HDRLEN + _ = syscall.NLMSG_ALIGNTO + _ = syscall.NLMSG_DONE + _ = syscall.NLMSG_ERROR + _ = syscall.NLMSG_HDRLEN + _ = syscall.NLMSG_MIN_TYPE + _ = syscall.NLMSG_NOOP + _ = syscall.NLMSG_OVERRUN + _ = syscall.NLM_F_ACK + _ = syscall.NLM_F_APPEND + _ = syscall.NLM_F_ATOMIC + _ = syscall.NLM_F_CREATE + _ = syscall.NLM_F_DUMP + _ = syscall.NLM_F_ECHO + _ = syscall.NLM_F_EXCL + _ = syscall.NLM_F_MATCH + _ = syscall.NLM_F_MULTI + _ = syscall.NLM_F_REPLACE + _ = syscall.NLM_F_REQUEST + _ = syscall.NLM_F_ROOT + _ = syscall.NOFLSH + _ = syscall.Nanosleep + var _ syscall.NetlinkMessage + _ = syscall.NetlinkRIB + var _ syscall.NetlinkRouteAttr + var _ syscall.NetlinkRouteRequest + var _ syscall.NlAttr + var _ syscall.NlMsgerr + var _ syscall.NlMsghdr + _ = syscall.NsecToTimespec + _ = syscall.NsecToTimeval + _ = syscall.OCRNL + _ = syscall.OFDEL + _ = syscall.OFILL + _ = syscall.OLCUC + _ = syscall.ONLCR + _ = syscall.ONLRET + _ = syscall.ONOCR + _ = syscall.OPOST + _ = syscall.O_ACCMODE + _ = syscall.O_APPEND + _ = syscall.O_ASYNC + _ = syscall.O_CLOEXEC + _ = syscall.O_CREAT + _ = syscall.O_DIRECT + _ = syscall.O_DIRECTORY + _ = syscall.O_DSYNC + _ = syscall.O_EXCL + _ = syscall.O_FSYNC + _ = syscall.O_LARGEFILE + _ = syscall.O_NDELAY + _ = syscall.O_NOATIME + _ = syscall.O_NOCTTY + _ = syscall.O_NOFOLLOW + _ = syscall.O_NONBLOCK + _ = syscall.O_RDONLY + _ = syscall.O_RDWR + _ = syscall.O_RSYNC + _ = syscall.O_SYNC + _ = syscall.O_TRUNC + _ = syscall.O_WRONLY + _ = syscall.Open + _ = syscall.Openat + _ = syscall.PACKET_ADD_MEMBERSHIP + _ = syscall.PACKET_BROADCAST + _ = syscall.PACKET_DROP_MEMBERSHIP + _ = syscall.PACKET_FASTROUTE + _ = syscall.PACKET_HOST + _ = syscall.PACKET_LOOPBACK + _ = syscall.PACKET_MR_ALLMULTI + _ = syscall.PACKET_MR_MULTICAST + _ = syscall.PACKET_MR_PROMISC + _ = syscall.PACKET_MULTICAST + _ = syscall.PACKET_OTHERHOST + _ = syscall.PACKET_OUTGOING + _ = syscall.PACKET_RECV_OUTPUT + _ = syscall.PACKET_RX_RING + _ = syscall.PACKET_STATISTICS + _ = syscall.PARENB + _ = syscall.PARMRK + _ = syscall.PARODD + _ = syscall.PENDIN + _ = syscall.PRIO_PGRP + _ = syscall.PRIO_PROCESS + _ = syscall.PRIO_USER + _ = syscall.PROT_EXEC + _ = syscall.PROT_GROWSDOWN + _ = syscall.PROT_GROWSUP + _ = syscall.PROT_NONE + _ = syscall.PROT_READ + _ = syscall.PROT_WRITE + _ = syscall.PR_CAPBSET_DROP + _ = syscall.PR_CAPBSET_READ + _ = syscall.PR_ENDIAN_BIG + _ = syscall.PR_ENDIAN_LITTLE + _ = syscall.PR_ENDIAN_PPC_LITTLE + _ = syscall.PR_FPEMU_NOPRINT + _ = syscall.PR_FPEMU_SIGFPE + _ = syscall.PR_FP_EXC_ASYNC + _ = syscall.PR_FP_EXC_DISABLED + _ = syscall.PR_FP_EXC_DIV + _ = syscall.PR_FP_EXC_INV + _ = syscall.PR_FP_EXC_NONRECOV + _ = syscall.PR_FP_EXC_OVF + _ = syscall.PR_FP_EXC_PRECISE + _ = syscall.PR_FP_EXC_RES + _ = syscall.PR_FP_EXC_SW_ENABLE + _ = syscall.PR_FP_EXC_UND + _ = syscall.PR_GET_DUMPABLE + _ = syscall.PR_GET_ENDIAN + _ = syscall.PR_GET_FPEMU + _ = syscall.PR_GET_FPEXC + _ = syscall.PR_GET_KEEPCAPS + _ = syscall.PR_GET_NAME + _ = syscall.PR_GET_PDEATHSIG + _ = syscall.PR_GET_SECCOMP + _ = syscall.PR_GET_SECUREBITS + _ = syscall.PR_GET_TIMERSLACK +} diff --git a/test/std/syscall/symbols_linux_common_part06_test.go b/test/std/syscall/symbols_linux_common_part06_test.go new file mode 100644 index 0000000000..5756c17085 --- /dev/null +++ b/test/std/syscall/symbols_linux_common_part06_test.go @@ -0,0 +1,231 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build linux + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_linuxcommon_Part06(t *testing.T) { + _ = t + _ = syscall.PR_GET_TIMING + _ = syscall.PR_GET_TSC + _ = syscall.PR_GET_UNALIGN + _ = syscall.PR_MCE_KILL + _ = syscall.PR_MCE_KILL_CLEAR + _ = syscall.PR_MCE_KILL_DEFAULT + _ = syscall.PR_MCE_KILL_EARLY + _ = syscall.PR_MCE_KILL_GET + _ = syscall.PR_MCE_KILL_LATE + _ = syscall.PR_MCE_KILL_SET + _ = syscall.PR_SET_DUMPABLE + _ = syscall.PR_SET_ENDIAN + _ = syscall.PR_SET_FPEMU + _ = syscall.PR_SET_FPEXC + _ = syscall.PR_SET_KEEPCAPS + _ = syscall.PR_SET_NAME + _ = syscall.PR_SET_PDEATHSIG + _ = syscall.PR_SET_PTRACER + _ = syscall.PR_SET_SECCOMP + _ = syscall.PR_SET_SECUREBITS + _ = syscall.PR_SET_TIMERSLACK + _ = syscall.PR_SET_TIMING + _ = syscall.PR_SET_TSC + _ = syscall.PR_SET_UNALIGN + _ = syscall.PR_TASK_PERF_EVENTS_DISABLE + _ = syscall.PR_TASK_PERF_EVENTS_ENABLE + _ = syscall.PR_TIMING_STATISTICAL + _ = syscall.PR_TIMING_TIMESTAMP + _ = syscall.PR_TSC_ENABLE + _ = syscall.PR_TSC_SIGSEGV + _ = syscall.PR_UNALIGN_NOPRINT + _ = syscall.PR_UNALIGN_SIGBUS + _ = syscall.PTRACE_ATTACH + _ = syscall.PTRACE_CONT + _ = syscall.PTRACE_DETACH + _ = syscall.PTRACE_EVENT_CLONE + _ = syscall.PTRACE_EVENT_EXEC + _ = syscall.PTRACE_EVENT_EXIT + _ = syscall.PTRACE_EVENT_FORK + _ = syscall.PTRACE_EVENT_VFORK + _ = syscall.PTRACE_EVENT_VFORK_DONE + _ = syscall.PTRACE_GETEVENTMSG + _ = syscall.PTRACE_GETREGS + _ = syscall.PTRACE_GETREGSET + _ = syscall.PTRACE_GETSIGINFO + _ = syscall.PTRACE_KILL + _ = syscall.PTRACE_O_MASK + _ = syscall.PTRACE_O_TRACECLONE + _ = syscall.PTRACE_O_TRACEEXEC + _ = syscall.PTRACE_O_TRACEEXIT + _ = syscall.PTRACE_O_TRACEFORK + _ = syscall.PTRACE_O_TRACESYSGOOD + _ = syscall.PTRACE_O_TRACEVFORK + _ = syscall.PTRACE_O_TRACEVFORKDONE + _ = syscall.PTRACE_PEEKDATA + _ = syscall.PTRACE_PEEKTEXT + _ = syscall.PTRACE_PEEKUSR + _ = syscall.PTRACE_POKEDATA + _ = syscall.PTRACE_POKETEXT + _ = syscall.PTRACE_POKEUSR + _ = syscall.PTRACE_SETOPTIONS + _ = syscall.PTRACE_SETREGS + _ = syscall.PTRACE_SETREGSET + _ = syscall.PTRACE_SETSIGINFO + _ = syscall.PTRACE_SINGLESTEP + _ = syscall.PTRACE_SYSCALL + _ = syscall.PTRACE_TRACEME + _ = syscall.ParseDirent + _ = syscall.ParseNetlinkMessage + _ = syscall.ParseNetlinkRouteAttr + _ = syscall.ParseSocketControlMessage + _ = syscall.ParseUnixCredentials + _ = syscall.ParseUnixRights + _ = syscall.PathMax + _ = syscall.Pause + _ = syscall.Pipe + _ = syscall.Pipe2 + _ = syscall.PivotRoot + _ = syscall.Pread + var _ syscall.ProcAttr + _ = syscall.PtraceAttach + _ = syscall.PtraceCont + _ = syscall.PtraceDetach + _ = syscall.PtraceGetEventMsg + _ = syscall.PtraceGetRegs + _ = syscall.PtracePeekData + _ = syscall.PtracePeekText + _ = syscall.PtracePokeData + _ = syscall.PtracePokeText + var _ syscall.PtraceRegs + _ = syscall.PtraceSetOptions + _ = syscall.PtraceSetRegs + _ = syscall.PtraceSingleStep + _ = syscall.PtraceSyscall + _ = syscall.Pwrite + _ = syscall.RLIMIT_AS + _ = syscall.RLIMIT_CORE + _ = syscall.RLIMIT_CPU + _ = syscall.RLIMIT_DATA + _ = syscall.RLIMIT_FSIZE + _ = syscall.RLIMIT_NOFILE + _ = syscall.RLIMIT_STACK + _ = syscall.RLIM_INFINITY + _ = syscall.RTAX_ADVMSS + _ = syscall.RTAX_CWND + _ = syscall.RTAX_FEATURES + _ = syscall.RTAX_FEATURE_ALLFRAG + _ = syscall.RTAX_FEATURE_ECN + _ = syscall.RTAX_FEATURE_SACK + _ = syscall.RTAX_FEATURE_TIMESTAMP + _ = syscall.RTAX_HOPLIMIT + _ = syscall.RTAX_INITCWND + _ = syscall.RTAX_INITRWND + _ = syscall.RTAX_LOCK + _ = syscall.RTAX_MAX + _ = syscall.RTAX_MTU + _ = syscall.RTAX_REORDERING + _ = syscall.RTAX_RTO_MIN + _ = syscall.RTAX_RTT + _ = syscall.RTAX_RTTVAR + _ = syscall.RTAX_SSTHRESH + _ = syscall.RTAX_UNSPEC + _ = syscall.RTAX_WINDOW + _ = syscall.RTA_ALIGNTO + _ = syscall.RTA_CACHEINFO + _ = syscall.RTA_DST + _ = syscall.RTA_FLOW + _ = syscall.RTA_GATEWAY + _ = syscall.RTA_IIF + _ = syscall.RTA_MAX + _ = syscall.RTA_METRICS + _ = syscall.RTA_MULTIPATH + _ = syscall.RTA_OIF + _ = syscall.RTA_PREFSRC + _ = syscall.RTA_PRIORITY + _ = syscall.RTA_SRC + _ = syscall.RTA_TABLE + _ = syscall.RTA_UNSPEC + _ = syscall.RTCF_DIRECTSRC + _ = syscall.RTCF_DOREDIRECT + _ = syscall.RTCF_LOG + _ = syscall.RTCF_MASQ + _ = syscall.RTCF_NAT + _ = syscall.RTCF_VALVE + _ = syscall.RTF_ADDRCLASSMASK + _ = syscall.RTF_ADDRCONF + _ = syscall.RTF_ALLONLINK + _ = syscall.RTF_BROADCAST + _ = syscall.RTF_CACHE + _ = syscall.RTF_DEFAULT + _ = syscall.RTF_DYNAMIC + _ = syscall.RTF_FLOW + _ = syscall.RTF_GATEWAY + _ = syscall.RTF_HOST + _ = syscall.RTF_INTERFACE + _ = syscall.RTF_IRTT + _ = syscall.RTF_LINKRT + _ = syscall.RTF_LOCAL + _ = syscall.RTF_MODIFIED + _ = syscall.RTF_MSS + _ = syscall.RTF_MTU + _ = syscall.RTF_MULTICAST + _ = syscall.RTF_NAT + _ = syscall.RTF_NOFORWARD + _ = syscall.RTF_NONEXTHOP + _ = syscall.RTF_NOPMTUDISC + _ = syscall.RTF_POLICY + _ = syscall.RTF_REINSTATE + _ = syscall.RTF_REJECT + _ = syscall.RTF_STATIC + _ = syscall.RTF_THROW + _ = syscall.RTF_UP + _ = syscall.RTF_WINDOW + _ = syscall.RTF_XRESOLVE + _ = syscall.RTM_BASE + _ = syscall.RTM_DELACTION + _ = syscall.RTM_DELADDR + _ = syscall.RTM_DELADDRLABEL + _ = syscall.RTM_DELLINK + _ = syscall.RTM_DELNEIGH + _ = syscall.RTM_DELQDISC + _ = syscall.RTM_DELROUTE + _ = syscall.RTM_DELRULE + _ = syscall.RTM_DELTCLASS + _ = syscall.RTM_DELTFILTER + _ = syscall.RTM_F_CLONED + _ = syscall.RTM_F_EQUALIZE + _ = syscall.RTM_F_NOTIFY + _ = syscall.RTM_F_PREFIX + _ = syscall.RTM_GETACTION + _ = syscall.RTM_GETADDR + _ = syscall.RTM_GETADDRLABEL + _ = syscall.RTM_GETANYCAST + _ = syscall.RTM_GETDCB + _ = syscall.RTM_GETLINK + _ = syscall.RTM_GETMULTICAST + _ = syscall.RTM_GETNEIGH + _ = syscall.RTM_GETNEIGHTBL + _ = syscall.RTM_GETQDISC + _ = syscall.RTM_GETROUTE + _ = syscall.RTM_GETRULE + _ = syscall.RTM_GETTCLASS + _ = syscall.RTM_GETTFILTER + _ = syscall.RTM_MAX + _ = syscall.RTM_NEWACTION + _ = syscall.RTM_NEWADDR + _ = syscall.RTM_NEWADDRLABEL + _ = syscall.RTM_NEWLINK + _ = syscall.RTM_NEWNDUSEROPT + _ = syscall.RTM_NEWNEIGH + _ = syscall.RTM_NEWNEIGHTBL + _ = syscall.RTM_NEWPREFIX + _ = syscall.RTM_NEWQDISC + _ = syscall.RTM_NEWROUTE + _ = syscall.RTM_NEWRULE + _ = syscall.RTM_NEWTCLASS + _ = syscall.RTM_NEWTFILTER + _ = syscall.RTM_NR_FAMILIES +} diff --git a/test/std/syscall/symbols_linux_common_part07_test.go b/test/std/syscall/symbols_linux_common_part07_test.go new file mode 100644 index 0000000000..94744e83a9 --- /dev/null +++ b/test/std/syscall/symbols_linux_common_part07_test.go @@ -0,0 +1,230 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build linux + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_linuxcommon_Part07(t *testing.T) { + _ = t + _ = syscall.RTM_NR_MSGTYPES + _ = syscall.RTM_SETDCB + _ = syscall.RTM_SETLINK + _ = syscall.RTM_SETNEIGHTBL + _ = syscall.RTNH_ALIGNTO + _ = syscall.RTNH_F_DEAD + _ = syscall.RTNH_F_ONLINK + _ = syscall.RTNH_F_PERVASIVE + _ = syscall.RTNLGRP_IPV4_IFADDR + _ = syscall.RTNLGRP_IPV4_MROUTE + _ = syscall.RTNLGRP_IPV4_ROUTE + _ = syscall.RTNLGRP_IPV4_RULE + _ = syscall.RTNLGRP_IPV6_IFADDR + _ = syscall.RTNLGRP_IPV6_IFINFO + _ = syscall.RTNLGRP_IPV6_MROUTE + _ = syscall.RTNLGRP_IPV6_PREFIX + _ = syscall.RTNLGRP_IPV6_ROUTE + _ = syscall.RTNLGRP_IPV6_RULE + _ = syscall.RTNLGRP_LINK + _ = syscall.RTNLGRP_ND_USEROPT + _ = syscall.RTNLGRP_NEIGH + _ = syscall.RTNLGRP_NONE + _ = syscall.RTNLGRP_NOTIFY + _ = syscall.RTNLGRP_TC + _ = syscall.RTN_ANYCAST + _ = syscall.RTN_BLACKHOLE + _ = syscall.RTN_BROADCAST + _ = syscall.RTN_LOCAL + _ = syscall.RTN_MAX + _ = syscall.RTN_MULTICAST + _ = syscall.RTN_NAT + _ = syscall.RTN_PROHIBIT + _ = syscall.RTN_THROW + _ = syscall.RTN_UNICAST + _ = syscall.RTN_UNREACHABLE + _ = syscall.RTN_UNSPEC + _ = syscall.RTN_XRESOLVE + _ = syscall.RTPROT_BIRD + _ = syscall.RTPROT_BOOT + _ = syscall.RTPROT_DHCP + _ = syscall.RTPROT_DNROUTED + _ = syscall.RTPROT_GATED + _ = syscall.RTPROT_KERNEL + _ = syscall.RTPROT_MRT + _ = syscall.RTPROT_NTK + _ = syscall.RTPROT_RA + _ = syscall.RTPROT_REDIRECT + _ = syscall.RTPROT_STATIC + _ = syscall.RTPROT_UNSPEC + _ = syscall.RTPROT_XORP + _ = syscall.RTPROT_ZEBRA + _ = syscall.RT_CLASS_DEFAULT + _ = syscall.RT_CLASS_LOCAL + _ = syscall.RT_CLASS_MAIN + _ = syscall.RT_CLASS_MAX + _ = syscall.RT_CLASS_UNSPEC + _ = syscall.RT_SCOPE_HOST + _ = syscall.RT_SCOPE_LINK + _ = syscall.RT_SCOPE_NOWHERE + _ = syscall.RT_SCOPE_SITE + _ = syscall.RT_SCOPE_UNIVERSE + _ = syscall.RT_TABLE_COMPAT + _ = syscall.RT_TABLE_DEFAULT + _ = syscall.RT_TABLE_LOCAL + _ = syscall.RT_TABLE_MAIN + _ = syscall.RT_TABLE_MAX + _ = syscall.RT_TABLE_UNSPEC + _ = syscall.RUSAGE_CHILDREN + _ = syscall.RUSAGE_SELF + _ = syscall.RUSAGE_THREAD + var _ syscall.RawConn + var _ syscall.RawSockaddr + var _ syscall.RawSockaddrAny + var _ syscall.RawSockaddrInet4 + var _ syscall.RawSockaddrInet6 + var _ syscall.RawSockaddrLinklayer + var _ syscall.RawSockaddrNetlink + var _ syscall.RawSockaddrUnix + _ = syscall.RawSyscall + _ = syscall.RawSyscall6 + _ = syscall.Read + _ = syscall.ReadDirent + _ = syscall.Readlink + _ = syscall.Reboot + _ = syscall.Recvfrom + _ = syscall.Recvmsg + _ = syscall.Removexattr + _ = syscall.Rename + _ = syscall.Renameat + var _ syscall.Rlimit + _ = syscall.Rmdir + var _ syscall.RtAttr + var _ syscall.RtGenmsg + var _ syscall.RtMsg + var _ syscall.RtNexthop + var _ syscall.Rusage + _ = syscall.SCM_CREDENTIALS + _ = syscall.SCM_RIGHTS + _ = syscall.SCM_TIMESTAMP + _ = syscall.SCM_TIMESTAMPING + _ = syscall.SCM_TIMESTAMPNS + _ = syscall.SHUT_RD + _ = syscall.SHUT_RDWR + _ = syscall.SHUT_WR + _ = syscall.SIGABRT + _ = syscall.SIGALRM + _ = syscall.SIGBUS + _ = syscall.SIGCHLD + _ = syscall.SIGCLD + _ = syscall.SIGCONT + _ = syscall.SIGFPE + _ = syscall.SIGHUP + _ = syscall.SIGILL + _ = syscall.SIGINT + _ = syscall.SIGIO + _ = syscall.SIGIOT + _ = syscall.SIGKILL + _ = syscall.SIGPIPE + _ = syscall.SIGPOLL + _ = syscall.SIGPROF + _ = syscall.SIGPWR + _ = syscall.SIGQUIT + _ = syscall.SIGSEGV + _ = syscall.SIGSTKFLT + _ = syscall.SIGSTOP + _ = syscall.SIGSYS + _ = syscall.SIGTERM + _ = syscall.SIGTRAP + _ = syscall.SIGTSTP + _ = syscall.SIGTTIN + _ = syscall.SIGTTOU + _ = syscall.SIGUNUSED + _ = syscall.SIGURG + _ = syscall.SIGUSR1 + _ = syscall.SIGUSR2 + _ = syscall.SIGVTALRM + _ = syscall.SIGWINCH + _ = syscall.SIGXCPU + _ = syscall.SIGXFSZ + _ = syscall.SIOCADDDLCI + _ = syscall.SIOCADDMULTI + _ = syscall.SIOCADDRT + _ = syscall.SIOCATMARK + _ = syscall.SIOCDARP + _ = syscall.SIOCDELDLCI + _ = syscall.SIOCDELMULTI + _ = syscall.SIOCDELRT + _ = syscall.SIOCDEVPRIVATE + _ = syscall.SIOCDIFADDR + _ = syscall.SIOCDRARP + _ = syscall.SIOCGARP + _ = syscall.SIOCGIFADDR + _ = syscall.SIOCGIFBR + _ = syscall.SIOCGIFBRDADDR + _ = syscall.SIOCGIFCONF + _ = syscall.SIOCGIFCOUNT + _ = syscall.SIOCGIFDSTADDR + _ = syscall.SIOCGIFENCAP + _ = syscall.SIOCGIFFLAGS + _ = syscall.SIOCGIFHWADDR + _ = syscall.SIOCGIFINDEX + _ = syscall.SIOCGIFMAP + _ = syscall.SIOCGIFMEM + _ = syscall.SIOCGIFMETRIC + _ = syscall.SIOCGIFMTU + _ = syscall.SIOCGIFNAME + _ = syscall.SIOCGIFNETMASK + _ = syscall.SIOCGIFPFLAGS + _ = syscall.SIOCGIFSLAVE + _ = syscall.SIOCGIFTXQLEN + _ = syscall.SIOCGPGRP + _ = syscall.SIOCGRARP + _ = syscall.SIOCGSTAMP + _ = syscall.SIOCGSTAMPNS + _ = syscall.SIOCPROTOPRIVATE + _ = syscall.SIOCRTMSG + _ = syscall.SIOCSARP + _ = syscall.SIOCSIFADDR + _ = syscall.SIOCSIFBR + _ = syscall.SIOCSIFBRDADDR + _ = syscall.SIOCSIFDSTADDR + _ = syscall.SIOCSIFENCAP + _ = syscall.SIOCSIFFLAGS + _ = syscall.SIOCSIFHWADDR + _ = syscall.SIOCSIFHWBROADCAST + _ = syscall.SIOCSIFLINK + _ = syscall.SIOCSIFMAP + _ = syscall.SIOCSIFMEM + _ = syscall.SIOCSIFMETRIC + _ = syscall.SIOCSIFMTU + _ = syscall.SIOCSIFNAME + _ = syscall.SIOCSIFNETMASK + _ = syscall.SIOCSIFPFLAGS + _ = syscall.SIOCSIFSLAVE + _ = syscall.SIOCSIFTXQLEN + _ = syscall.SIOCSPGRP + _ = syscall.SIOCSRARP + _ = syscall.SOCK_CLOEXEC + _ = syscall.SOCK_DCCP + _ = syscall.SOCK_DGRAM + _ = syscall.SOCK_NONBLOCK + _ = syscall.SOCK_PACKET + _ = syscall.SOCK_RAW + _ = syscall.SOCK_RDM + _ = syscall.SOCK_SEQPACKET + _ = syscall.SOCK_STREAM + _ = syscall.SOL_AAL + _ = syscall.SOL_ATM + _ = syscall.SOL_DECNET + _ = syscall.SOL_ICMPV6 + _ = syscall.SOL_IP + _ = syscall.SOL_IPV6 + _ = syscall.SOL_IRDA + _ = syscall.SOL_PACKET + _ = syscall.SOL_RAW + _ = syscall.SOL_SOCKET + _ = syscall.SOL_TCP +} diff --git a/test/std/syscall/symbols_linux_common_part08_test.go b/test/std/syscall/symbols_linux_common_part08_test.go new file mode 100644 index 0000000000..4e239c2992 --- /dev/null +++ b/test/std/syscall/symbols_linux_common_part08_test.go @@ -0,0 +1,233 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build linux + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_linuxcommon_Part08(t *testing.T) { + _ = t + _ = syscall.SOL_X25 + _ = syscall.SOMAXCONN + _ = syscall.SO_ACCEPTCONN + _ = syscall.SO_ATTACH_FILTER + _ = syscall.SO_BINDTODEVICE + _ = syscall.SO_BROADCAST + _ = syscall.SO_BSDCOMPAT + _ = syscall.SO_DEBUG + _ = syscall.SO_DETACH_FILTER + _ = syscall.SO_DOMAIN + _ = syscall.SO_DONTROUTE + _ = syscall.SO_ERROR + _ = syscall.SO_KEEPALIVE + _ = syscall.SO_LINGER + _ = syscall.SO_MARK + _ = syscall.SO_NO_CHECK + _ = syscall.SO_OOBINLINE + _ = syscall.SO_PASSCRED + _ = syscall.SO_PASSSEC + _ = syscall.SO_PEERCRED + _ = syscall.SO_PEERNAME + _ = syscall.SO_PEERSEC + _ = syscall.SO_PRIORITY + _ = syscall.SO_PROTOCOL + _ = syscall.SO_RCVBUF + _ = syscall.SO_RCVBUFFORCE + _ = syscall.SO_RCVLOWAT + _ = syscall.SO_RCVTIMEO + _ = syscall.SO_REUSEADDR + _ = syscall.SO_RXQ_OVFL + _ = syscall.SO_SECURITY_AUTHENTICATION + _ = syscall.SO_SECURITY_ENCRYPTION_NETWORK + _ = syscall.SO_SECURITY_ENCRYPTION_TRANSPORT + _ = syscall.SO_SNDBUF + _ = syscall.SO_SNDBUFFORCE + _ = syscall.SO_SNDLOWAT + _ = syscall.SO_SNDTIMEO + _ = syscall.SO_TIMESTAMP + _ = syscall.SO_TIMESTAMPING + _ = syscall.SO_TIMESTAMPNS + _ = syscall.SO_TYPE + _ = syscall.SYS_ACCEPT + _ = syscall.SYS_ACCEPT4 + _ = syscall.SYS_ACCT + _ = syscall.SYS_ADD_KEY + _ = syscall.SYS_ADJTIMEX + _ = syscall.SYS_BIND + _ = syscall.SYS_BRK + _ = syscall.SYS_CAPGET + _ = syscall.SYS_CAPSET + _ = syscall.SYS_CHDIR + _ = syscall.SYS_CHROOT + _ = syscall.SYS_CLOCK_GETRES + _ = syscall.SYS_CLOCK_GETTIME + _ = syscall.SYS_CLOCK_NANOSLEEP + _ = syscall.SYS_CLOCK_SETTIME + _ = syscall.SYS_CLONE + _ = syscall.SYS_CLOSE + _ = syscall.SYS_CONNECT + _ = syscall.SYS_DELETE_MODULE + _ = syscall.SYS_DUP + _ = syscall.SYS_DUP3 + _ = syscall.SYS_EPOLL_CREATE1 + _ = syscall.SYS_EPOLL_CTL + _ = syscall.SYS_EPOLL_PWAIT + _ = syscall.SYS_EVENTFD2 + _ = syscall.SYS_EXECVE + _ = syscall.SYS_EXIT + _ = syscall.SYS_EXIT_GROUP + _ = syscall.SYS_FACCESSAT + _ = syscall.SYS_FADVISE64 + _ = syscall.SYS_FALLOCATE + _ = syscall.SYS_FANOTIFY_INIT + _ = syscall.SYS_FANOTIFY_MARK + _ = syscall.SYS_FCHDIR + _ = syscall.SYS_FCHMOD + _ = syscall.SYS_FCHMODAT + _ = syscall.SYS_FCHOWN + _ = syscall.SYS_FCHOWNAT + _ = syscall.SYS_FCNTL + _ = syscall.SYS_FDATASYNC + _ = syscall.SYS_FGETXATTR + _ = syscall.SYS_FLISTXATTR + _ = syscall.SYS_FLOCK + _ = syscall.SYS_FREMOVEXATTR + _ = syscall.SYS_FSETXATTR + _ = syscall.SYS_FSTAT + _ = syscall.SYS_FSTATFS + _ = syscall.SYS_FSYNC + _ = syscall.SYS_FTRUNCATE + _ = syscall.SYS_FUTEX + _ = syscall.SYS_GETCWD + _ = syscall.SYS_GETDENTS64 + _ = syscall.SYS_GETEGID + _ = syscall.SYS_GETEUID + _ = syscall.SYS_GETGID + _ = syscall.SYS_GETGROUPS + _ = syscall.SYS_GETITIMER + _ = syscall.SYS_GETPEERNAME + _ = syscall.SYS_GETPGID + _ = syscall.SYS_GETPID + _ = syscall.SYS_GETPPID + _ = syscall.SYS_GETPRIORITY + _ = syscall.SYS_GETRESGID + _ = syscall.SYS_GETRESUID + _ = syscall.SYS_GETRLIMIT + _ = syscall.SYS_GETRUSAGE + _ = syscall.SYS_GETSID + _ = syscall.SYS_GETSOCKNAME + _ = syscall.SYS_GETSOCKOPT + _ = syscall.SYS_GETTID + _ = syscall.SYS_GETTIMEOFDAY + _ = syscall.SYS_GETUID + _ = syscall.SYS_GETXATTR + _ = syscall.SYS_GET_MEMPOLICY + _ = syscall.SYS_GET_ROBUST_LIST + _ = syscall.SYS_INIT_MODULE + _ = syscall.SYS_INOTIFY_ADD_WATCH + _ = syscall.SYS_INOTIFY_INIT1 + _ = syscall.SYS_INOTIFY_RM_WATCH + _ = syscall.SYS_IOCTL + _ = syscall.SYS_IOPRIO_GET + _ = syscall.SYS_IOPRIO_SET + _ = syscall.SYS_IO_CANCEL + _ = syscall.SYS_IO_DESTROY + _ = syscall.SYS_IO_GETEVENTS + _ = syscall.SYS_IO_SETUP + _ = syscall.SYS_IO_SUBMIT + _ = syscall.SYS_KEXEC_LOAD + _ = syscall.SYS_KEYCTL + _ = syscall.SYS_KILL + _ = syscall.SYS_LGETXATTR + _ = syscall.SYS_LINKAT + _ = syscall.SYS_LISTEN + _ = syscall.SYS_LISTXATTR + _ = syscall.SYS_LLISTXATTR + _ = syscall.SYS_LOOKUP_DCOOKIE + _ = syscall.SYS_LREMOVEXATTR + _ = syscall.SYS_LSEEK + _ = syscall.SYS_LSETXATTR + _ = syscall.SYS_MADVISE + _ = syscall.SYS_MBIND + _ = syscall.SYS_MIGRATE_PAGES + _ = syscall.SYS_MINCORE + _ = syscall.SYS_MKDIRAT + _ = syscall.SYS_MKNODAT + _ = syscall.SYS_MLOCK + _ = syscall.SYS_MLOCKALL + _ = syscall.SYS_MMAP + _ = syscall.SYS_MOUNT + _ = syscall.SYS_MOVE_PAGES + _ = syscall.SYS_MPROTECT + _ = syscall.SYS_MQ_GETSETATTR + _ = syscall.SYS_MQ_NOTIFY + _ = syscall.SYS_MQ_OPEN + _ = syscall.SYS_MQ_TIMEDRECEIVE + _ = syscall.SYS_MQ_TIMEDSEND + _ = syscall.SYS_MQ_UNLINK + _ = syscall.SYS_MREMAP + _ = syscall.SYS_MSGCTL + _ = syscall.SYS_MSGGET + _ = syscall.SYS_MSGRCV + _ = syscall.SYS_MSGSND + _ = syscall.SYS_MSYNC + _ = syscall.SYS_MUNLOCK + _ = syscall.SYS_MUNLOCKALL + _ = syscall.SYS_MUNMAP + _ = syscall.SYS_NANOSLEEP + _ = syscall.SYS_NFSSERVCTL + _ = syscall.SYS_OPENAT + _ = syscall.SYS_PERF_EVENT_OPEN + _ = syscall.SYS_PERSONALITY + _ = syscall.SYS_PIPE2 + _ = syscall.SYS_PIVOT_ROOT + _ = syscall.SYS_PPOLL + _ = syscall.SYS_PRCTL + _ = syscall.SYS_PREAD64 + _ = syscall.SYS_PREADV + _ = syscall.SYS_PRLIMIT64 + _ = syscall.SYS_PSELECT6 + _ = syscall.SYS_PTRACE + _ = syscall.SYS_PWRITE64 + _ = syscall.SYS_PWRITEV + _ = syscall.SYS_QUOTACTL + _ = syscall.SYS_READ + _ = syscall.SYS_READAHEAD + _ = syscall.SYS_READLINKAT + _ = syscall.SYS_READV + _ = syscall.SYS_REBOOT + _ = syscall.SYS_RECVFROM + _ = syscall.SYS_RECVMMSG + _ = syscall.SYS_RECVMSG + _ = syscall.SYS_REMAP_FILE_PAGES + _ = syscall.SYS_REMOVEXATTR + _ = syscall.SYS_RENAMEAT + _ = syscall.SYS_REQUEST_KEY + _ = syscall.SYS_RESTART_SYSCALL + _ = syscall.SYS_RT_SIGACTION + _ = syscall.SYS_RT_SIGPENDING + _ = syscall.SYS_RT_SIGPROCMASK + _ = syscall.SYS_RT_SIGQUEUEINFO + _ = syscall.SYS_RT_SIGRETURN + _ = syscall.SYS_RT_SIGSUSPEND + _ = syscall.SYS_RT_SIGTIMEDWAIT + _ = syscall.SYS_RT_TGSIGQUEUEINFO + _ = syscall.SYS_SCHED_GETAFFINITY + _ = syscall.SYS_SCHED_GETPARAM + _ = syscall.SYS_SCHED_GETSCHEDULER + _ = syscall.SYS_SCHED_GET_PRIORITY_MAX + _ = syscall.SYS_SCHED_GET_PRIORITY_MIN + _ = syscall.SYS_SCHED_RR_GET_INTERVAL + _ = syscall.SYS_SCHED_SETAFFINITY + _ = syscall.SYS_SCHED_SETPARAM + _ = syscall.SYS_SCHED_SETSCHEDULER + _ = syscall.SYS_SCHED_YIELD + _ = syscall.SYS_SEMCTL + _ = syscall.SYS_SEMGET + _ = syscall.SYS_SEMOP + _ = syscall.SYS_SEMTIMEDOP + _ = syscall.SYS_SENDFILE +} diff --git a/test/std/syscall/symbols_linux_common_part09_test.go b/test/std/syscall/symbols_linux_common_part09_test.go new file mode 100644 index 0000000000..6097df59c9 --- /dev/null +++ b/test/std/syscall/symbols_linux_common_part09_test.go @@ -0,0 +1,231 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build linux + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_linuxcommon_Part09(t *testing.T) { + _ = t + _ = syscall.SYS_SENDMSG + _ = syscall.SYS_SENDTO + _ = syscall.SYS_SETDOMAINNAME + _ = syscall.SYS_SETFSGID + _ = syscall.SYS_SETFSUID + _ = syscall.SYS_SETGID + _ = syscall.SYS_SETGROUPS + _ = syscall.SYS_SETHOSTNAME + _ = syscall.SYS_SETITIMER + _ = syscall.SYS_SETPGID + _ = syscall.SYS_SETPRIORITY + _ = syscall.SYS_SETREGID + _ = syscall.SYS_SETRESGID + _ = syscall.SYS_SETRESUID + _ = syscall.SYS_SETREUID + _ = syscall.SYS_SETRLIMIT + _ = syscall.SYS_SETSID + _ = syscall.SYS_SETSOCKOPT + _ = syscall.SYS_SETTIMEOFDAY + _ = syscall.SYS_SETUID + _ = syscall.SYS_SETXATTR + _ = syscall.SYS_SET_MEMPOLICY + _ = syscall.SYS_SET_ROBUST_LIST + _ = syscall.SYS_SET_TID_ADDRESS + _ = syscall.SYS_SHMAT + _ = syscall.SYS_SHMCTL + _ = syscall.SYS_SHMDT + _ = syscall.SYS_SHMGET + _ = syscall.SYS_SHUTDOWN + _ = syscall.SYS_SIGALTSTACK + _ = syscall.SYS_SIGNALFD4 + _ = syscall.SYS_SOCKET + _ = syscall.SYS_SOCKETPAIR + _ = syscall.SYS_SPLICE + _ = syscall.SYS_STATFS + _ = syscall.SYS_SWAPOFF + _ = syscall.SYS_SWAPON + _ = syscall.SYS_SYMLINKAT + _ = syscall.SYS_SYNC + _ = syscall.SYS_SYNC_FILE_RANGE + _ = syscall.SYS_SYSINFO + _ = syscall.SYS_SYSLOG + _ = syscall.SYS_TEE + _ = syscall.SYS_TGKILL + _ = syscall.SYS_TIMERFD_CREATE + _ = syscall.SYS_TIMERFD_GETTIME + _ = syscall.SYS_TIMERFD_SETTIME + _ = syscall.SYS_TIMER_CREATE + _ = syscall.SYS_TIMER_DELETE + _ = syscall.SYS_TIMER_GETOVERRUN + _ = syscall.SYS_TIMER_GETTIME + _ = syscall.SYS_TIMER_SETTIME + _ = syscall.SYS_TIMES + _ = syscall.SYS_TKILL + _ = syscall.SYS_TRUNCATE + _ = syscall.SYS_UMASK + _ = syscall.SYS_UMOUNT2 + _ = syscall.SYS_UNAME + _ = syscall.SYS_UNLINKAT + _ = syscall.SYS_UNSHARE + _ = syscall.SYS_UTIMENSAT + _ = syscall.SYS_VHANGUP + _ = syscall.SYS_VMSPLICE + _ = syscall.SYS_WAIT4 + _ = syscall.SYS_WAITID + _ = syscall.SYS_WRITE + _ = syscall.SYS_WRITEV + _ = syscall.S_BLKSIZE + _ = syscall.S_IEXEC + _ = syscall.S_IFBLK + _ = syscall.S_IFCHR + _ = syscall.S_IFDIR + _ = syscall.S_IFIFO + _ = syscall.S_IFLNK + _ = syscall.S_IFMT + _ = syscall.S_IFREG + _ = syscall.S_IFSOCK + _ = syscall.S_IREAD + _ = syscall.S_IRGRP + _ = syscall.S_IROTH + _ = syscall.S_IRUSR + _ = syscall.S_IRWXG + _ = syscall.S_IRWXO + _ = syscall.S_IRWXU + _ = syscall.S_ISGID + _ = syscall.S_ISUID + _ = syscall.S_ISVTX + _ = syscall.S_IWGRP + _ = syscall.S_IWOTH + _ = syscall.S_IWRITE + _ = syscall.S_IWUSR + _ = syscall.S_IXGRP + _ = syscall.S_IXOTH + _ = syscall.S_IXUSR + _ = syscall.Seek + _ = syscall.Select + _ = syscall.Sendfile + _ = syscall.Sendmsg + _ = syscall.SendmsgN + _ = syscall.Sendto + _ = syscall.SetLsfPromisc + _ = syscall.SetNonblock + _ = syscall.Setdomainname + _ = syscall.Setegid + _ = syscall.Setenv + _ = syscall.Seteuid + _ = syscall.Setfsgid + _ = syscall.Setfsuid + _ = syscall.Setgid + _ = syscall.Setgroups + _ = syscall.Sethostname + _ = syscall.Setpgid + _ = syscall.Setpriority + _ = syscall.Setregid + _ = syscall.Setresgid + _ = syscall.Setresuid + _ = syscall.Setreuid + _ = syscall.Setrlimit + _ = syscall.Setsid + _ = syscall.SetsockoptByte + _ = syscall.SetsockoptICMPv6Filter + _ = syscall.SetsockoptIPMreq + _ = syscall.SetsockoptIPMreqn + _ = syscall.SetsockoptIPv6Mreq + _ = syscall.SetsockoptInet4Addr + _ = syscall.SetsockoptInt + _ = syscall.SetsockoptLinger + _ = syscall.SetsockoptString + _ = syscall.SetsockoptTimeval + _ = syscall.Settimeofday + _ = syscall.Setuid + _ = syscall.Setxattr + _ = syscall.Shutdown + var _ syscall.Signal + _ = syscall.SizeofCmsghdr + _ = syscall.SizeofICMPv6Filter + _ = syscall.SizeofIPMreq + _ = syscall.SizeofIPMreqn + _ = syscall.SizeofIPv6MTUInfo + _ = syscall.SizeofIPv6Mreq + _ = syscall.SizeofIfAddrmsg + _ = syscall.SizeofIfInfomsg + _ = syscall.SizeofInet4Pktinfo + _ = syscall.SizeofInet6Pktinfo + _ = syscall.SizeofInotifyEvent + _ = syscall.SizeofLinger + _ = syscall.SizeofMsghdr + _ = syscall.SizeofNlAttr + _ = syscall.SizeofNlMsgerr + _ = syscall.SizeofNlMsghdr + _ = syscall.SizeofRtAttr + _ = syscall.SizeofRtGenmsg + _ = syscall.SizeofRtMsg + _ = syscall.SizeofRtNexthop + _ = syscall.SizeofSockFilter + _ = syscall.SizeofSockFprog + _ = syscall.SizeofSockaddrAny + _ = syscall.SizeofSockaddrInet4 + _ = syscall.SizeofSockaddrInet6 + _ = syscall.SizeofSockaddrLinklayer + _ = syscall.SizeofSockaddrNetlink + _ = syscall.SizeofSockaddrUnix + _ = syscall.SizeofTCPInfo + _ = syscall.SizeofUcred + _ = syscall.SlicePtrFromStrings + var _ syscall.SockFilter + var _ syscall.SockFprog + var _ syscall.Sockaddr + var _ syscall.SockaddrInet4 + var _ syscall.SockaddrInet6 + var _ syscall.SockaddrLinklayer + var _ syscall.SockaddrNetlink + var _ syscall.SockaddrUnix + _ = syscall.Socket + var _ syscall.SocketControlMessage + _ = syscall.SocketDisableIPv6 + _ = syscall.Socketpair + _ = syscall.Splice + _ = syscall.StartProcess + _ = syscall.Stat + var _ syscall.Stat_t + _ = syscall.Statfs + var _ syscall.Statfs_t + _ = syscall.Stderr + _ = syscall.Stdin + _ = syscall.Stdout + _ = syscall.StringBytePtr + _ = syscall.StringByteSlice + _ = syscall.StringSlicePtr + _ = syscall.Symlink + _ = syscall.Sync + _ = syscall.SyncFileRange + var _ syscall.SysProcAttr + var _ syscall.SysProcIDMap + _ = syscall.Syscall + _ = syscall.Syscall6 + _ = syscall.Sysinfo + var _ syscall.Sysinfo_t + _ = syscall.TCGETS + _ = syscall.TCIFLUSH + _ = syscall.TCIOFLUSH + _ = syscall.TCOFLUSH + var _ syscall.TCPInfo + _ = syscall.TCP_CONGESTION + _ = syscall.TCP_CORK + _ = syscall.TCP_DEFER_ACCEPT + _ = syscall.TCP_INFO + _ = syscall.TCP_KEEPCNT + _ = syscall.TCP_KEEPIDLE + _ = syscall.TCP_KEEPINTVL + _ = syscall.TCP_LINGER2 + _ = syscall.TCP_MAXSEG + _ = syscall.TCP_MAXWIN + _ = syscall.TCP_MAX_WINSHIFT + _ = syscall.TCP_MD5SIG + _ = syscall.TCP_MD5SIG_MAXKEYLEN + _ = syscall.TCP_MSS + _ = syscall.TCP_NODELAY +} diff --git a/test/std/syscall/symbols_linux_common_part10_test.go b/test/std/syscall/symbols_linux_common_part10_test.go new file mode 100644 index 0000000000..e3c4fd463f --- /dev/null +++ b/test/std/syscall/symbols_linux_common_part10_test.go @@ -0,0 +1,159 @@ +// Code generated by /tmp/gen_syscall_symbols_from_types.go; DO NOT EDIT. +//go:build linux + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPublicAPISymbols_linuxcommon_Part10(t *testing.T) { + _ = t + _ = syscall.TCP_QUICKACK + _ = syscall.TCP_SYNCNT + _ = syscall.TCP_WINDOW_CLAMP + _ = syscall.TCSETS + _ = syscall.TIOCCBRK + _ = syscall.TIOCCONS + _ = syscall.TIOCEXCL + _ = syscall.TIOCGDEV + _ = syscall.TIOCGETD + _ = syscall.TIOCGICOUNT + _ = syscall.TIOCGLCKTRMIOS + _ = syscall.TIOCGPGRP + _ = syscall.TIOCGPTN + _ = syscall.TIOCGRS485 + _ = syscall.TIOCGSERIAL + _ = syscall.TIOCGSID + _ = syscall.TIOCGSOFTCAR + _ = syscall.TIOCGWINSZ + _ = syscall.TIOCINQ + _ = syscall.TIOCLINUX + _ = syscall.TIOCMBIC + _ = syscall.TIOCMBIS + _ = syscall.TIOCMGET + _ = syscall.TIOCMIWAIT + _ = syscall.TIOCMSET + _ = syscall.TIOCM_CAR + _ = syscall.TIOCM_CD + _ = syscall.TIOCM_CTS + _ = syscall.TIOCM_DSR + _ = syscall.TIOCM_DTR + _ = syscall.TIOCM_LE + _ = syscall.TIOCM_RI + _ = syscall.TIOCM_RNG + _ = syscall.TIOCM_RTS + _ = syscall.TIOCM_SR + _ = syscall.TIOCM_ST + _ = syscall.TIOCNOTTY + _ = syscall.TIOCNXCL + _ = syscall.TIOCOUTQ + _ = syscall.TIOCPKT + _ = syscall.TIOCPKT_DATA + _ = syscall.TIOCPKT_DOSTOP + _ = syscall.TIOCPKT_FLUSHREAD + _ = syscall.TIOCPKT_FLUSHWRITE + _ = syscall.TIOCPKT_IOCTL + _ = syscall.TIOCPKT_NOSTOP + _ = syscall.TIOCPKT_START + _ = syscall.TIOCPKT_STOP + _ = syscall.TIOCSBRK + _ = syscall.TIOCSCTTY + _ = syscall.TIOCSERCONFIG + _ = syscall.TIOCSERGETLSR + _ = syscall.TIOCSERGETMULTI + _ = syscall.TIOCSERGSTRUCT + _ = syscall.TIOCSERGWILD + _ = syscall.TIOCSERSETMULTI + _ = syscall.TIOCSERSWILD + _ = syscall.TIOCSER_TEMT + _ = syscall.TIOCSETD + _ = syscall.TIOCSIG + _ = syscall.TIOCSLCKTRMIOS + _ = syscall.TIOCSPGRP + _ = syscall.TIOCSPTLCK + _ = syscall.TIOCSRS485 + _ = syscall.TIOCSSERIAL + _ = syscall.TIOCSSOFTCAR + _ = syscall.TIOCSTI + _ = syscall.TIOCSWINSZ + _ = syscall.TOSTOP + _ = syscall.TUNATTACHFILTER + _ = syscall.TUNDETACHFILTER + _ = syscall.TUNGETFEATURES + _ = syscall.TUNGETIFF + _ = syscall.TUNGETSNDBUF + _ = syscall.TUNGETVNETHDRSZ + _ = syscall.TUNSETDEBUG + _ = syscall.TUNSETGROUP + _ = syscall.TUNSETIFF + _ = syscall.TUNSETLINK + _ = syscall.TUNSETNOCSUM + _ = syscall.TUNSETOFFLOAD + _ = syscall.TUNSETOWNER + _ = syscall.TUNSETPERSIST + _ = syscall.TUNSETSNDBUF + _ = syscall.TUNSETTXFILTER + _ = syscall.TUNSETVNETHDRSZ + _ = syscall.Tee + var _ syscall.Termios + _ = syscall.Tgkill + _ = syscall.Time + var _ syscall.Time_t + _ = syscall.Times + var _ syscall.Timespec + _ = syscall.TimespecToNsec + var _ syscall.Timeval + _ = syscall.TimevalToNsec + var _ syscall.Timex + var _ syscall.Tms + _ = syscall.Truncate + var _ syscall.Ucred + _ = syscall.Umask + _ = syscall.Uname + _ = syscall.UnixCredentials + _ = syscall.UnixRights + _ = syscall.Unlink + _ = syscall.Unlinkat + _ = syscall.Unmount + _ = syscall.Unsetenv + _ = syscall.Unshare + var _ syscall.Ustat_t + var _ syscall.Utimbuf + _ = syscall.Utime + _ = syscall.Utimes + _ = syscall.UtimesNano + var _ syscall.Utsname + _ = syscall.VDISCARD + _ = syscall.VEOF + _ = syscall.VEOL + _ = syscall.VEOL2 + _ = syscall.VERASE + _ = syscall.VINTR + _ = syscall.VKILL + _ = syscall.VLNEXT + _ = syscall.VMIN + _ = syscall.VQUIT + _ = syscall.VREPRINT + _ = syscall.VSTART + _ = syscall.VSTOP + _ = syscall.VSUSP + _ = syscall.VSWTC + _ = syscall.VTIME + _ = syscall.VWERASE + _ = syscall.WALL + _ = syscall.WCLONE + _ = syscall.WCONTINUED + _ = syscall.WEXITED + _ = syscall.WNOHANG + _ = syscall.WNOTHREAD + _ = syscall.WNOWAIT + _ = syscall.WORDSIZE + _ = syscall.WSTOPPED + _ = syscall.WUNTRACED + _ = syscall.Wait4 + var _ syscall.WaitStatus + _ = syscall.Write + _ = syscall.XCASE +} diff --git a/test/std/syscall/syscall_common_file_test.go b/test/std/syscall/syscall_common_file_test.go new file mode 100644 index 0000000000..c80a292a41 --- /dev/null +++ b/test/std/syscall/syscall_common_file_test.go @@ -0,0 +1,62 @@ +//go:build unix + +package syscall_test + +import ( + "os" + "path/filepath" + "syscall" + "testing" +) + +func TestOpenSeekReadWrite(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "x.txt") + + fd, err := syscall.Open(path, syscall.O_CREAT|syscall.O_RDWR|syscall.O_TRUNC, 0o644) + if err != nil { + t.Fatalf("Open: %v", err) + } + defer func() { + if err := syscall.Close(fd); err != nil { + t.Errorf("Close(fd): %v", err) + } + }() + + payload := []byte("hello-syscall") + n, err := syscall.Write(fd, payload) + if err != nil { + t.Fatalf("Write(file): %v", err) + } + if n != len(payload) { + t.Fatalf("Write len = %d, want %d", n, len(payload)) + } + + off, err := syscall.Seek(fd, 0, 0) + if err != nil { + t.Fatalf("Seek: %v", err) + } + if off != 0 { + t.Fatalf("Seek off = %d, want 0", off) + } + + buf := make([]byte, len(payload)) + n, err = syscall.Read(fd, buf) + if err != nil { + t.Fatalf("Read(file): %v", err) + } + if n != len(payload) { + t.Fatalf("Read len = %d, want %d", n, len(payload)) + } + if string(buf) != string(payload) { + t.Fatalf("Read data = %q, want %q", string(buf), string(payload)) + } + + st, err := os.Stat(path) + if err != nil { + t.Fatalf("os.Stat: %v", err) + } + if st.Size() != int64(len(payload)) { + t.Fatalf("size = %d, want %d", st.Size(), len(payload)) + } +} diff --git a/test/std/syscall/syscall_common_ids_test.go b/test/std/syscall/syscall_common_ids_test.go new file mode 100644 index 0000000000..cb93dc8616 --- /dev/null +++ b/test/std/syscall/syscall_common_ids_test.go @@ -0,0 +1,20 @@ +//go:build unix + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestBasicIDs(t *testing.T) { + if syscall.Getpid() <= 0 { + t.Fatalf("Getpid = %d", syscall.Getpid()) + } + if syscall.Getppid() <= 0 { + t.Fatalf("Getppid = %d", syscall.Getppid()) + } + if syscall.Getuid() < 0 || syscall.Geteuid() < 0 || syscall.Getgid() < 0 || syscall.Getegid() < 0 { + t.Fatalf("invalid ids uid=%d euid=%d gid=%d egid=%d", syscall.Getuid(), syscall.Geteuid(), syscall.Getgid(), syscall.Getegid()) + } +} diff --git a/test/std/syscall/syscall_common_pipe_test.go b/test/std/syscall/syscall_common_pipe_test.go new file mode 100644 index 0000000000..b75138fc7f --- /dev/null +++ b/test/std/syscall/syscall_common_pipe_test.go @@ -0,0 +1,44 @@ +//go:build unix + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestPipeReadWrite(t *testing.T) { + fds := make([]int, 2) + if err := syscall.Pipe(fds); err != nil { + t.Fatalf("Pipe: %v", err) + } + defer func() { + if err := syscall.Close(fds[0]); err != nil { + t.Errorf("Close(read fd): %v", err) + } + if err := syscall.Close(fds[1]); err != nil { + t.Errorf("Close(write fd): %v", err) + } + }() + + msg := []byte("llgo-syscall-pipe") + n, err := syscall.Write(fds[1], msg) + if err != nil { + t.Fatalf("Write(pipe): %v", err) + } + if n != len(msg) { + t.Fatalf("Write len = %d, want %d", n, len(msg)) + } + + buf := make([]byte, len(msg)) + n, err = syscall.Read(fds[0], buf) + if err != nil { + t.Fatalf("Read(pipe): %v", err) + } + if n != len(msg) { + t.Fatalf("Read len = %d, want %d", n, len(msg)) + } + if string(buf) != string(msg) { + t.Fatalf("Read data = %q, want %q", string(buf), string(msg)) + } +} diff --git a/test/std/syscall/syscall_darwin_test.go b/test/std/syscall/syscall_darwin_test.go new file mode 100644 index 0000000000..a362476e47 --- /dev/null +++ b/test/std/syscall/syscall_darwin_test.go @@ -0,0 +1,22 @@ +//go:build darwin + +package syscall_test + +import ( + "strings" + "syscall" + "testing" +) + +func TestDarwinSysctl(t *testing.T) { + v, err := syscall.Sysctl("kern.ostype") + if err != nil { + t.Fatalf("Sysctl(kern.ostype): %v", err) + } + if v == "" { + t.Fatal("Sysctl(kern.ostype) empty") + } + if !strings.Contains(strings.ToLower(v), "darwin") { + t.Fatalf("unexpected kern.ostype: %q", v) + } +} diff --git a/test/std/syscall/syscall_linux_process_test.go b/test/std/syscall/syscall_linux_process_test.go new file mode 100644 index 0000000000..59d3ac60a2 --- /dev/null +++ b/test/std/syscall/syscall_linux_process_test.go @@ -0,0 +1,24 @@ +//go:build linux + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestLinuxGetrlimitNoFile(t *testing.T) { + var lim syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil { + t.Fatalf("Getrlimit(RLIMIT_NOFILE): %v", err) + } + if lim.Cur == 0 || lim.Max == 0 { + t.Fatalf("unexpected rlimit values: cur=%d max=%d", lim.Cur, lim.Max) + } +} + +func TestLinuxKillSignal0(t *testing.T) { + if err := syscall.Kill(syscall.Getpid(), 0); err != nil { + t.Fatalf("Kill(getpid, 0): %v", err) + } +} diff --git a/test/std/syscall/syscall_linux_ptrace_test.go b/test/std/syscall/syscall_linux_ptrace_test.go new file mode 100644 index 0000000000..9ed1fa3b4f --- /dev/null +++ b/test/std/syscall/syscall_linux_ptrace_test.go @@ -0,0 +1,17 @@ +//go:build linux + +package syscall_test + +import ( + "syscall" + "testing" +) + +func TestLinuxPtraceRegsPCMethods(t *testing.T) { + var r syscall.PtraceRegs + const wantPC = uint64(0x1234ABCD) + r.SetPC(wantPC) + if got := r.PC(); got != wantPC { + t.Fatalf("PtraceRegs.PC() = %#x, want %#x", got, wantPC) + } +} diff --git a/test/std/syscall/syscall_linux_uname_test.go b/test/std/syscall/syscall_linux_uname_test.go new file mode 100644 index 0000000000..6453a0f804 --- /dev/null +++ b/test/std/syscall/syscall_linux_uname_test.go @@ -0,0 +1,32 @@ +//go:build linux + +package syscall_test + +import ( + "syscall" + "testing" +) + +func cString(b []int8) string { + n := 0 + for n < len(b) && b[n] != 0 { + n++ + } + r := make([]byte, n) + for i := 0; i < n; i++ { + r[i] = byte(b[i]) + } + return string(r) +} + +func TestLinuxUname(t *testing.T) { + var u syscall.Utsname + if err := syscall.Uname(&u); err != nil { + t.Fatalf("Uname: %v", err) + } + sysname := cString(u.Sysname[:]) + release := cString(u.Release[:]) + if sysname == "" || release == "" { + t.Fatalf("Uname fields empty: sysname=%q release=%q", sysname, release) + } +} diff --git a/test/std/syscall/syscall_unix_methods_test.go b/test/std/syscall/syscall_unix_methods_test.go new file mode 100644 index 0000000000..5e3fa050ea --- /dev/null +++ b/test/std/syscall/syscall_unix_methods_test.go @@ -0,0 +1,141 @@ +//go:build unix + +package syscall_test + +import ( + "os" + "runtime" + "syscall" + "testing" +) + +func TestErrnoMethods(t *testing.T) { + e := syscall.ENOENT + if e.Error() == "" { + t.Fatal("Errno.Error returned empty string") + } + if !e.Is(os.ErrNotExist) { + t.Fatal("ENOENT should match os.ErrNotExist") + } + if e.Is(os.ErrPermission) { + t.Fatal("ENOENT should not match os.ErrPermission") + } + if e.Timeout() { + t.Fatal("ENOENT should not be timeout") + } + if e.Temporary() { + t.Fatal("ENOENT should not be temporary") + } +} + +func TestSignalMethods(t *testing.T) { + s := syscall.SIGTERM + s.Signal() + if s.String() == "" { + t.Fatal("Signal.String returned empty string") + } +} + +func TestTimespecAndTimevalMethods(t *testing.T) { + const nsTS = int64(1_234_567_891) + ts := syscall.NsecToTimespec(nsTS) + if got := ts.Nano(); got != nsTS { + t.Fatalf("Timespec.Nano() = %d, want %d", got, nsTS) + } + sec, nsec := ts.Unix() + if sec != 1 || nsec != 234_567_891 { + t.Fatalf("Timespec.Unix() = (%d,%d), want (1,234567891)", sec, nsec) + } + + const nsTV = int64(2_234_567_000) + tv := syscall.NsecToTimeval(nsTV) + if got := tv.Nano(); got != nsTV { + t.Fatalf("Timeval.Nano() = %d, want %d", got, nsTV) + } + sec, nsec = tv.Unix() + if sec != 2 || nsec != 234_567_000 { + t.Fatalf("Timeval.Unix() = (%d,%d), want (2,234567000)", sec, nsec) + } +} + +func TestSetLenMethods(t *testing.T) { + var c syscall.Cmsghdr + c.SetLen(64) + if got := uint64(c.Len); got != 64 { + t.Fatalf("Cmsghdr.SetLen did not set Len, got %d", got) + } + + var buf [8]byte + iov := syscall.Iovec{Base: &buf[0]} + iov.SetLen(7) + if got := uint64(iov.Len); got != 7 { + t.Fatalf("Iovec.SetLen did not set Len, got %d", got) + } + + var m syscall.Msghdr + m.SetControllen(128) + if got := uint64(m.Controllen); got != 128 { + t.Fatalf("Msghdr.SetControllen did not set Controllen, got %d", got) + } +} + +func TestWaitStatusMethods(t *testing.T) { + exited := syscall.WaitStatus(7 << 8) + if !exited.Exited() || exited.ExitStatus() != 7 { + t.Fatalf("exit status decode failed: Exited=%v ExitStatus=%d", exited.Exited(), exited.ExitStatus()) + } + if exited.Signaled() { + t.Fatal("exited status should not be signaled") + } + + signaled := syscall.WaitStatus(syscall.SIGTERM) + if !signaled.Signaled() { + t.Fatal("signaled status should report Signaled()") + } + if signaled.Signal() != syscall.SIGTERM { + t.Fatalf("Signal() = %v, want %v", signaled.Signal(), syscall.SIGTERM) + } + + coredump := syscall.WaitStatus(syscall.SIGABRT | 0x80) + if !coredump.CoreDump() { + t.Fatal("core bit status should report CoreDump()") + } + + switch runtime.GOOS { + case "linux": + stopped := syscall.WaitStatus(0x7F | (uint32(syscall.SIGSTOP) << 8)) + if !stopped.Stopped() { + t.Fatal("linux stop status should report Stopped()") + } + if stopped.StopSignal() != syscall.SIGSTOP { + t.Fatalf("StopSignal() = %v, want %v", stopped.StopSignal(), syscall.SIGSTOP) + } + if stopped.Continued() { + t.Fatal("linux stop status should not report Continued()") + } + + continued := syscall.WaitStatus(0xFFFF) + if !continued.Continued() { + t.Fatal("linux continued status should report Continued()") + } + + trap := syscall.WaitStatus(0x7F | (uint32(syscall.SIGTRAP) << 8) | (2 << 16)) + if trap.TrapCause() != 2 { + t.Fatalf("TrapCause() = %d, want 2", trap.TrapCause()) + } + case "darwin": + continued := syscall.WaitStatus(0x7F | (uint32(syscall.SIGSTOP) << 8)) + if !continued.Continued() { + t.Fatal("darwin continued status should report Continued()") + } + if continued.Stopped() { + t.Fatal("darwin continued status should not report Stopped()") + } + if continued.StopSignal() != -1 { + t.Fatalf("StopSignal() = %v, want -1", continued.StopSignal()) + } + if continued.TrapCause() != -1 { + t.Fatalf("TrapCause() = %d, want -1", continued.TrapCause()) + } + } +} diff --git a/test/std/testing/cryptotest/cryptotest_test.go b/test/std/testing/cryptotest/cryptotest_test.go new file mode 100644 index 0000000000..475bcbdc0d --- /dev/null +++ b/test/std/testing/cryptotest/cryptotest_test.go @@ -0,0 +1,26 @@ +//go:build go1.26 + +package cryptotest_test + +import ( + "bytes" + "crypto/rand" + "testing" + "testing/cryptotest" +) + +func TestSetGlobalRandom(t *testing.T) { + cryptotest.SetGlobalRandom(t, 1) + first := make([]byte, 32) + if _, err := rand.Read(first); err != nil { + t.Fatal(err) + } + cryptotest.SetGlobalRandom(t, 1) + second := make([]byte, 32) + if _, err := rand.Read(second); err != nil { + t.Fatal(err) + } + if !bytes.Equal(first, second) { + t.Fatal("resetting the seed did not reproduce the random stream") + } +} diff --git a/test/std/testing/fstest/fstest_test.go b/test/std/testing/fstest/fstest_test.go new file mode 100644 index 0000000000..0de8c33584 --- /dev/null +++ b/test/std/testing/fstest/fstest_test.go @@ -0,0 +1,502 @@ +package fstest_test + +import ( + "errors" + "io" + "io/fs" + "testing" + "testing/fstest" + "time" +) + +func TestMapFSBasic(t *testing.T) { + mfs := fstest.MapFS{ + "hello.txt": { + Data: []byte("hello world"), + }, + "dir/file.txt": { + Data: []byte("content"), + }, + } + + // Test Open and Read + f, err := mfs.Open("hello.txt") + if err != nil { + t.Fatalf("Open failed: %v", err) + } + defer f.Close() + + data := make([]byte, 100) + n, err := f.Read(data) + if err != nil && !errors.Is(err, io.EOF) { + t.Fatalf("Read failed: %v", err) + } + if string(data[:n]) != "hello world" { + t.Fatalf("Read got %q, want %q", data[:n], "hello world") + } + + // Test Stat + info, err := f.Stat() + if err != nil { + t.Fatalf("Stat failed: %v", err) + } + if info.Name() != "hello.txt" { + t.Fatalf("Name got %q", info.Name()) + } + if info.Size() != 11 { + t.Fatalf("Size got %d", info.Size()) + } + if info.IsDir() { + t.Fatal("IsDir should be false") + } +} + +func TestMapFSReadFile(t *testing.T) { + mfs := fstest.MapFS{ + "data.txt": { + Data: []byte("test data"), + }, + } + + data, err := mfs.ReadFile("data.txt") + if err != nil { + t.Fatalf("ReadFile failed: %v", err) + } + if string(data) != "test data" { + t.Fatalf("ReadFile got %q", data) + } + + // Test non-existent file + _, err = mfs.ReadFile("notexist.txt") + if err == nil { + t.Fatal("ReadFile should fail for non-existent file") + } +} + +func TestMapFSReadDir(t *testing.T) { + mfs := fstest.MapFS{ + "a.txt": {Data: []byte("a")}, + "b.txt": {Data: []byte("b")}, + "dir/c.txt": {Data: []byte("c")}, + "dir/subdir/d.txt": {Data: []byte("d")}, + } + + // Read root directory + entries, err := mfs.ReadDir(".") + if err != nil { + t.Fatalf("ReadDir failed: %v", err) + } + if len(entries) != 3 { // a.txt, b.txt, dir + t.Fatalf("ReadDir got %d entries, want 3", len(entries)) + } + + // Read subdirectory + entries, err = mfs.ReadDir("dir") + if err != nil { + t.Fatalf("ReadDir dir failed: %v", err) + } + if len(entries) != 2 { // c.txt, subdir + t.Fatalf("ReadDir dir got %d entries, want 2", len(entries)) + } + + // Check entry properties + found := false + for _, e := range entries { + if e.Name() == "c.txt" { + found = true + if e.IsDir() { + t.Fatal("c.txt should not be a directory") + } + } + } + if !found { + t.Fatal("c.txt not found in dir entries") + } +} + +func TestMapFSGlob(t *testing.T) { + mfs := fstest.MapFS{ + "file1.txt": {Data: []byte("1")}, + "file2.txt": {Data: []byte("2")}, + "data.log": {Data: []byte("log")}, + "dir/file3.txt": {Data: []byte("3")}, + } + + matches, err := mfs.Glob("*.txt") + if err != nil { + t.Fatalf("Glob failed: %v", err) + } + if len(matches) != 2 { + t.Fatalf("Glob *.txt got %d matches, want 2", len(matches)) + } + + matches, err = mfs.Glob("dir/*.txt") + if err != nil { + t.Fatalf("Glob dir/*.txt failed: %v", err) + } + if len(matches) != 1 || matches[0] != "dir/file3.txt" { + t.Fatalf("Glob dir/*.txt got %v", matches) + } +} + +func TestMapFSSub(t *testing.T) { + mfs := fstest.MapFS{ + "root.txt": {Data: []byte("root")}, + "subdir/file.txt": {Data: []byte("sub")}, + "subdir/nested/deep.txt": {Data: []byte("deep")}, + } + + subFS, err := mfs.Sub("subdir") + if err != nil { + t.Fatalf("Sub failed: %v", err) + } + + // Read file from sub filesystem + data, err := fs.ReadFile(subFS, "file.txt") + if err != nil { + t.Fatalf("ReadFile from sub failed: %v", err) + } + if string(data) != "sub" { + t.Fatalf("Sub ReadFile got %q", data) + } + + // Read nested file + data, err = fs.ReadFile(subFS, "nested/deep.txt") + if err != nil { + t.Fatalf("ReadFile nested from sub failed: %v", err) + } + if string(data) != "deep" { + t.Fatalf("Sub ReadFile nested got %q", data) + } + + // Root file should not be accessible + _, err = fs.ReadFile(subFS, "root.txt") + if err == nil { + t.Fatal("Should not be able to read root.txt from subFS") + } +} + +func TestMapFSWithModeAndModTime(t *testing.T) { + modTime := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) + mfs := fstest.MapFS{ + "file.txt": { + Data: []byte("content"), + Mode: 0644, + ModTime: modTime, + }, + "executable": { + Data: []byte("#!/bin/sh"), + Mode: 0755, + }, + "emptydir": { + Mode: fs.ModeDir | 0755, + }, + } + + // Check file mode and modtime + info, err := mfs.Stat("file.txt") + if err != nil { + t.Fatalf("Stat failed: %v", err) + } + if info.Mode()&0777 != 0644 { + t.Fatalf("Mode got %o, want 0644", info.Mode()&0777) + } + if !info.ModTime().Equal(modTime) { + t.Fatalf("ModTime got %v, want %v", info.ModTime(), modTime) + } + + // Check executable mode + info, err = mfs.Stat("executable") + if err != nil { + t.Fatalf("Stat executable failed: %v", err) + } + if info.Mode()&0777 != 0755 { + t.Fatalf("Executable mode got %o", info.Mode()&0777) + } + + // Check directory + info, err = mfs.Stat("emptydir") + if err != nil { + t.Fatalf("Stat emptydir failed: %v", err) + } + if !info.IsDir() { + t.Fatal("emptydir should be a directory") + } +} + +func TestMapFSSysField(t *testing.T) { + type customSys struct { + ID int + } + + mfs := fstest.MapFS{ + "file.txt": { + Data: []byte("data"), + Sys: &customSys{ID: 42}, + }, + } + + info, err := mfs.Stat("file.txt") + if err != nil { + t.Fatalf("Stat failed: %v", err) + } + + if sys, ok := info.Sys().(*customSys); !ok || sys.ID != 42 { + t.Fatalf("Sys field not preserved correctly: %v", info.Sys()) + } +} + +func TestMapFSErrors(t *testing.T) { + mfs := fstest.MapFS{ + "exists.txt": {Data: []byte("data")}, + } + + // Test opening non-existent file + _, err := mfs.Open("notexist.txt") + if err == nil { + t.Fatal("Open should fail for non-existent file") + } + if !errors.Is(err, fs.ErrNotExist) { + t.Fatalf("Expected ErrNotExist, got %v", err) + } + + // Test invalid paths + _, err = mfs.Open("../outside") + if err == nil { + t.Fatal("Open should fail for invalid path") + } + + // Test reading directory that doesn't exist + _, err = mfs.ReadDir("nodir") + if err == nil { + t.Fatal("ReadDir should fail for non-existent directory") + } +} + +func TestFSWithTestFS(t *testing.T) { + mfs := fstest.MapFS{ + "file1.txt": {Data: []byte("content1")}, + "file2.txt": {Data: []byte("content2")}, + "dir/file3.txt": {Data: []byte("content3")}, + } + + // Test that FS is valid + err := fstest.TestFS(mfs, "file1.txt", "file2.txt", "dir/file3.txt") + if err != nil { + t.Fatalf("TestFS failed: %v", err) + } +} + +func TestTestFSWithEmpty(t *testing.T) { + mfs := fstest.MapFS{} + + // Empty FS should pass when no files expected + err := fstest.TestFS(mfs) + if err != nil { + t.Fatalf("TestFS on empty FS failed: %v", err) + } +} + +func TestTestFSMissingExpected(t *testing.T) { + mfs := fstest.MapFS{ + "exists.txt": {Data: []byte("data")}, + } + + // Should fail if expected file is missing + err := fstest.TestFS(mfs, "missing.txt") + if err == nil { + t.Fatal("TestFS should fail when expected file is missing") + } +} + +func TestMapFSOpenDirectory(t *testing.T) { + mfs := fstest.MapFS{ + "dir/file.txt": {Data: []byte("content")}, + } + + // Open synthesized directory + f, err := mfs.Open("dir") + if err != nil { + t.Fatalf("Open dir failed: %v", err) + } + defer f.Close() + + info, err := f.Stat() + if err != nil { + t.Fatalf("Stat dir failed: %v", err) + } + if !info.IsDir() { + t.Fatal("dir should be a directory") + } + + // Read directory + dirFile, ok := f.(fs.ReadDirFile) + if !ok { + t.Fatal("Directory file should implement ReadDirFile") + } + + entries, err := dirFile.ReadDir(-1) + if err != nil { + t.Fatalf("ReadDir failed: %v", err) + } + if len(entries) != 1 || entries[0].Name() != "file.txt" { + t.Fatalf("ReadDir entries: %v", entries) + } +} + +func TestMapFSRootDirectory(t *testing.T) { + mfs := fstest.MapFS{ + "a.txt": {Data: []byte("a")}, + "b.txt": {Data: []byte("b")}, + } + + // Open root directory + f, err := mfs.Open(".") + if err != nil { + t.Fatalf("Open root failed: %v", err) + } + defer f.Close() + + info, err := f.Stat() + if err != nil { + t.Fatalf("Stat root failed: %v", err) + } + if !info.IsDir() { + t.Fatal("Root should be a directory") + } + if info.Name() != "." { + t.Fatalf("Root name got %q", info.Name()) + } +} + +func TestMapFSSeek(t *testing.T) { + mfs := fstest.MapFS{ + "data.txt": {Data: []byte("0123456789")}, + } + + f, err := mfs.Open("data.txt") + if err != nil { + t.Fatalf("Open failed: %v", err) + } + defer f.Close() + + seeker, ok := f.(io.Seeker) + if !ok { + t.Fatal("File should implement Seeker") + } + + // Seek to position 5 + pos, err := seeker.Seek(5, io.SeekStart) + if err != nil || pos != 5 { + t.Fatalf("Seek failed: pos=%d err=%v", pos, err) + } + + buf := make([]byte, 3) + n, err := f.Read(buf) + if err != nil || n != 3 || string(buf) != "567" { + t.Fatalf("Read after seek got %q", buf) + } + + // Seek relative + pos, err = seeker.Seek(-2, io.SeekCurrent) + if err != nil || pos != 6 { + t.Fatalf("SeekCurrent failed: pos=%d err=%v", pos, err) + } + + // Seek from end + pos, err = seeker.Seek(-3, io.SeekEnd) + if err != nil || pos != 7 { + t.Fatalf("SeekEnd failed: pos=%d err=%v", pos, err) + } + + n, err = f.Read(buf) + if err != nil && !errors.Is(err, io.EOF) { + t.Fatalf("Read after SeekEnd failed: %v", err) + } + if string(buf[:n]) != "789" { + t.Fatalf("Read after SeekEnd got %q", buf[:n]) + } +} + +func TestMapFSStatRoot(t *testing.T) { + mfs := fstest.MapFS{ + "file.txt": {Data: []byte("data")}, + } + + info, err := mfs.Stat(".") + if err != nil { + t.Fatalf("Stat root failed: %v", err) + } + if !info.IsDir() { + t.Fatal("Root should be directory") + } +} + +func TestMapFSMultipleReads(t *testing.T) { + mfs := fstest.MapFS{ + "data.txt": {Data: []byte("hello")}, + } + + f, err := mfs.Open("data.txt") + if err != nil { + t.Fatalf("Open failed: %v", err) + } + defer f.Close() + + // First read + buf1 := make([]byte, 2) + n, err := f.Read(buf1) + if err != nil || n != 2 || string(buf1) != "he" { + t.Fatalf("First read got %q", buf1) + } + + // Second read + buf2 := make([]byte, 3) + n, err = f.Read(buf2) + if (err != nil && !errors.Is(err, io.EOF)) || n != 3 || string(buf2) != "llo" { + t.Fatalf("Second read got %q", buf2) + } + + // Third read should return EOF + buf3 := make([]byte, 1) + n, err = f.Read(buf3) + if !errors.Is(err, io.EOF) || n != 0 { + t.Fatalf("Third read should return EOF: n=%d err=%v", n, err) + } +} + +func TestMapFileStruct(t *testing.T) { + // Test MapFile struct directly + modTime := time.Date(2024, 6, 1, 12, 0, 0, 0, time.UTC) + mapFile := &fstest.MapFile{ + Data: []byte("file content"), + Mode: 0755, + ModTime: modTime, + Sys: "custom sys data", + } + + if string(mapFile.Data) != "file content" { + t.Fatalf("MapFile.Data got %q", mapFile.Data) + } + if mapFile.Mode != 0755 { + t.Fatalf("MapFile.Mode got %o", mapFile.Mode) + } + if !mapFile.ModTime.Equal(modTime) { + t.Fatalf("MapFile.ModTime got %v", mapFile.ModTime) + } + if mapFile.Sys != "custom sys data" { + t.Fatalf("MapFile.Sys got %v", mapFile.Sys) + } + + // Test MapFile with zero values + zeroFile := &fstest.MapFile{} + if zeroFile.Data != nil { + t.Fatal("Zero MapFile.Data should be nil") + } + if zeroFile.Mode != 0 { + t.Fatal("Zero MapFile.Mode should be 0") + } + if !zeroFile.ModTime.IsZero() { + t.Fatal("Zero MapFile.ModTime should be zero time") + } +} diff --git a/test/std/testing/fstest/go126_symbols_test.go b/test/std/testing/fstest/go126_symbols_test.go new file mode 100644 index 0000000000..82340bf373 --- /dev/null +++ b/test/std/testing/fstest/go126_symbols_test.go @@ -0,0 +1,27 @@ +//go:build go1.26 + +package fstest_test + +import ( + "io/fs" + "testing" + "testing/fstest" +) + +func TestMapFSSymlink(t *testing.T) { + filesystem := fstest.MapFS{ + "target": &fstest.MapFile{Data: []byte("contents")}, + "link": &fstest.MapFile{Data: []byte("target"), Mode: fs.ModeSymlink}, + } + info, err := filesystem.Lstat("link") + if err != nil { + t.Fatal(err) + } + if info.Mode()&fs.ModeSymlink == 0 { + t.Fatalf("Lstat mode = %v, want symlink", info.Mode()) + } + target, err := filesystem.ReadLink("link") + if err != nil || target != "target" { + t.Fatalf("ReadLink = %q, %v; want target, nil", target, err) + } +} diff --git a/test/std/testing/go126_symbols_test.go b/test/std/testing/go126_symbols_test.go new file mode 100644 index 0000000000..e6bbeb471f --- /dev/null +++ b/test/std/testing/go126_symbols_test.go @@ -0,0 +1,65 @@ +//go:build go1.26 + +package testing_test + +import ( + "fmt" + "os" + "path/filepath" + "testing" +) + +func TestOutputAndArtifactDir(t *testing.T) { + directory := t.ArtifactDir() + if again := t.ArtifactDir(); again != directory { + t.Fatalf("ArtifactDir changed from %q to %q", directory, again) + } + if err := os.WriteFile(filepath.Join(directory, "result.txt"), []byte("ok"), 0600); err != nil { + t.Fatal(err) + } + t.Attr("go-version", "1.26") + if _, err := fmt.Fprintln(t.Output(), "test output"); err != nil { + t.Fatal(err) + } +} + +func TestBenchmarkOutput(t *testing.T) { + result := testing.Benchmark(func(b *testing.B) { + b.Attr("go-version", "1.26") + fmt.Fprintln(b.Output(), "benchmark output") + for range b.N { + } + }) + if result.N <= 0 { + t.Fatalf("Benchmark ran %d iterations", result.N) + } +} + +func BenchmarkGo126ArtifactDir(b *testing.B) { + directory := b.ArtifactDir() + if again := b.ArtifactDir(); again != directory { + b.Fatalf("ArtifactDir changed from %q to %q", directory, again) + } + if err := os.WriteFile(filepath.Join(directory, "benchmark.txt"), []byte("ok"), 0600); err != nil { + b.Fatal(err) + } + b.Attr("go-version", "1.26") + fmt.Fprintln(b.Output(), "benchmark output") + for range b.N { + } +} + +func FuzzOutputAndArtifactDir(f *testing.F) { + directory := f.ArtifactDir() + if err := os.WriteFile(filepath.Join(directory, "fuzz.txt"), []byte("ok"), 0600); err != nil { + f.Fatal(err) + } + f.Attr("go-version", "1.26") + fmt.Fprintln(f.Output(), "fuzz output") + f.Add("seed") + f.Fuzz(func(t *testing.T, input string) { + if input == "" { + t.Skip() + } + }) +} diff --git a/test/std/testing/iotest/iotest_test.go b/test/std/testing/iotest/iotest_test.go new file mode 100644 index 0000000000..6b31fc14d5 --- /dev/null +++ b/test/std/testing/iotest/iotest_test.go @@ -0,0 +1,335 @@ +package iotest_test + +import ( + "bytes" + "errors" + "io" + "strings" + "testing" + "testing/iotest" +) + +func TestErrReader(t *testing.T) { + testErr := errors.New("test error") + r := iotest.ErrReader(testErr) + + buf := make([]byte, 10) + n, err := r.Read(buf) + if n != 0 { + t.Fatalf("ErrReader returned n=%d, want 0", n) + } + if err != testErr { + t.Fatalf("ErrReader returned err=%v, want %v", err, testErr) + } +} + +func TestHalfReader(t *testing.T) { + data := "0123456789" + r := iotest.HalfReader(strings.NewReader(data)) + + buf := make([]byte, 8) + n, err := r.Read(buf) + if err != nil && err != io.EOF { + t.Fatalf("HalfReader returned err=%v", err) + } + if n != 4 { // Should read half of 8 + t.Fatalf("HalfReader read %d bytes, want 4", n) + } + if string(buf[:n]) != "0123" { + t.Fatalf("HalfReader read %q, want %q", buf[:n], "0123") + } +} + +func TestOneByteReader(t *testing.T) { + data := "hello" + r := iotest.OneByteReader(strings.NewReader(data)) + + buf := make([]byte, 10) + n, err := r.Read(buf) + if err != nil && err != io.EOF { + t.Fatalf("OneByteReader returned err=%v", err) + } + if n != 1 { + t.Fatalf("OneByteReader read %d bytes, want 1", n) + } + if buf[0] != 'h' { + t.Fatalf("OneByteReader read %q, want 'h'", buf[0]) + } +} + +func TestDataErrReader(t *testing.T) { + data := "test" + r := iotest.DataErrReader(strings.NewReader(data)) + + buf := make([]byte, 10) + n, err := r.Read(buf) + // DataErrReader returns EOF with the last data + if n != 4 { + t.Fatalf("DataErrReader read %d bytes, want 4", n) + } + if string(buf[:n]) != "test" { + t.Fatalf("DataErrReader read %q", buf[:n]) + } + if err != io.EOF { + t.Fatalf("DataErrReader err=%v, want EOF", err) + } +} + +func TestTimeoutReader(t *testing.T) { + r := iotest.TimeoutReader(strings.NewReader("data")) + + buf := make([]byte, 10) + + // First read should succeed + n, err := r.Read(buf) + if err != nil && err != io.EOF { + t.Fatalf("First read returned err=%v", err) + } + if n == 0 { + t.Fatal("First read returned 0 bytes") + } + + // Second read should return timeout + n, err = r.Read(buf) + if !errors.Is(err, iotest.ErrTimeout) { + t.Fatalf("Second read returned err=%v, want ErrTimeout", err) + } + if n != 0 { + t.Fatalf("Second read returned n=%d, want 0", n) + } + + // Third read should succeed again + _, err = r.Read(buf) + if err != nil && err != io.EOF && !errors.Is(err, iotest.ErrTimeout) { + t.Fatalf("Third read returned err=%v", err) + } +} + +func TestTruncateWriter(t *testing.T) { + var buf bytes.Buffer + w := iotest.TruncateWriter(&buf, 5) + + // Write 10 bytes, but only 5 should be written + n, err := w.Write([]byte("0123456789")) + if err != nil { + t.Fatalf("TruncateWriter returned err=%v", err) + } + if n != 10 { // TruncateWriter reports full write + t.Fatalf("TruncateWriter reported n=%d", n) + } + if buf.String() != "01234" { + t.Fatalf("TruncateWriter wrote %q, want %q", buf.String(), "01234") + } + + // Additional writes should be silently ignored + n, err = w.Write([]byte("abc")) + if err != nil { + t.Fatalf("Second write returned err=%v", err) + } + if buf.String() != "01234" { + t.Fatalf("Buffer changed to %q", buf.String()) + } +} + +func TestTestReader(t *testing.T) { + data := []byte("hello world") + r := bytes.NewReader(data) + + err := iotest.TestReader(r, data) + if err != nil { + t.Fatalf("TestReader failed: %v", err) + } +} + +func TestTestReaderWithMismatch(t *testing.T) { + data := []byte("hello") + r := strings.NewReader("world") + + err := iotest.TestReader(r, data) + if err == nil { + t.Fatal("TestReader should fail with mismatched content") + } +} + +func TestNewReadLogger(t *testing.T) { + data := "test data" + r := iotest.NewReadLogger("PREFIX: ", strings.NewReader(data)) + + buf := make([]byte, 20) + n, err := r.Read(buf) + if err != nil && err != io.EOF { + t.Fatalf("NewReadLogger returned err=%v", err) + } + if string(buf[:n]) != data { + t.Fatalf("NewReadLogger read %q, want %q", buf[:n], data) + } + // The logger should have printed to stderr, but we can't easily verify that +} + +func TestNewWriteLogger(t *testing.T) { + var buf bytes.Buffer + w := iotest.NewWriteLogger("PREFIX: ", &buf) + + data := []byte("test data") + n, err := w.Write(data) + if err != nil { + t.Fatalf("NewWriteLogger returned err=%v", err) + } + if n != len(data) { + t.Fatalf("NewWriteLogger wrote %d bytes, want %d", n, len(data)) + } + if buf.String() != string(data) { + t.Fatalf("NewWriteLogger wrote %q", buf.String()) + } + // The logger should have printed to stderr, but we can't easily verify that +} + +func TestErrTimeout(t *testing.T) { + if iotest.ErrTimeout == nil { + t.Fatal("ErrTimeout should not be nil") + } + if !strings.Contains(iotest.ErrTimeout.Error(), "timeout") { + t.Fatalf("ErrTimeout message: %q", iotest.ErrTimeout.Error()) + } +} + +func TestHalfReaderMultipleReads(t *testing.T) { + data := "0123456789abcdef" + r := iotest.HalfReader(strings.NewReader(data)) + + var result bytes.Buffer + buf := make([]byte, 8) + + for { + n, err := r.Read(buf) + if n > 0 { + result.Write(buf[:n]) + } + if err == io.EOF { + break + } + if err != nil { + t.Fatalf("Read error: %v", err) + } + } + + if result.String() != data { + t.Fatalf("HalfReader total read %q, want %q", result.String(), data) + } +} + +func TestOneByteReaderMultipleReads(t *testing.T) { + data := "hello" + r := iotest.OneByteReader(strings.NewReader(data)) + + var result bytes.Buffer + buf := make([]byte, 10) + + for { + n, err := r.Read(buf) + if n > 0 { + result.Write(buf[:n]) + } + if err == io.EOF { + break + } + if err != nil { + t.Fatalf("Read error: %v", err) + } + } + + if result.String() != data { + t.Fatalf("OneByteReader total read %q, want %q", result.String(), data) + } +} + +func TestTruncateWriterZeroLimit(t *testing.T) { + var buf bytes.Buffer + w := iotest.TruncateWriter(&buf, 0) + + n, err := w.Write([]byte("hello")) + if err != nil { + t.Fatalf("Write returned err=%v", err) + } + if n != 5 { + t.Fatalf("Write reported n=%d", n) + } + if buf.Len() != 0 { + t.Fatalf("Buffer has %d bytes, want 0", buf.Len()) + } +} + +func TestTestReaderEmpty(t *testing.T) { + r := strings.NewReader("") + err := iotest.TestReader(r, []byte{}) + if err != nil { + t.Fatalf("TestReader on empty reader failed: %v", err) + } +} + +func TestTestReaderWithSeeker(t *testing.T) { + data := []byte("seekable data") + r := bytes.NewReader(data) + + err := iotest.TestReader(r, data) + if err != nil { + t.Fatalf("TestReader with seeker failed: %v", err) + } +} + +func TestDataErrReaderWithEmptyReader(t *testing.T) { + r := iotest.DataErrReader(strings.NewReader("")) + + buf := make([]byte, 10) + n, err := r.Read(buf) + if n != 0 { + t.Fatalf("DataErrReader on empty read %d bytes", n) + } + if err != io.EOF { + t.Fatalf("DataErrReader on empty returned err=%v, want EOF", err) + } +} + +func TestErrReaderMultipleCalls(t *testing.T) { + testErr := errors.New("persistent error") + r := iotest.ErrReader(testErr) + + buf := make([]byte, 10) + + // Multiple reads should all return the same error + for i := 0; i < 3; i++ { + n, err := r.Read(buf) + if n != 0 { + t.Fatalf("Read %d: returned n=%d", i, n) + } + if err != testErr { + t.Fatalf("Read %d: returned err=%v", i, err) + } + } +} + +func TestTruncateWriterExactLimit(t *testing.T) { + var buf bytes.Buffer + w := iotest.TruncateWriter(&buf, 5) + + // Write exactly the limit + n, err := w.Write([]byte("12345")) + if err != nil { + t.Fatalf("Write returned err=%v", err) + } + if n != 5 { + t.Fatalf("Write reported n=%d", n) + } + if buf.String() != "12345" { + t.Fatalf("Buffer has %q", buf.String()) + } + + // Next write should be truncated completely + n, err = w.Write([]byte("abc")) + if err != nil { + t.Fatalf("Second write returned err=%v", err) + } + if buf.String() != "12345" { + t.Fatalf("Buffer changed to %q", buf.String()) + } +} diff --git a/test/std/testing/quick/quick_test.go b/test/std/testing/quick/quick_test.go new file mode 100644 index 0000000000..393845e100 --- /dev/null +++ b/test/std/testing/quick/quick_test.go @@ -0,0 +1,65 @@ +package quick_test + +import ( + "math/rand" + "reflect" + "testing" + "testing/quick" +) + +type evenInt int + +func (evenInt) Generate(r *rand.Rand, _ int) reflect.Value { + return reflect.ValueOf(evenInt(r.Intn(100) * 2)) +} + +var _ quick.Generator = evenInt(0) + +func TestCheckSymbolsAndConfig(t *testing.T) { + _ = quick.Check + _ = quick.CheckEqual + + cfg := quick.Config{ + MaxCount: 20, + MaxCountScale: 1.0, + Rand: rand.New(rand.NewSource(1)), + Values: func(args []reflect.Value, _ *rand.Rand) { + for i := range args { + args[i] = reflect.Zero(args[i].Type()) + } + }, + } + if cfg.MaxCount == 0 || cfg.MaxCountScale == 0 || cfg.Rand == nil || cfg.Values == nil { + t.Fatalf("unexpected zero config fields") + } + + ce := &quick.CheckError{Count: 1, In: []any{1}} + if ce.Error() == "" { + t.Fatalf("CheckError.Error returned empty string") + } +} + +func TestCheckEqualAndError(t *testing.T) { + ce := &quick.CheckEqualError{CheckError: quick.CheckError{Count: 1, In: []any{1}}, Out1: []any{2}, Out2: []any{3}} + if len(ce.Out1) == 0 || len(ce.Out2) == 0 { + t.Fatalf("CheckEqualError outputs should be non-empty") + } + if ce.Error() == "" { + t.Fatalf("CheckEqualError.Error returned empty string") + } +} + +func TestValueAndSetupError(t *testing.T) { + v, ok := quick.Value(reflect.TypeOf(evenInt(0)), rand.New(rand.NewSource(4))) + if !ok { + t.Fatalf("Value returned ok=false") + } + if got := int(v.Interface().(evenInt)); got%2 != 0 { + t.Fatalf("generated evenInt = %d, want even", got) + } + + se := quick.SetupError("bad setup") + if se.Error() == "" { + t.Fatalf("SetupError.Error returned empty string") + } +} diff --git a/test/std/testing/slogtest/slogtest_test.go b/test/std/testing/slogtest/slogtest_test.go new file mode 100644 index 0000000000..76c2029fe3 --- /dev/null +++ b/test/std/testing/slogtest/slogtest_test.go @@ -0,0 +1,51 @@ +package slogtest_test + +import ( + "bytes" + "encoding/json" + "log/slog" + "testing" + "testing/slogtest" +) + +func TestRun(t *testing.T) { + var buf bytes.Buffer + + newHandler := func(*testing.T) slog.Handler { + buf.Reset() + return slog.NewJSONHandler(&buf, nil) + } + result := func(t *testing.T) map[string]any { + m := map[string]any{} + if err := json.Unmarshal(buf.Bytes(), &m); err != nil { + t.Fatal(err) + } + return m + } + + slogtest.Run(t, newHandler, result) +} + +func TestHandler(t *testing.T) { + var buf bytes.Buffer + h := slog.NewJSONHandler(&buf, nil) + + results := func() []map[string]any { + var out []map[string]any + for _, line := range bytes.Split(buf.Bytes(), []byte{'\n'}) { + if len(line) == 0 { + continue + } + m := map[string]any{} + if err := json.Unmarshal(line, &m); err != nil { + t.Fatalf("unmarshal result: %v", err) + } + out = append(out, m) + } + return out + } + + if err := slogtest.TestHandler(h, results); err != nil { + t.Fatalf("TestHandler failed: %v", err) + } +} diff --git a/test/std/testing/synctest/synctest_test.go b/test/std/testing/synctest/synctest_test.go new file mode 100644 index 0000000000..f5de1ee28b --- /dev/null +++ b/test/std/testing/synctest/synctest_test.go @@ -0,0 +1,30 @@ +//go:build go1.26 + +package synctest_test + +import ( + "testing" + "testing/synctest" +) + +func TestCallbackExecution(t *testing.T) { + calls := 0 + cleanupRan := false + synctest.Test(t, func(bubbleT *testing.T) { + calls++ + if bubbleT.Name() != t.Name() { + bubbleT.Fatalf("callback test name = %q, want %q", bubbleT.Name(), t.Name()) + } + bubbleT.Cleanup(func() { cleanupRan = true }) + synctest.Wait() + if calls != 1 { + bubbleT.Fatalf("Wait resumed with callback count %d", calls) + } + }) + if calls != 1 { + t.Fatalf("synctest callback ran %d times, want 1", calls) + } + if !cleanupRan { + t.Fatal("synctest callback cleanup did not run before Test returned") + } +} diff --git a/test/std/testing/testing_test.go b/test/std/testing/testing_test.go new file mode 100644 index 0000000000..b7ea23a40b --- /dev/null +++ b/test/std/testing/testing_test.go @@ -0,0 +1,538 @@ +package testing_test + +import ( + "fmt" + "strings" + "testing" +) + +// Test basic T methods +func TestTBasicMethods(t *testing.T) { + // Test Name + if !strings.Contains(t.Name(), "TestTBasicMethods") { + t.Errorf("Name() = %q", t.Name()) + } + + // Test Log and Logf + t.Log("This is a log message") + t.Logf("This is a formatted log: %d", 42) + + // Test Helper (just call it, shouldn't panic) + helperFunc := func(t *testing.T) { + t.Helper() + t.Log("Called from helper") + } + helperFunc(t) + + // Test Setenv + t.Setenv("TEST_VAR", "test_value") + + // Test TempDir + dir := t.TempDir() + if dir == "" { + t.Error("TempDir returned empty") + } + + // Test Cleanup + t.Cleanup(func() { + t.Log("Cleanup called") + }) + + // Test Context + ctx := t.Context() + if ctx == nil { + t.Fatal("Context returned nil") + } + + // Test Chdir + origDir := t.TempDir() + t.Chdir(origDir) +} + +func TestTFailureMethods(t *testing.T) { + // Test Error and Errorf in a way that doesn't fail the test + t.Run("ErrorMethods", func(t *testing.T) { + // Capture using Failed() + if t.Failed() { + t.Log("Already failed") + } + // We test these exist, but in passing subtests + }) + + // Test Fatal and Fatalf existence + t.Run("FatalExists", func(t *testing.T) { + // Don't call Fatal, just verify it exists via type + var _ func(...any) = t.Fatal + var _ func(string, ...any) = t.Fatalf + }) + + // Test Fail and FailNow existence + t.Run("FailExists", func(t *testing.T) { + var _ func() = t.Fail + var _ func() = t.FailNow + }) +} + +func TestTSkip(t *testing.T) { + // Test Skip methods existence + t.Run("Skip", func(t *testing.T) { + if t.Skipped() { + t.Log("Already skipped") + } + var _ func(...any) = t.Skip + var _ func(string, ...any) = t.Skipf + var _ func() = t.SkipNow + }) + + if testing.Short() { + t.Skip("Skipping in short mode") + } + t.Log("Not in short mode") +} + +func TestShortAndVerbose(t *testing.T) { + short := testing.Short() + verbose := testing.Verbose() + if short { + t.Log("short mode enabled") + } + if verbose { + t.Log("verbose mode enabled") + } + + // Test Testing() - should return true since we're in a test + if !testing.Testing() { + t.Fatal("Testing() should return true") + } +} + +func TestTempDir(t *testing.T) { + dir := t.TempDir() + if dir == "" { + t.Fatal("TempDir returned empty string") + } + t.Logf("TempDir: %s", dir) +} + +func TestTCleanup(t *testing.T) { + cleaned := false + t.Cleanup(func() { + cleaned = true + }) + // Cleanup will run after test finishes + if cleaned { + t.Fatal("Cleanup ran too early") + } +} + +func TestTDeadline(t *testing.T) { + deadline, ok := t.Deadline() + if ok { + t.Logf("Test has deadline: %v", deadline) + } else { + t.Log("Test has no deadline") + } +} + +// Test Benchmark types +func BenchmarkExample(b *testing.B) { + // Test B.N + for i := 0; i < b.N; i++ { + _ = i * 2 + } +} + +func BenchmarkBMethods(b *testing.B) { + // Test B methods + b.Log("Log message") + b.Logf("Formatted: %d", 42) + b.Helper() + + name := b.Name() + if name == "" { + b.Error("Name is empty") + } + + if elapsed := b.Elapsed(); elapsed < 0 { + b.Fatalf("Elapsed should be non-negative, got %v", elapsed) + } + + if b.Failed() { + b.Log("Benchmark failed") + } + + if b.Skipped() { + b.Log("Benchmark skipped") + } + + dir := b.TempDir() + if dir != "" { + b.Chdir(dir) + } + + b.Setenv("BENCH_VAR", "value") + b.Cleanup(func() { + b.Log("Cleanup") + }) + + ctx := b.Context() + if ctx == nil { + b.Fatal("Context is nil") + } + + // Test method existence + var _ func(...any) = b.Error + var _ func(string, ...any) = b.Errorf + var _ func() = b.Fail + var _ func() = b.FailNow + var _ func(...any) = b.Fatal + var _ func(string, ...any) = b.Fatalf + var _ func(...any) = b.Skip + var _ func(string, ...any) = b.Skipf + var _ func() = b.SkipNow + + b.SetParallelism(2) + + for i := 0; i < b.N; i++ { + _ = i + } +} + +func BenchmarkResetTimer(b *testing.B) { + // Do some setup + _ = make([]int, 1000) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _ = i * 2 + } +} + +func BenchmarkStartStopTimer(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + // Do some work that shouldn't be timed + _ = make([]int, 10) + b.StartTimer() + + _ = i * 2 + } +} + +func BenchmarkReportAllocs(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = make([]byte, 10) + } +} + +func BenchmarkReportMetric(b *testing.B) { + b.ReportMetric(123.45, "custom/op") + for i := 0; i < b.N; i++ { + _ = i * 2 + } +} + +func BenchmarkSetBytes(b *testing.B) { + b.SetBytes(1024) + for i := 0; i < b.N; i++ { + _ = make([]byte, 1024) + } +} + +func BenchmarkRunParallel(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = 1 + 1 + } + }) +} + +func BenchmarkLoop(b *testing.B) { + // Test B.Loop method + for b.Loop() { + _ = 1 + 1 + } +} + +func BenchmarkRun(b *testing.B) { + // Test B.Run method + b.Run("SubBenchmark", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = i * 2 + } + }) +} + +// Test AllocsPerRun +func TestAllocsPerRun(t *testing.T) { + avg := testing.AllocsPerRun(100, func() { + _ = make([]int, 10) + }) + if avg < 0 { + t.Errorf("AllocsPerRun returned negative: %f", avg) + } + t.Logf("Average allocations: %f", avg) +} + +// Test Coverage functions +func TestCoverage(t *testing.T) { + coverage := testing.Coverage() + t.Logf("Coverage: %f", coverage) + + mode := testing.CoverMode() + t.Logf("Cover mode: %s", mode) +} + +// Test F (fuzzing support) +func FuzzExample(f *testing.F) { + // Test F methods + f.Log("Log message") + f.Logf("Formatted: %d", 42) + f.Helper() + + name := f.Name() + if name == "" { + f.Error("Name is empty") + } + + if f.Failed() { + f.Log("Fuzz failed") + } + + if f.Skipped() { + f.Log("Fuzz skipped") + } + + dir := f.TempDir() + if dir != "" { + f.Chdir(dir) + } + + f.Setenv("FUZZ_VAR", "value") + f.Cleanup(func() { + f.Log("Cleanup") + }) + + ctx := f.Context() + if ctx == nil { + f.Fatal("Context is nil") + } + + // Test method existence + var _ func(...any) = f.Error + var _ func(string, ...any) = f.Errorf + var _ func() = f.Fail + var _ func() = f.FailNow + var _ func(...any) = f.Fatal + var _ func(string, ...any) = f.Fatalf + var _ func(...any) = f.Skip + var _ func(string, ...any) = f.Skipf + var _ func() = f.SkipNow + + // Add seed corpus + f.Add(5) + f.Add(10) + + f.Fuzz(func(t *testing.T, n int) { + if n < 0 { + t.Skip("negative numbers") + } + // Test something + _ = n * 2 + }) +} + +// Test TB interface +func helperUsingTB(tb testing.TB) { + tb.Helper() + tb.Log("Testing TB interface") +} + +func TestTBInterface(t *testing.T) { + helperUsingTB(t) +} + +func BenchmarkTBInterface(b *testing.B) { + helperUsingTB(b) + for i := 0; i < b.N; i++ { + _ = i + } +} + +// Test InternalTest +func TestInternalTest(t *testing.T) { + it := testing.InternalTest{ + Name: "DummyTest", + F: func(t *testing.T) { + t.Log("Dummy test") + }, + } + + if it.Name != "DummyTest" { + t.Errorf("InternalTest.Name = %q", it.Name) + } +} + +// Test InternalBenchmark +func TestInternalBenchmark(t *testing.T) { + ib := testing.InternalBenchmark{ + Name: "DummyBenchmark", + F: func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = i + } + }, + } + + if ib.Name != "DummyBenchmark" { + t.Errorf("InternalBenchmark.Name = %q", ib.Name) + } +} + +// Test InternalExample +func TestInternalExample(t *testing.T) { + ie := testing.InternalExample{ + Name: "ExampleTest", + F: func() { fmt.Println("example") }, + Output: "example\n", + } + + if ie.Name != "ExampleTest" { + t.Errorf("InternalExample.Name = %q", ie.Name) + } +} + +// Test InternalFuzzTarget +func TestInternalFuzzTarget(t *testing.T) { + ift := testing.InternalFuzzTarget{ + Name: "FuzzTest", + Fn: func(f *testing.F) { + f.Add(1) + }, + } + + if ift.Name != "FuzzTest" { + t.Errorf("InternalFuzzTarget.Name = %q", ift.Name) + } +} + +// Test BenchmarkResult +func TestBenchmarkResult(t *testing.T) { + result := testing.Benchmark(func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = i * 2 + } + }) + + if result.N == 0 { + t.Error("Benchmark didn't run") + } + + // Test String method + str := result.String() + if str == "" { + t.Error("BenchmarkResult.String() is empty") + } + t.Logf("Benchmark result: %s", str) + + // Test NsPerOp + nsPerOp := result.NsPerOp() + t.Logf("ns/op: %d", nsPerOp) + + // Test AllocsPerOp + allocsPerOp := result.AllocsPerOp() + t.Logf("allocs/op: %d", allocsPerOp) + + // Test AllocedBytesPerOp + bytesPerOp := result.AllocedBytesPerOp() + t.Logf("bytes/op: %d", bytesPerOp) + + // Test MemString + memStr := result.MemString() + t.Logf("Memory: %s", memStr) +} + +// Test Cover types +func TestCoverTypes(t *testing.T) { + // Test Cover struct + cover := testing.Cover{ + Mode: "set", + Counters: make(map[string][]uint32), + Blocks: make(map[string][]testing.CoverBlock), + CoveredPackages: "pkg1,pkg2", + } + + if cover.Mode != "set" { + t.Errorf("Cover.Mode = %q", cover.Mode) + } + + // Test CoverBlock + block := testing.CoverBlock{ + Line0: 10, + Col0: 5, + Line1: 15, + Col1: 20, + Stmts: 3, + } + + if block.Line0 != 10 { + t.Errorf("CoverBlock.Line0 = %d", block.Line0) + } +} + +// Test M (test main) +func TestMType(t *testing.T) { + // We can't easily test M.Run() as it's meant for TestMain + // But we can verify the type exists + var _ *testing.M + // Test method existence + var m *testing.M + if m != nil { + var _ func() int = m.Run + } +} + +// Test other testing functions +func TestOtherFunctions(t *testing.T) { + // Test Init + testing.Init() + + // Test RunTests - we can reference it but not easily call it + var _ func(func(string, string) (bool, error), []testing.InternalTest) bool = testing.RunTests + + // Test RunBenchmarks + testing.RunBenchmarks(func(pat, str string) (bool, error) { return true, nil }, []testing.InternalBenchmark{}) + + // Test RunExamples + var _ func(func(string, string) (bool, error), []testing.InternalExample) bool = testing.RunExamples + + // Test Main - verify it exists but don't call it + var _ func(func(string, string) (bool, error), []testing.InternalTest, []testing.InternalBenchmark, []testing.InternalExample) = testing.Main + + // Test RegisterCover + cover := testing.Cover{ + Mode: "set", + } + testing.RegisterCover(cover) +} + +// Test parallel execution +func TestParallel(t *testing.T) { + t.Run("Parallel1", func(t *testing.T) { + t.Parallel() + t.Log("Running in parallel 1") + }) + + t.Run("Parallel2", func(t *testing.T) { + t.Parallel() + t.Log("Running in parallel 2") + }) +} + +// Example test +func Example() { + fmt.Println("Hello, World!") + // Output: Hello, World! +} diff --git a/test/std/text/scanner/scanner_test.go b/test/std/text/scanner/scanner_test.go new file mode 100644 index 0000000000..714ef5ad4d --- /dev/null +++ b/test/std/text/scanner/scanner_test.go @@ -0,0 +1,105 @@ +package scanner_test + +import ( + "strings" + "testing" + "text/scanner" + "unicode" +) + +func TestScannerConstants(t *testing.T) { + mode := scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanChars | + scanner.ScanStrings | scanner.ScanRawStrings | scanner.ScanComments | scanner.SkipComments + if mode&scanner.ScanInts == 0 { + t.Fatal("mode should include ScanInts") + } + if scanner.GoTokens&scanner.ScanRawStrings == 0 { + t.Fatal("GoTokens should include ScanRawStrings") + } + if scanner.GoWhitespace&(1<<' ') == 0 { + t.Fatal("GoWhitespace should include space") + } + + tokens := []rune{ + scanner.EOF, + scanner.Ident, + scanner.Int, + scanner.Float, + scanner.Char, + scanner.String, + scanner.RawString, + scanner.Comment, + } + for _, tok := range tokens { + if scanner.TokenString(tok) == "" { + t.Fatalf("TokenString for %v should not be empty", tok) + } + } + if got := scanner.TokenString('x'); got != "\"x\"" { + t.Fatalf("TokenString for literal expected quoted rune, got %q", got) + } +} + +func TestScannerUsage(t *testing.T) { + var s scanner.Scanner + source := strings.NewReader("ident 123 'a' \"str\" `raw`\n// comment\n") + s.Init(source) + s.Mode = scanner.GoTokens + s.Whitespace = scanner.GoWhitespace + s.IsIdentRune = func(r rune, i int) bool { + if r == '_' || r == '$' { + return true + } + if unicode.IsLetter(r) { + return true + } + return i > 0 && unicode.IsDigit(r) + } + + firstPeek := s.Peek() + if firstPeek == scanner.EOF { + t.Fatal("Peek should not report EOF at start") + } + + var tokens []rune + for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() { + tokens = append(tokens, tok) + if s.TokenText() == "" { + t.Fatal("TokenText returned empty") + } + } + if len(tokens) == 0 { + t.Fatal("expected tokens to be scanned") + } + + pos := s.Pos() + if !pos.IsValid() { + t.Fatal("Pos should be valid after scanning tokens") + } + if !strings.Contains(pos.String(), ":") { + t.Fatalf("expected position string to contain colon, got %q", pos.String()) + } +} + +func TestScannerPositionAndNext(t *testing.T) { + pos := scanner.Position{Filename: "file.txt", Line: 2, Column: 3} + if !pos.IsValid() { + t.Fatal("Position should be valid when line > 0") + } + if want := "file.txt:2:3"; pos.String() != want { + t.Fatalf("Position string mismatch: got %q want %q", pos.String(), want) + } + + var s scanner.Scanner + s.Init(strings.NewReader("go")) + r := s.Next() + if r != 'g' { + t.Fatalf("Next expected 'g', got %q", r) + } + if s.Position.IsValid() { + t.Fatal("embedded Position should be invalid immediately after Next") + } + if pos := s.Pos(); !pos.IsValid() { + t.Fatal("Pos should provide a valid position after Next") + } +} diff --git a/test/std/text/tabwriter/tabwriter_test.go b/test/std/text/tabwriter/tabwriter_test.go new file mode 100644 index 0000000000..29743b8539 --- /dev/null +++ b/test/std/text/tabwriter/tabwriter_test.go @@ -0,0 +1,66 @@ +package tabwriter_test + +import ( + "bytes" + "strings" + "testing" + "text/tabwriter" +) + +func TestTabWriterFormatting(t *testing.T) { + var buf bytes.Buffer + + w := tabwriter.NewWriter(&buf, 0, 8, 2, ' ', tabwriter.FilterHTML|tabwriter.StripEscape|tabwriter.Debug|tabwriter.TabIndent) + if _, err := w.Write([]byte("Name\tValue\tNote\n")); err != nil { + t.Fatalf("Write failed: %v", err) + } + if _, err := w.Write([]byte("A\t10\tEscaped\t")); err != nil { + t.Fatalf("Write failed: %v", err) + } + if _, err := w.Write([]byte{tabwriter.Escape}); err != nil { + t.Fatalf("Write escape failed: %v", err) + } + if _, err := w.Write([]byte("Hidden Text")); err != nil { + t.Fatalf("Write hidden text failed: %v", err) + } + if _, err := w.Write([]byte{tabwriter.Escape, '\n'}); err != nil { + t.Fatalf("Write closing escape failed: %v", err) + } + if err := w.Flush(); err != nil { + t.Fatalf("Flush failed: %v", err) + } + + output := buf.String() + if !strings.Contains(output, "|") { + t.Fatalf("expected debug output to include column separators, got %q", output) + } + if !strings.Contains(output, "Hidden Text") { + t.Fatalf("escaped text should remain when StripEscape is set, got %q", output) + } + if strings.ContainsRune(output, rune(tabwriter.Escape)) { + t.Fatalf("Escape characters should be stripped from output, got %q", output) + } + + buf.Reset() + w.Init(&buf, 4, 4, 1, ' ', tabwriter.AlignRight|tabwriter.DiscardEmptyColumns) + if _, err := w.Write([]byte("Left\t\t42\n")); err != nil { + t.Fatalf("Write failed: %v", err) + } + if _, err := w.Write([]byte("Wide\t12345\t\n")); err != nil { + t.Fatalf("Write failed: %v", err) + } + if err := w.Flush(); err != nil { + t.Fatalf("Flush failed: %v", err) + } + output = buf.String() + lines := strings.Split(strings.TrimSpace(output), "\n") + if len(lines) != 2 { + t.Fatalf("expected 2 lines, got %d", len(lines)) + } + if strings.Contains(lines[0], "\t") { + t.Fatalf("tabs should be expanded in formatted output, got %q", lines[0]) + } + if !strings.Contains(lines[0], " 42") { + t.Fatalf("AlignRight should pad numeric column, got %q", lines[0]) + } +} diff --git a/test/std/text/template/parse/parse_test.go b/test/std/text/template/parse/parse_test.go new file mode 100644 index 0000000000..11540a0c81 --- /dev/null +++ b/test/std/text/template/parse/parse_test.go @@ -0,0 +1,892 @@ +package parse_test + +import ( + "testing" + "text/template/parse" +) + +func assertNodeStringCopyType(t *testing.T, node parse.Node) parse.Node { + t.Helper() + if node == nil { + t.Fatal("node is nil") + } + nodeStr := node.String() + if nodeStr == "" { + t.Fatalf("%T.String() returned empty", node) + } + if node.Type() != node.Type().Type() { + t.Fatalf("%T.Type() should be stable", node) + } + copied := node.Copy() + if copied == nil { + t.Fatalf("%T.Copy() returned nil", node) + } + if copied.Type() != node.Type() { + t.Fatalf("%T.Copy() changed node type: got %v, want %v", node, copied.Type(), node.Type()) + } + if copied.String() != nodeStr { + t.Fatalf("%T.Copy().String() mismatch: got %q, want %q", node, copied.String(), nodeStr) + } + return copied +} + +// Test basic Parse function +func TestParse(t *testing.T) { + trees, err := parse.Parse("test", "{{.Name}}", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + if len(trees) == 0 { + t.Fatal("Parse returned no trees") + } + if trees["test"] == nil { + t.Error("Parse should return tree named 'test'") + } +} + +// Test New function +func TestNew(t *testing.T) { + tree := parse.New("test") + if tree == nil { + t.Fatal("New returned nil") + } +} + +// Test Tree.Parse +func TestTreeParse(t *testing.T) { + tree := parse.New("test") + treeSet := make(map[string]*parse.Tree) + _, err := tree.Parse("{{.Name}}", "{{", "}}", treeSet) + if err != nil { + t.Fatalf("Tree.Parse failed: %v", err) + } +} + +// Test Tree.Copy +func TestTreeCopy(t *testing.T) { + tree := parse.New("test") + treeSet := make(map[string]*parse.Tree) + tree.Parse("{{.Name}}", "{{", "}}", treeSet) + + copied := tree.Copy() + if copied == nil { + t.Fatal("Tree.Copy returned nil") + } +} + +// Test Tree.ErrorContext +func TestTreeErrorContext(t *testing.T) { + tree := parse.New("test") + treeSet := make(map[string]*parse.Tree) + tree.Parse("{{.Name}}", "{{", "}}", treeSet) + + if tree.Root != nil && len(tree.Root.Nodes) > 0 { + location, context := tree.ErrorContext(tree.Root.Nodes[0]) + if location == "" && context == "" { + t.Fatal("ErrorContext returned empty location and context") + } + } +} + +// Test IsEmptyTree +func TestIsEmptyTree(t *testing.T) { + tree := parse.New("test") + treeSet := make(map[string]*parse.Tree) + tree.Parse("", "{{", "}}", treeSet) + + if tree.Root != nil { + isEmpty := parse.IsEmptyTree(tree.Root) + if !isEmpty { + t.Error("Empty tree should be empty") + } + } +} + +// Test NodeType constants +func TestNodeType(t *testing.T) { + types := []parse.NodeType{ + parse.NodeText, + parse.NodeAction, + parse.NodeBool, + parse.NodeChain, + parse.NodeCommand, + parse.NodeDot, + parse.NodeField, + parse.NodeIdentifier, + parse.NodeIf, + parse.NodeList, + parse.NodeNil, + parse.NodeNumber, + parse.NodePipe, + parse.NodeRange, + parse.NodeString, + parse.NodeTemplate, + parse.NodeVariable, + parse.NodeWith, + parse.NodeComment, + parse.NodeBreak, + parse.NodeContinue, + } + + for _, typ := range types { + if typ.Type() != typ { + t.Errorf("NodeType.Type() should return self") + } + } +} + +// Test Pos.Position +func TestPosPosition(t *testing.T) { + var p parse.Pos = 42 + if p.Position() != p { + t.Error("Pos.Position should return self") + } +} + +// Test Mode constants +func TestModeConstants(t *testing.T) { + _ = parse.ParseComments + _ = parse.SkipFuncCheck +} + +// Test NewIdentifier +func TestNewIdentifier(t *testing.T) { + ident := parse.NewIdentifier("test") + if ident == nil { + t.Fatal("NewIdentifier returned nil") + } + if ident.Ident != "test" { + t.Errorf("Identifier should be 'test', got %q", ident.Ident) + } +} + +// Test IdentifierNode methods +func TestIdentifierNode(t *testing.T) { + ident := parse.NewIdentifier("test") + + // Test String + str := ident.String() + if str == "" { + t.Error("IdentifierNode.String should not be empty") + } + + // Test Copy + copied := ident.Copy() + if copied == nil { + t.Fatal("IdentifierNode.Copy returned nil") + } + + // Test SetPos + ident2 := ident.SetPos(10) + if ident2 == nil { + t.Error("SetPos should return self") + } + + // Test SetTree + tree := parse.New("test") + ident3 := ident.SetTree(tree) + if ident3 == nil { + t.Error("SetTree should return self") + } +} + +// Test various node types with a parsed template +func TestNodeTypes(t *testing.T) { + // Test with a template that exercises different node types + template := `{{/* comment */}}{{if .Cond}}{{.Field}}{{end}}{{range .Items}}{{.}}{{break}}{{continue}}{{end}}{{with .Obj}}{{.}}{{end}}` + + trees, err := parse.Parse("test", template, "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + tree := trees["test"] + if tree == nil || tree.Root == nil { + t.Fatal("Parse returned invalid tree") + } + + // Walk through nodes to exercise Copy and String methods + var walkNodes func([]parse.Node) + walkNodes = func(nodes []parse.Node) { + for _, node := range nodes { + assertNodeStringCopyType(t, node) + + // Exercise specific node types + switch n := node.(type) { + case *parse.ListNode: + copied := n.CopyList() + if copied == nil { + t.Fatal("ListNode.CopyList returned nil") + } + if copied.String() != n.String() { + t.Fatalf("ListNode.CopyList().String() mismatch: got %q, want %q", copied.String(), n.String()) + } + if n.Nodes != nil { + walkNodes(n.Nodes) + } + case *parse.ActionNode: + if n.Pipe != nil { + walkNodes([]parse.Node{n.Pipe}) + } + case *parse.IfNode: + if n.BranchNode.Pipe != nil { + walkNodes([]parse.Node{n.BranchNode.Pipe}) + } + if n.BranchNode.List != nil { + walkNodes([]parse.Node{n.BranchNode.List}) + } + if n.BranchNode.ElseList != nil { + walkNodes([]parse.Node{n.BranchNode.ElseList}) + } + case *parse.RangeNode: + if n.BranchNode.Pipe != nil { + walkNodes([]parse.Node{n.BranchNode.Pipe}) + } + if n.BranchNode.List != nil { + walkNodes([]parse.Node{n.BranchNode.List}) + } + case *parse.WithNode: + if n.BranchNode.Pipe != nil { + walkNodes([]parse.Node{n.BranchNode.Pipe}) + } + if n.BranchNode.List != nil { + walkNodes([]parse.Node{n.BranchNode.List}) + } + case *parse.PipeNode: + copied := n.CopyPipe() + if copied == nil { + t.Fatal("PipeNode.CopyPipe returned nil") + } + if copied.String() != n.String() { + t.Fatalf("PipeNode.CopyPipe().String() mismatch: got %q, want %q", copied.String(), n.String()) + } + if n.Decl != nil { + for _, v := range n.Decl { + walkNodes([]parse.Node{v}) + } + } + if n.Cmds != nil { + for _, cmd := range n.Cmds { + walkNodes([]parse.Node{cmd}) + } + } + case *parse.CommandNode: + if n.Args != nil { + walkNodes(n.Args) + } + case *parse.ChainNode: + n.Add("field") + } + } + } + + walkNodes(tree.Root.Nodes) +} + +// Test DotNode +func TestDotNode(t *testing.T) { + trees, err := parse.Parse("test", "{{.}}", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + // Find the DotNode in the tree + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if action, ok := tree.Root.Nodes[0].(*parse.ActionNode); ok { + if action.Pipe != nil && len(action.Pipe.Cmds) > 0 { + if len(action.Pipe.Cmds[0].Args) > 0 { + if dot, ok := action.Pipe.Cmds[0].Args[0].(*parse.DotNode); ok { + if dot.Type() != parse.NodeDot { + t.Fatalf("DotNode.Type mismatch: got %v", dot.Type()) + } + dotStr := dot.String() + if dotStr == "" { + t.Fatal("DotNode.String returned empty") + } + copiedDot, ok := dot.Copy().(*parse.DotNode) + if !ok || copiedDot == nil { + t.Fatal("DotNode.Copy returned invalid copy") + } + if copiedDot.String() != dotStr { + t.Fatalf("DotNode.Copy().String() mismatch: got %q, want %q", copiedDot.String(), dotStr) + } + } + } + } + } + } +} + +// Test NilNode +func TestNilNode(t *testing.T) { + trees, err := parse.Parse("test", "{{nil}}", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + // Find the NilNode in the tree + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if action, ok := tree.Root.Nodes[0].(*parse.ActionNode); ok { + if action.Pipe != nil && len(action.Pipe.Cmds) > 0 { + if len(action.Pipe.Cmds[0].Args) > 0 { + if nilNode, ok := action.Pipe.Cmds[0].Args[0].(*parse.NilNode); ok { + if nilNode.Type() != parse.NodeNil { + t.Fatalf("NilNode.Type mismatch: got %v", nilNode.Type()) + } + nilStr := nilNode.String() + if nilStr == "" { + t.Fatal("NilNode.String returned empty") + } + copiedNil, ok := nilNode.Copy().(*parse.NilNode) + if !ok || copiedNil == nil { + t.Fatal("NilNode.Copy returned invalid copy") + } + if copiedNil.String() != nilStr { + t.Fatalf("NilNode.Copy().String() mismatch: got %q, want %q", copiedNil.String(), nilStr) + } + } + } + } + } + } +} + +// Test BoolNode +func TestBoolNode(t *testing.T) { + trees, err := parse.Parse("test", "{{true}}", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + // Find the BoolNode in the tree + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if action, ok := tree.Root.Nodes[0].(*parse.ActionNode); ok { + if action.Pipe != nil && len(action.Pipe.Cmds) > 0 { + if len(action.Pipe.Cmds[0].Args) > 0 { + if boolNode, ok := action.Pipe.Cmds[0].Args[0].(*parse.BoolNode); ok { + boolStr := boolNode.String() + if boolStr != "true" { + t.Fatalf("BoolNode.String mismatch: got %q, want %q", boolStr, "true") + } + copiedBool, ok := boolNode.Copy().(*parse.BoolNode) + if !ok || copiedBool == nil { + t.Fatal("BoolNode.Copy returned invalid copy") + } + if copiedBool.String() != boolStr { + t.Fatalf("BoolNode.Copy().String() mismatch: got %q, want %q", copiedBool.String(), boolStr) + } + } + } + } + } + } +} + +// Test NumberNode +func TestNumberNode(t *testing.T) { + trees, err := parse.Parse("test", "{{42}}", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + // Find the NumberNode in the tree + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if action, ok := tree.Root.Nodes[0].(*parse.ActionNode); ok { + if action.Pipe != nil && len(action.Pipe.Cmds) > 0 { + if len(action.Pipe.Cmds[0].Args) > 0 { + if numNode, ok := action.Pipe.Cmds[0].Args[0].(*parse.NumberNode); ok { + numStr := numNode.String() + if numStr == "" { + t.Fatal("NumberNode.String returned empty") + } + copiedNum, ok := numNode.Copy().(*parse.NumberNode) + if !ok || copiedNum == nil { + t.Fatal("NumberNode.Copy returned invalid copy") + } + if copiedNum.String() != numStr { + t.Fatalf("NumberNode.Copy().String() mismatch: got %q, want %q", copiedNum.String(), numStr) + } + } + } + } + } + } +} + +// Test StringNode +func TestStringNode(t *testing.T) { + trees, err := parse.Parse("test", `{{"hello"}}`, "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + // Find the StringNode in the tree + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if action, ok := tree.Root.Nodes[0].(*parse.ActionNode); ok { + if action.Pipe != nil && len(action.Pipe.Cmds) > 0 { + if len(action.Pipe.Cmds[0].Args) > 0 { + if strNode, ok := action.Pipe.Cmds[0].Args[0].(*parse.StringNode); ok { + strVal := strNode.String() + if strVal != "\"hello\"" { + t.Fatalf("StringNode.String mismatch: got %q, want %q", strVal, "\"hello\"") + } + copiedStr, ok := strNode.Copy().(*parse.StringNode) + if !ok || copiedStr == nil { + t.Fatal("StringNode.Copy returned invalid copy") + } + if copiedStr.String() != strVal { + t.Fatalf("StringNode.Copy().String() mismatch: got %q, want %q", copiedStr.String(), strVal) + } + } + } + } + } + } +} + +// Test VariableNode +func TestVariableNode(t *testing.T) { + trees, err := parse.Parse("test", "{{$x := 1}}{{$x}}", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + // Find a VariableNode in the tree + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 1 { + if action, ok := tree.Root.Nodes[1].(*parse.ActionNode); ok { + if action.Pipe != nil && len(action.Pipe.Cmds) > 0 { + if len(action.Pipe.Cmds[0].Args) > 0 { + if varNode, ok := action.Pipe.Cmds[0].Args[0].(*parse.VariableNode); ok { + varStr := varNode.String() + if varStr != "$x" { + t.Fatalf("VariableNode.String mismatch: got %q, want %q", varStr, "$x") + } + copiedVar, ok := varNode.Copy().(*parse.VariableNode) + if !ok || copiedVar == nil { + t.Fatal("VariableNode.Copy returned invalid copy") + } + if copiedVar.String() != varStr { + t.Fatalf("VariableNode.Copy().String() mismatch: got %q, want %q", copiedVar.String(), varStr) + } + } + } + } + } + } +} + +// Test FieldNode +func TestFieldNode(t *testing.T) { + trees, err := parse.Parse("test", "{{.Field}}", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + // Find the FieldNode in the tree + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if action, ok := tree.Root.Nodes[0].(*parse.ActionNode); ok { + if action.Pipe != nil && len(action.Pipe.Cmds) > 0 { + if len(action.Pipe.Cmds[0].Args) > 0 { + if fieldNode, ok := action.Pipe.Cmds[0].Args[0].(*parse.FieldNode); ok { + fieldStr := fieldNode.String() + if fieldStr != ".Field" { + t.Fatalf("FieldNode.String mismatch: got %q, want %q", fieldStr, ".Field") + } + copiedField, ok := fieldNode.Copy().(*parse.FieldNode) + if !ok || copiedField == nil { + t.Fatal("FieldNode.Copy returned invalid copy") + } + if copiedField.String() != fieldStr { + t.Fatalf("FieldNode.Copy().String() mismatch: got %q, want %q", copiedField.String(), fieldStr) + } + } + } + } + } + } +} + +// Test TemplateNode +func TestTemplateNode(t *testing.T) { + trees, err := parse.Parse("test", `{{template "other" .}}`, "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + // Find the TemplateNode in the tree + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if tmplNode, ok := tree.Root.Nodes[0].(*parse.TemplateNode); ok { + tmplStr := tmplNode.String() + if tmplStr == "" { + t.Fatal("TemplateNode.String returned empty") + } + copiedTmpl, ok := tmplNode.Copy().(*parse.TemplateNode) + if !ok || copiedTmpl == nil { + t.Fatal("TemplateNode.Copy returned invalid copy") + } + if copiedTmpl.String() != tmplStr { + t.Fatalf("TemplateNode.Copy().String() mismatch: got %q, want %q", copiedTmpl.String(), tmplStr) + } + } + } +} + +// Test TextNode +func TestTextNode(t *testing.T) { + trees, err := parse.Parse("test", "plain text", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + // Find the TextNode in the tree + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if textNode, ok := tree.Root.Nodes[0].(*parse.TextNode); ok { + textStr := textNode.String() + if textStr != "plain text" { + t.Fatalf("TextNode.String mismatch: got %q, want %q", textStr, "plain text") + } + copiedText, ok := textNode.Copy().(*parse.TextNode) + if !ok || copiedText == nil { + t.Fatal("TextNode.Copy returned invalid copy") + } + if copiedText.String() != textStr { + t.Fatalf("TextNode.Copy().String() mismatch: got %q, want %q", copiedText.String(), textStr) + } + } + } +} + +// Test CommentNode +func TestCommentNode(t *testing.T) { + // ParseComments is a Mode, need to create a Tree with it + tree := parse.New("test") + tree.Mode = parse.ParseComments + treeSet := make(map[string]*parse.Tree) + _, err := tree.Parse("{{/* comment */}}", "{{", "}}", treeSet) + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + // Find the CommentNode in the tree + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if commentNode, ok := tree.Root.Nodes[0].(*parse.CommentNode); ok { + commentStr := commentNode.String() + if commentStr != "{{/* comment */}}" { + t.Fatalf("CommentNode.String mismatch: got %q, want %q", commentStr, "{{/* comment */}}") + } + copiedComment, ok := commentNode.Copy().(*parse.CommentNode) + if !ok || copiedComment == nil { + t.Fatal("CommentNode.Copy returned invalid copy") + } + if copiedComment.String() != commentStr { + t.Fatalf("CommentNode.Copy().String() mismatch: got %q, want %q", copiedComment.String(), commentStr) + } + } + } +} + +// Test BreakNode +func TestBreakNode(t *testing.T) { + trees, err := parse.Parse("test", "{{range .}}{{break}}{{end}}", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + // Find the BreakNode in the tree + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if rangeNode, ok := tree.Root.Nodes[0].(*parse.RangeNode); ok { + if rangeNode.List != nil && len(rangeNode.List.Nodes) > 0 { + if breakNode, ok := rangeNode.List.Nodes[0].(*parse.BreakNode); ok { + breakStr := breakNode.String() + if breakStr != "{{break}}" { + t.Fatalf("BreakNode.String mismatch: got %q, want %q", breakStr, "{{break}}") + } + copiedBreak, ok := breakNode.Copy().(*parse.BreakNode) + if !ok || copiedBreak == nil { + t.Fatal("BreakNode.Copy returned invalid copy") + } + if copiedBreak.String() != breakStr { + t.Fatalf("BreakNode.Copy().String() mismatch: got %q, want %q", copiedBreak.String(), breakStr) + } + } + } + } + } +} + +// Test ContinueNode +func TestContinueNode(t *testing.T) { + trees, err := parse.Parse("test", "{{range .}}{{continue}}{{end}}", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + // Find the ContinueNode in the tree + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if rangeNode, ok := tree.Root.Nodes[0].(*parse.RangeNode); ok { + if rangeNode.List != nil && len(rangeNode.List.Nodes) > 0 { + if continueNode, ok := rangeNode.List.Nodes[0].(*parse.ContinueNode); ok { + continueStr := continueNode.String() + if continueStr != "{{continue}}" { + t.Fatalf("ContinueNode.String mismatch: got %q, want %q", continueStr, "{{continue}}") + } + copiedContinue, ok := continueNode.Copy().(*parse.ContinueNode) + if !ok || copiedContinue == nil { + t.Fatal("ContinueNode.Copy returned invalid copy") + } + if copiedContinue.String() != continueStr { + t.Fatalf("ContinueNode.Copy().String() mismatch: got %q, want %q", copiedContinue.String(), continueStr) + } + } + } + } + } +} + +// Test ActionNode +func TestActionNode(t *testing.T) { + trees, err := parse.Parse("test", "{{.}}", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if action, ok := tree.Root.Nodes[0].(*parse.ActionNode); ok { + actionStr := action.String() + if actionStr != "{{.}}" { + t.Fatalf("ActionNode.String mismatch: got %q, want %q", actionStr, "{{.}}") + } + copiedAction, ok := action.Copy().(*parse.ActionNode) + if !ok || copiedAction == nil { + t.Fatal("ActionNode.Copy returned invalid copy") + } + if copiedAction.String() != actionStr { + t.Fatalf("ActionNode.Copy().String() mismatch: got %q, want %q", copiedAction.String(), actionStr) + } + } + } +} + +// Test ListNode +func TestListNode(t *testing.T) { + trees, err := parse.Parse("test", "{{range .}}text{{end}}", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if rangeNode, ok := tree.Root.Nodes[0].(*parse.RangeNode); ok { + if rangeNode.List != nil { + listStr := rangeNode.List.String() + if listStr != "text" { + t.Fatalf("ListNode.String mismatch: got %q, want %q", listStr, "text") + } + copiedList, ok := rangeNode.List.Copy().(*parse.ListNode) + if !ok || copiedList == nil { + t.Fatal("ListNode.Copy returned invalid copy") + } + if copiedList.String() != listStr { + t.Fatalf("ListNode.Copy().String() mismatch: got %q, want %q", copiedList.String(), listStr) + } + } + } + } +} + +// Test PipeNode +func TestPipeNode(t *testing.T) { + trees, err := parse.Parse("test", "{{.}}", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if action, ok := tree.Root.Nodes[0].(*parse.ActionNode); ok { + if action.Pipe != nil { + pipeStr := action.Pipe.String() + if pipeStr != "." { + t.Fatalf("PipeNode.String mismatch: got %q, want %q", pipeStr, ".") + } + copiedPipe, ok := action.Pipe.Copy().(*parse.PipeNode) + if !ok || copiedPipe == nil { + t.Fatal("PipeNode.Copy returned invalid copy") + } + if copiedPipe.String() != pipeStr { + t.Fatalf("PipeNode.Copy().String() mismatch: got %q, want %q", copiedPipe.String(), pipeStr) + } + } + } + } +} + +// Test CommandNode +func TestCommandNode(t *testing.T) { + trees, err := parse.Parse("test", "{{.}}", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if action, ok := tree.Root.Nodes[0].(*parse.ActionNode); ok { + if action.Pipe != nil && len(action.Pipe.Cmds) > 0 { + cmd := action.Pipe.Cmds[0] + cmdStr := cmd.String() + if cmdStr != "." { + t.Fatalf("CommandNode.String mismatch: got %q, want %q", cmdStr, ".") + } + copiedCmd, ok := cmd.Copy().(*parse.CommandNode) + if !ok || copiedCmd == nil { + t.Fatal("CommandNode.Copy returned invalid copy") + } + if copiedCmd.String() != cmdStr { + t.Fatalf("CommandNode.Copy().String() mismatch: got %q, want %q", copiedCmd.String(), cmdStr) + } + } + } + } +} + +// Test ChainNode +func TestChainNode(t *testing.T) { + trees, err := parse.Parse("test", "{{.A.B}}", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if action, ok := tree.Root.Nodes[0].(*parse.ActionNode); ok { + if action.Pipe != nil && len(action.Pipe.Cmds) > 0 { + if len(action.Pipe.Cmds[0].Args) > 0 { + if chain, ok := action.Pipe.Cmds[0].Args[0].(*parse.ChainNode); ok { + chainStr := chain.String() + if chainStr != ".A.B" { + t.Fatalf("ChainNode.String mismatch: got %q, want %q", chainStr, ".A.B") + } + copiedChain, ok := chain.Copy().(*parse.ChainNode) + if !ok || copiedChain == nil { + t.Fatal("ChainNode.Copy returned invalid copy") + } + if copiedChain.String() != chainStr { + t.Fatalf("ChainNode.Copy().String() mismatch: got %q, want %q", copiedChain.String(), chainStr) + } + } + } + } + } + } +} + +// Test BranchNode through IfNode +func TestBranchNodeIfNode(t *testing.T) { + trees, err := parse.Parse("test", "{{if .}}yes{{end}}", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if ifNode, ok := tree.Root.Nodes[0].(*parse.IfNode); ok { + // Test IfNode.Copy + copiedIf, ok := ifNode.Copy().(*parse.IfNode) + if !ok || copiedIf == nil { + t.Fatal("IfNode.Copy returned invalid copy") + } + if copiedIf.String() != ifNode.String() { + t.Fatalf("IfNode.Copy().String() mismatch: got %q, want %q", copiedIf.String(), ifNode.String()) + } + + // Test BranchNode methods through embedded field + branchStr := ifNode.BranchNode.String() + if branchStr == "" { + t.Fatal("BranchNode.String returned empty") + } + branchCopy := ifNode.BranchNode.Copy() + if branchCopy == nil { + t.Fatal("BranchNode.Copy returned nil") + } + if branchCopy.String() != branchStr { + t.Fatalf("BranchNode.Copy().String() mismatch: got %q, want %q", branchCopy.String(), branchStr) + } + } + } +} + +// Test RangeNode +func TestRangeNodeCopy(t *testing.T) { + trees, err := parse.Parse("test", "{{range .}}item{{end}}", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if rangeNode, ok := tree.Root.Nodes[0].(*parse.RangeNode); ok { + rangeStr := rangeNode.String() + if rangeStr == "" { + t.Fatal("RangeNode.String returned empty") + } + copiedRange, ok := rangeNode.Copy().(*parse.RangeNode) + if !ok || copiedRange == nil { + t.Fatal("RangeNode.Copy returned invalid copy") + } + if copiedRange.String() != rangeStr { + t.Fatalf("RangeNode.Copy().String() mismatch: got %q, want %q", copiedRange.String(), rangeStr) + } + } + } +} + +// Test WithNode +func TestWithNodeCopy(t *testing.T) { + trees, err := parse.Parse("test", "{{with .}}item{{end}}", "{{", "}}") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + tree := trees["test"] + if tree != nil && tree.Root != nil && len(tree.Root.Nodes) > 0 { + if withNode, ok := tree.Root.Nodes[0].(*parse.WithNode); ok { + withStr := withNode.String() + if withStr == "" { + t.Fatal("WithNode.String returned empty") + } + copiedWith, ok := withNode.Copy().(*parse.WithNode) + if !ok || copiedWith == nil { + t.Fatal("WithNode.Copy returned invalid copy") + } + if copiedWith.String() != withStr { + t.Fatalf("WithNode.Copy().String() mismatch: got %q, want %q", copiedWith.String(), withStr) + } + } + } +} + +// Test Mode type +func TestMode(t *testing.T) { + var m parse.Mode + m = parse.ParseComments + if m != parse.ParseComments { + t.Error("Mode assignment failed") + } + + m = parse.SkipFuncCheck + if m != parse.SkipFuncCheck { + t.Error("Mode assignment failed") + } +} diff --git a/test/std/text/template/template_test.go b/test/std/text/template/template_test.go new file mode 100644 index 0000000000..7846ea504c --- /dev/null +++ b/test/std/text/template/template_test.go @@ -0,0 +1,220 @@ +package template_test + +import ( + "bytes" + "errors" + "os" + "path/filepath" + "strings" + "testing" + "testing/fstest" + "text/template" + "text/template/parse" +) + +func TestTemplateEscapersAndTruth(t *testing.T) { + var htmlBuf bytes.Buffer + template.HTMLEscape(&htmlBuf, []byte("bold")) + if htmlBuf.String() != "<b>bold</b>" { + t.Fatalf("HTMLEscape mismatch: %q", htmlBuf.String()) + } + if res := template.HTMLEscapeString(""); res != "<tag>" { + t.Fatalf("HTMLEscapeString mismatch: %q", res) + } + if res := template.HTMLEscaper("<", ">"); res != "<>" { + t.Fatalf("HTMLEscaper mismatch: %q", res) + } + + var jsBuf bytes.Buffer + template.JSEscape(&jsBuf, []byte(`alert("x")`)) + jsEscaped := jsBuf.String() + if !strings.Contains(jsEscaped, `\"`) && !strings.Contains(jsEscaped, `\x22`) { + t.Fatalf("expected escaped quote in %q", jsBuf.String()) + } + if res := template.JSEscapeString(`"quote"`); !strings.Contains(res, `\"`) && !strings.Contains(res, `\x22`) { + t.Fatalf("JSEscapeString mismatch: %q", res) + } + if res := template.JSEscaper(`"`, `\`); (!strings.Contains(res, `\"`) && !strings.Contains(res, `\x22`)) || (!strings.Contains(res, `\\`) && !strings.Contains(res, `\x5c`)) { + t.Fatalf("JSEscaper mismatch: %q", res) + } + + if res := template.URLQueryEscaper("a b", "c&d"); res != "a+bc%26d" { + t.Fatalf("URLQueryEscaper mismatch: %q", res) + } + + truthy, ok := template.IsTrue(1) + if !ok || !truthy { + t.Fatalf("IsTrue should report non-zero ints as true, got ok=%v truthy=%v", ok, truthy) + } + falsey, ok := template.IsTrue("") + if !ok || falsey { + t.Fatalf("IsTrue should report empty string as false, got ok=%v truthy=%v", ok, falsey) + } +} + +func TestTemplateCreationAndExecution(t *testing.T) { + tmpl := template.Must(template.New("base").Funcs(template.FuncMap{ + "upper": strings.ToUpper, + }).Parse("Hello {{upper .Name}}")) + + if tmpl.Name() != "base" { + t.Fatalf("Name mismatch: %q", tmpl.Name()) + } + if tmpl.Lookup("base") == nil { + t.Fatal("Lookup should find base template") + } + if !strings.Contains(tmpl.DefinedTemplates(), "base") { + t.Fatalf("DefinedTemplates should list base, got %q", tmpl.DefinedTemplates()) + } + if tmpl.Option("missingkey=error") != tmpl { + t.Fatal("Option should return the template receiver") + } + + var buf bytes.Buffer + if err := tmpl.Execute(&buf, map[string]string{"Name": "LLGo"}); err != nil { + t.Fatalf("Execute failed: %v", err) + } + if buf.String() != "Hello LLGO" { + t.Fatalf("Execute output mismatch: %q", buf.String()) + } + + nested := template.Must(tmpl.New("nested").Parse(`{{define "nested"}}Hi {{.Name}}{{end}}`)) + if nested == nil { + t.Fatal("New should create nested template") + } + buf.Reset() + if err := tmpl.ExecuteTemplate(&buf, "nested", map[string]string{"Name": "Gopher"}); err != nil { + t.Fatalf("ExecuteTemplate failed: %v", err) + } + if buf.String() != "Hi Gopher" { + t.Fatalf("ExecuteTemplate output mismatch: %q", buf.String()) + } + + if len(tmpl.Templates()) == 0 { + t.Fatal("Templates should list associated templates") + } +} + +func TestTemplateCloneAndAddParseTree(t *testing.T) { + tmpl := template.Must(template.New("base").Parse("{{define \"base\"}}Base {{.}}{{end}}")) + + clone, err := tmpl.Clone() + if err != nil { + t.Fatalf("Clone failed: %v", err) + } + + clone = clone.Delims("[[", "]]") + if clone == nil { + t.Fatal("Delims should return template receiver") + } + + treeMap, err := parse.Parse("extra", "[[define \"extra\"]]Extra [[.]][[end]]", "[[", "]]") + if err != nil { + t.Fatalf("parse.Parse failed: %v", err) + } + tree, ok := treeMap["extra"] + if !ok { + t.Fatal("parse.Parse should return tree named extra") + } + + if _, err := clone.AddParseTree("extra", tree); err != nil { + t.Fatalf("AddParseTree failed: %v", err) + } + + var buf bytes.Buffer + if err := clone.ExecuteTemplate(&buf, "extra", "Template"); err != nil { + t.Fatalf("ExecuteTemplate failed: %v", err) + } + if buf.String() != "Extra Template" { + t.Fatalf("AddParseTree output mismatch: %q", buf.String()) + } +} + +func TestTemplateFileParsing(t *testing.T) { + tmpDir := t.TempDir() + fileA := filepath.Join(tmpDir, "a.tmpl") + if err := os.WriteFile(fileA, []byte("FileA {{.}}\n"), 0o600); err != nil { + t.Fatalf("WriteFile failed: %v", err) + } + fileB := filepath.Join(tmpDir, "b.tmpl") + if err := os.WriteFile(fileB, []byte("FileB {{.}}\n"), 0o600); err != nil { + t.Fatalf("WriteFile failed: %v", err) + } + + globTmpl := template.Must(template.ParseGlob(filepath.Join(tmpDir, "*.tmpl"))) + var globBuf bytes.Buffer + if err := globTmpl.ExecuteTemplate(&globBuf, "b.tmpl", "Y"); err != nil { + t.Fatalf("ParseGlob function ExecuteTemplate failed: %v", err) + } + if !strings.Contains(globBuf.String(), "FileB Y") { + t.Fatalf("ParseGlob function output mismatch: %q", globBuf.String()) + } + + filesTmpl := template.Must(template.ParseFiles(fileA, fileB)) + var buf bytes.Buffer + if err := filesTmpl.ExecuteTemplate(&buf, "a.tmpl", "X"); err != nil { + t.Fatalf("ExecuteTemplate failed: %v", err) + } + if !strings.Contains(buf.String(), "FileA X") { + t.Fatalf("ParseFiles output mismatch: %q", buf.String()) + } + + buf.Reset() + if _, err := filesTmpl.ParseGlob(filepath.Join(tmpDir, "*.tmpl")); err != nil { + t.Fatalf("ParseGlob method failed: %v", err) + } + + tmpl := template.Must(template.New("glob").Parse("{{define \"b.tmpl\"}}Override {{.}}{{end}}")) + if _, err := tmpl.ParseFiles(fileA); err != nil { + t.Fatalf("Template.ParseFiles failed: %v", err) + } + if _, err := tmpl.ParseGlob(filepath.Join(tmpDir, "*.tmpl")); err != nil { + t.Fatalf("Template.ParseGlob failed: %v", err) + } +} + +func TestTemplateParseFS(t *testing.T) { + fs := fstest.MapFS{ + "one.tmpl": &fstest.MapFile{Data: []byte("One {{.}}\n")}, + "extra/two": &fstest.MapFile{Data: []byte("Two {{.}}\n")}, + "extra/dup": &fstest.MapFile{Data: []byte("Dup {{.}}\n")}, + "glob/three": &fstest.MapFile{Data: []byte("Three {{.}}\n")}, + } + + tmpl := template.Must(template.ParseFS(fs, "one.tmpl", "extra/two")) + var buf bytes.Buffer + if err := tmpl.ExecuteTemplate(&buf, "one.tmpl", "A"); err != nil { + t.Fatalf("ExecuteTemplate failed: %v", err) + } + if !strings.Contains(buf.String(), "One A") { + t.Fatalf("ParseFS output mismatch: %q", buf.String()) + } + + buf.Reset() + base := template.Must(template.New("fs").Parse("{{define \"dup\"}}Base {{.}}{{end}}")) + if _, err := base.ParseFS(fs, "glob/*"); err != nil { + t.Fatalf("Template.ParseFS failed: %v", err) + } + if _, err := base.ParseFS(fs, "extra/dup"); err != nil { + t.Fatalf("Template.ParseFS duplicate failed: %v", err) + } + if base.Lookup("dup") == nil { + t.Fatal("ParseFS should preserve existing definitions") + } +} + +func TestExecErrorWrapping(t *testing.T) { + tmpl := template.Must(template.New("error").Option("missingkey=error").Parse("{{.Missing}}")) + var buf bytes.Buffer + err := tmpl.Execute(&buf, map[string]string{}) + var execErr template.ExecError + if !errors.As(err, &execErr) { + t.Fatalf("expected ExecError, got %T", err) + } + if execErr.Error() == "" { + t.Fatal("ExecError message should not be empty") + } + if execErr.Unwrap() == nil { + t.Fatal("ExecError should wrap underlying error") + } +} diff --git a/test/std/time/time_test.go b/test/std/time/time_test.go new file mode 100644 index 0000000000..26195f4bda --- /dev/null +++ b/test/std/time/time_test.go @@ -0,0 +1,391 @@ +package time_test + +import ( + "bytes" + "encoding/json" + "errors" + "testing" + "time" +) + +func TestTimeDurations(t *testing.T) { + d, err := time.ParseDuration("1.5s") + if err != nil { + t.Fatalf("ParseDuration failed: %v", err) + } + if d != 1500*time.Millisecond { + t.Fatalf("unexpected duration: %v", d) + } + + mixed := d + 250*time.Microsecond + 10*time.Nanosecond + if mixed.Abs() <= 0 { + t.Fatalf("Abs should be positive, got %v", mixed.Abs()) + } + if mixed.Hours() <= 0 { + t.Fatalf("Hours should be positive") + } + if mixed.Minutes() <= 0 { + t.Fatalf("Minutes should be positive") + } + if mixed.Seconds() <= 0 { + t.Fatalf("Seconds should be positive") + } + if mixed.Milliseconds() <= 0 { + t.Fatalf("Milliseconds should be positive") + } + if mixed.Microseconds() <= 0 { + t.Fatalf("Microseconds should be positive") + } + if mixed.Nanoseconds() <= 0 { + t.Fatalf("Nanoseconds should be positive") + } + if mixed.String() == "" { + t.Fatalf("Duration String should not be empty") + } + if mixed.Truncate(time.Second) != time.Second { + t.Fatalf("Truncate expected 1s, got %v", mixed.Truncate(time.Second)) + } + if mixed.Round(time.Second) != 2*time.Second { + t.Fatalf("Round expected 2s, got %v", mixed.Round(time.Second)) + } + + before := time.Now().Add(-25 * time.Millisecond) + if time.Since(before) <= 0 { + t.Fatalf("Since should be positive") + } + future := time.Now().Add(25 * time.Millisecond) + if time.Until(future) <= 0 { + t.Fatalf("Until should be positive") + } +} + +func TestTimeCreationAndFormatting(t *testing.T) { + base := time.Date(2024, time.November, 11, 9, 8, 7, 654321000, time.UTC) + if base.Year() != 2024 || base.Month() != time.November || base.Day() != 11 { + t.Fatalf("unexpected date components: %v", base) + } + if base.Hour() != 9 || base.Minute() != 8 || base.Second() != 7 || base.Nanosecond() != 654321000 { + t.Fatalf("unexpected clock components: %v", base) + } + y, m, d := base.Date() + if y != 2024 || m != time.November || d != 11 { + t.Fatalf("Date mismatch: %d %v %d", y, m, d) + } + h, min, s := base.Clock() + if h != 9 || min != 8 || s != 7 { + t.Fatalf("Clock mismatch: %d:%d:%d", h, min, s) + } + year, week := base.ISOWeek() + if year < 2024 || week == 0 { + t.Fatalf("unexpected ISO week: %d-%d", year, week) + } + if base.YearDay() == 0 { + t.Fatalf("YearDay should be positive") + } + if base.Weekday().String() == "" { + t.Fatalf("Weekday string empty") + } + if time.Saturday.String() == "" || time.December.String() == "" { + t.Fatalf("enum String should not be empty") + } + + if base.IsZero() { + t.Fatalf("base should not be zero") + } + if !base.Equal(base) { + t.Fatalf("Equal should be true for same instant") + } + + local := base.Local() + if local.Location() == nil { + t.Fatalf("Local should preserve location") + } + if time.Local == nil { + t.Fatalf("time.Local should not be nil") + } + + plus := base.Add(2*time.Hour + 30*time.Minute) + if delta := plus.Sub(base); delta != 150*time.Minute { + t.Fatalf("unexpected delta: %v", delta) + } + plus = plus.AddDate(0, 1, 1) + if !plus.After(base) || !base.Before(plus) { + t.Fatalf("After/Before relationship violated") + } + if comp := base.Compare(plus); comp >= 0 { + t.Fatalf("Compare expected negative, got %d", comp) + } + + utc := base.UTC() + if utc.Location() != time.UTC { + t.Fatalf("UTC location expected") + } + fixed := time.FixedZone("Fixed", 3600) + inFixed := base.In(fixed) + if inFixed.Location().String() != "Fixed" { + t.Fatalf("unexpected fixed zone: %v", inFixed.Location()) + } + if inFixed.IsDST() { + t.Fatalf("FixedZone should not observe DST") + } + if loc := time.Local; loc == nil { + t.Fatalf("time.Local should be non-nil") + } + + if formatted := base.Format(time.RFC3339Nano); formatted == "" { + t.Fatalf("Format expected non-empty") + } + appended := base.AppendFormat([]byte("prefix:"), time.Kitchen) + if !bytes.HasPrefix(appended, []byte("prefix:")) { + t.Fatalf("AppendFormat prefix missing: %s", appended) + } + textAppended, err := base.AppendText(nil) + if err != nil || len(textAppended) == 0 { + t.Fatalf("AppendText failed: %v", err) + } + binAppended, err := base.AppendBinary(nil) + if err != nil || len(binAppended) == 0 { + t.Fatalf("AppendBinary failed: %v", err) + } + + marshaledBinary, err := base.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary failed: %v", err) + } + var unmarshaled time.Time + if err := unmarshaled.UnmarshalBinary(marshaledBinary); err != nil { + t.Fatalf("UnmarshalBinary failed: %v", err) + } + + marshaledJSON, err := base.MarshalJSON() + if err != nil { + t.Fatalf("MarshalJSON failed: %v", err) + } + if err := unmarshaled.UnmarshalJSON(marshaledJSON); err != nil { + t.Fatalf("UnmarshalJSON failed: %v", err) + } + + marshaledText, err := base.MarshalText() + if err != nil { + t.Fatalf("MarshalText failed: %v", err) + } + if err := unmarshaled.UnmarshalText(marshaledText); err != nil { + t.Fatalf("UnmarshalText failed: %v", err) + } + + gobData, err := base.GobEncode() + if err != nil { + t.Fatalf("GobEncode failed: %v", err) + } + if err := (&unmarshaled).GobDecode(gobData); err != nil { + t.Fatalf("GobDecode failed: %v", err) + } + + if base.GoString() == "" || base.String() == "" { + t.Fatalf("GoString/String should not be empty") + } + + if base.Round(time.Minute).Sub(base.Truncate(time.Minute)) > time.Minute { + t.Fatalf("Round/Truncate difference too large") + } + + layouts := []string{ + time.ANSIC, + time.UnixDate, + time.RubyDate, + time.RFC822, + time.RFC822Z, + time.RFC850, + time.RFC1123, + time.RFC1123Z, + time.RFC3339, + time.RFC3339Nano, + time.Kitchen, + time.Layout, + time.Stamp, + time.StampMilli, + time.StampMicro, + time.StampNano, + time.DateTime, + time.DateOnly, + time.TimeOnly, + } + for _, layout := range layouts { + str := base.Format(layout) + if str == "" { + t.Fatalf("layout %q produced empty string", layout) + } + if _, err := time.Parse(layout, str); err != nil { + t.Fatalf("Parse failed for layout %q: %v", layout, err) + } + } + + unix := base.Unix() + if unix != time.Unix(unix, 0).Unix() { + t.Fatalf("Unix round trip failed") + } + if base.UnixMilli() != time.UnixMilli(base.UnixMilli()).UnixMilli() { + t.Fatalf("UnixMilli round trip failed") + } + if base.UnixMicro() != time.UnixMicro(base.UnixMicro()).UnixMicro() { + t.Fatalf("UnixMicro round trip failed") + } + if base.UnixNano() != time.Unix(0, base.UnixNano()).UnixNano() { + t.Fatalf("UnixNano round trip failed") + } + + start, end := inFixed.ZoneBounds() + if !(start.Before(end) || start.Equal(end)) { + t.Fatal("ZoneBounds returned invalid order") + } + if name, offset := inFixed.Zone(); name == "" || offset != 3600 { + t.Fatalf("Zone information incorrect: %s %d", name, offset) + } +} + +func TestTimeParsingAndLocations(t *testing.T) { + locUTC, err := time.LoadLocation("UTC") + if err != nil { + t.Fatalf("LoadLocation UTC failed: %v", err) + } + if locUTC.String() != "UTC" { + t.Fatalf("expected UTC string, got %s", locUTC) + } + if _, err := time.LoadLocationFromTZData("Invalid", []byte("TZif")); err == nil { + t.Fatalf("expected error for invalid tz data") + } + + parsed, err := time.Parse(time.RFC3339, "2024-11-11T09:08:00Z") + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + parsedInLoc, err := time.ParseInLocation(time.RFC822, "11 Nov 24 09:08 UTC", locUTC) + if err != nil { + t.Fatalf("ParseInLocation failed: %v", err) + } + if !parsed.Equal(parsedInLoc) { + t.Fatalf("expected parsed times to equal") + } + + if _, err := time.Parse("2006", "invalid"); err == nil { + t.Fatalf("expected parse error") + } else { + var parseErr *time.ParseError + if !errors.As(err, &parseErr) { + t.Fatalf("expected ParseError, got %T", err) + } + if parseErr.Error() == "" { + t.Fatalf("ParseError error string empty") + } + } +} + +func TestTimeTimersAndTickers(t *testing.T) { + timer := time.NewTimer(50 * time.Millisecond) + if !timer.Stop() { + t.Fatalf("expected Stop to return true before expiry") + } + if timer.Reset(10 * time.Millisecond) { + t.Fatalf("Reset should report false on stopped timer") + } + select { + case <-timer.C: + case <-time.After(100 * time.Millisecond): + t.Fatalf("timer did not fire after reset") + } + timer.Stop() + + ticker := time.NewTicker(5 * time.Millisecond) + defer ticker.Stop() + select { + case <-ticker.C: + case <-time.After(50 * time.Millisecond): + t.Fatalf("ticker did not tick") + } + ticker.Reset(5 * time.Millisecond) + select { + case <-ticker.C: + case <-time.After(50 * time.Millisecond): + t.Fatalf("ticker did not tick after reset") + } + + tickCh := time.Tick(5 * time.Millisecond) + select { + case <-tickCh: + case <-time.After(50 * time.Millisecond): + t.Fatalf("tick channel did not deliver") + } + + afterCh := time.After(5 * time.Millisecond) + select { + case <-afterCh: + case <-time.After(50 * time.Millisecond): + t.Fatalf("After did not deliver") + } + + done := make(chan struct{}) + timerFunc := time.AfterFunc(5*time.Millisecond, func() { close(done) }) + select { + case <-done: + case <-time.After(100 * time.Millisecond): + t.Fatalf("AfterFunc did not execute") + } + timerFunc.Stop() + + time.Sleep(5 * time.Millisecond) + if since := time.Since(time.Now().Add(-2 * time.Millisecond)); since <= 0 { + t.Fatalf("Since expected positive") + } +} + +func TestTimeJSONInterop(t *testing.T) { + payload := struct { + When time.Time `json:"when"` + Span time.Duration `json:"span"` + }{ + When: time.Date(2023, time.June, 1, 12, 0, 0, 0, time.UTC), + Span: 42 * time.Second, + } + data, err := json.Marshal(payload) + if err != nil { + t.Fatalf("json.Marshal failed: %v", err) + } + var decoded struct { + When time.Time `json:"when"` + Span time.Duration `json:"span"` + } + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("json.Unmarshal failed: %v", err) + } + if !decoded.When.Equal(payload.When) || decoded.Span != payload.Span { + t.Fatalf("JSON round trip mismatch: %+v vs %+v", decoded, payload) + } +} + +func TestTimeGlobalFunctions(t *testing.T) { + now := time.Now() + if now.IsZero() { + t.Fatalf("Now returned zero") + } + unix := time.Unix(now.Unix(), int64(now.Nanosecond())) + if !unix.Round(time.Nanosecond).UTC().Equal(time.Unix(now.Unix(), int64(now.Nanosecond())).UTC()) { + t.Fatalf("Unix reconstruction mismatch") + } + unixMilli := time.UnixMilli(now.UnixMilli()) + if unixMilli.UnixMilli() != now.UnixMilli() { + t.Fatalf("UnixMilli round trip mismatch") + } + unixMicro := time.UnixMicro(now.UnixMicro()) + if unixMicro.UnixMicro() != now.UnixMicro() { + t.Fatalf("UnixMicro round trip mismatch") + } + + future := now.Add(10 * time.Millisecond) + if time.Until(future) <= 0 { + t.Fatalf("Until should be positive") + } + + if time.Nanosecond == 0 || time.Second == 0 || time.Hour == 0 { + t.Fatalf("time unit constants should be non-zero") + } +} diff --git a/test/std/time/tzdata/tzdata_test.go b/test/std/time/tzdata/tzdata_test.go new file mode 100644 index 0000000000..6f031c1b47 --- /dev/null +++ b/test/std/time/tzdata/tzdata_test.go @@ -0,0 +1,21 @@ +package tzdata_test + +import ( + "testing" + "time" + + _ "time/tzdata" +) + +func TestEmbeddedTZDataAvailable(t *testing.T) { + // Force the time package not to use an external zoneinfo directory. + t.Setenv("ZONEINFO", "/__llgo_nonexistent_zoneinfo__") + + loc, err := time.LoadLocation("Asia/Shanghai") + if err != nil { + t.Fatalf("LoadLocation failed with embedded tzdata: %v", err) + } + if got := loc.String(); got != "Asia/Shanghai" { + t.Fatalf("location name = %q, want %q", got, "Asia/Shanghai") + } +} diff --git a/test/std/unicode/go126_symbols_test.go b/test/std/unicode/go126_symbols_test.go new file mode 100644 index 0000000000..8b0bfb3480 --- /dev/null +++ b/test/std/unicode/go126_symbols_test.go @@ -0,0 +1,23 @@ +//go:build go1.26 + +package unicode_test + +import ( + "testing" + "unicode" +) + +func TestGo126Categories(t *testing.T) { + if got := unicode.CategoryAliases["Unassigned"]; got != "Cn" { + t.Fatalf("CategoryAliases[Unassigned] = %q, want Cn", got) + } + if got := unicode.CategoryAliases["Cased_Letter"]; got != "LC" { + t.Fatalf("CategoryAliases[Cased_Letter] = %q, want LC", got) + } + if !unicode.Is(unicode.Cn, '\u0378') || unicode.Is(unicode.Cn, 'A') { + t.Fatal("Cn does not identify unassigned code points") + } + if !unicode.Is(unicode.LC, 'A') || !unicode.Is(unicode.LC, 'a') || unicode.Is(unicode.LC, '1') { + t.Fatal("LC does not identify cased letters") + } +} diff --git a/test/std/unicode/unicode_symbols_test.go b/test/std/unicode/unicode_symbols_test.go new file mode 100644 index 0000000000..cf143d1972 --- /dev/null +++ b/test/std/unicode/unicode_symbols_test.go @@ -0,0 +1,312 @@ +// Code generated by go run gen_symbols.go; DO NOT EDIT. + +package unicode_test + +import "unicode" + +const ( + _ = unicode.LowerCase + _ = unicode.MaxASCII + _ = unicode.MaxCase + _ = unicode.MaxLatin1 + _ = unicode.MaxRune + _ = unicode.ReplacementChar + _ = unicode.TitleCase + _ = unicode.UpperCase + _ = unicode.UpperLower + _ = unicode.Version +) + +var ( + _ = unicode.ASCII_Hex_Digit + _ = unicode.Adlam + _ = unicode.Ahom + _ = unicode.Anatolian_Hieroglyphs + _ = unicode.Arabic + _ = unicode.Armenian + _ = unicode.Avestan + _ = unicode.Balinese + _ = unicode.Bamum + _ = unicode.Bassa_Vah + _ = unicode.Batak + _ = unicode.Bengali + _ = unicode.Bhaiksuki + _ = unicode.Bidi_Control + _ = unicode.Bopomofo + _ = unicode.Brahmi + _ = unicode.Braille + _ = unicode.Buginese + _ = unicode.Buhid + _ = unicode.C + _ = unicode.Canadian_Aboriginal + _ = unicode.Carian + _ = unicode.CaseRanges + _ = unicode.Categories + _ = unicode.Caucasian_Albanian + _ = unicode.Cc + _ = unicode.Cf + _ = unicode.Chakma + _ = unicode.Cham + _ = unicode.Cherokee + _ = unicode.Chorasmian + _ = unicode.Co + _ = unicode.Common + _ = unicode.Coptic + _ = unicode.Cs + _ = unicode.Cuneiform + _ = unicode.Cypriot + _ = unicode.Cypro_Minoan + _ = unicode.Cyrillic + _ = unicode.Dash + _ = unicode.Deprecated + _ = unicode.Deseret + _ = unicode.Devanagari + _ = unicode.Diacritic + _ = unicode.Digit + _ = unicode.Dives_Akuru + _ = unicode.Dogra + _ = unicode.Duployan + _ = unicode.Egyptian_Hieroglyphs + _ = unicode.Elbasan + _ = unicode.Elymaic + _ = unicode.Ethiopic + _ = unicode.Extender + _ = unicode.FoldCategory + _ = unicode.FoldScript + _ = unicode.Georgian + _ = unicode.Glagolitic + _ = unicode.Gothic + _ = unicode.Grantha + _ = unicode.GraphicRanges + _ = unicode.Greek + _ = unicode.Gujarati + _ = unicode.Gunjala_Gondi + _ = unicode.Gurmukhi + _ = unicode.Han + _ = unicode.Hangul + _ = unicode.Hanifi_Rohingya + _ = unicode.Hanunoo + _ = unicode.Hatran + _ = unicode.Hebrew + _ = unicode.Hex_Digit + _ = unicode.Hiragana + _ = unicode.Hyphen + _ = unicode.IDS_Binary_Operator + _ = unicode.IDS_Trinary_Operator + _ = unicode.Ideographic + _ = unicode.Imperial_Aramaic + _ = unicode.Inherited + _ = unicode.Inscriptional_Pahlavi + _ = unicode.Inscriptional_Parthian + _ = unicode.Javanese + _ = unicode.Join_Control + _ = unicode.Kaithi + _ = unicode.Kannada + _ = unicode.Katakana + _ = unicode.Kawi + _ = unicode.Kayah_Li + _ = unicode.Kharoshthi + _ = unicode.Khitan_Small_Script + _ = unicode.Khmer + _ = unicode.Khojki + _ = unicode.Khudawadi + _ = unicode.L + _ = unicode.Lao + _ = unicode.Latin + _ = unicode.Lepcha + _ = unicode.Letter + _ = unicode.Limbu + _ = unicode.Linear_A + _ = unicode.Linear_B + _ = unicode.Lisu + _ = unicode.Ll + _ = unicode.Lm + _ = unicode.Lo + _ = unicode.Logical_Order_Exception + _ = unicode.Lower + _ = unicode.Lt + _ = unicode.Lu + _ = unicode.Lycian + _ = unicode.Lydian + _ = unicode.M + _ = unicode.Mahajani + _ = unicode.Makasar + _ = unicode.Malayalam + _ = unicode.Mandaic + _ = unicode.Manichaean + _ = unicode.Marchen + _ = unicode.Mark + _ = unicode.Masaram_Gondi + _ = unicode.Mc + _ = unicode.Me + _ = unicode.Medefaidrin + _ = unicode.Meetei_Mayek + _ = unicode.Mende_Kikakui + _ = unicode.Meroitic_Cursive + _ = unicode.Meroitic_Hieroglyphs + _ = unicode.Miao + _ = unicode.Mn + _ = unicode.Modi + _ = unicode.Mongolian + _ = unicode.Mro + _ = unicode.Multani + _ = unicode.Myanmar + _ = unicode.N + _ = unicode.Nabataean + _ = unicode.Nag_Mundari + _ = unicode.Nandinagari + _ = unicode.Nd + _ = unicode.New_Tai_Lue + _ = unicode.Newa + _ = unicode.Nko + _ = unicode.Nl + _ = unicode.No + _ = unicode.Noncharacter_Code_Point + _ = unicode.Number + _ = unicode.Nushu + _ = unicode.Nyiakeng_Puachue_Hmong + _ = unicode.Ogham + _ = unicode.Ol_Chiki + _ = unicode.Old_Hungarian + _ = unicode.Old_Italic + _ = unicode.Old_North_Arabian + _ = unicode.Old_Permic + _ = unicode.Old_Persian + _ = unicode.Old_Sogdian + _ = unicode.Old_South_Arabian + _ = unicode.Old_Turkic + _ = unicode.Old_Uyghur + _ = unicode.Oriya + _ = unicode.Osage + _ = unicode.Osmanya + _ = unicode.Other + _ = unicode.Other_Alphabetic + _ = unicode.Other_Default_Ignorable_Code_Point + _ = unicode.Other_Grapheme_Extend + _ = unicode.Other_ID_Continue + _ = unicode.Other_ID_Start + _ = unicode.Other_Lowercase + _ = unicode.Other_Math + _ = unicode.Other_Uppercase + _ = unicode.P + _ = unicode.Pahawh_Hmong + _ = unicode.Palmyrene + _ = unicode.Pattern_Syntax + _ = unicode.Pattern_White_Space + _ = unicode.Pau_Cin_Hau + _ = unicode.Pc + _ = unicode.Pd + _ = unicode.Pe + _ = unicode.Pf + _ = unicode.Phags_Pa + _ = unicode.Phoenician + _ = unicode.Pi + _ = unicode.Po + _ = unicode.Prepended_Concatenation_Mark + _ = unicode.PrintRanges + _ = unicode.Properties + _ = unicode.Ps + _ = unicode.Psalter_Pahlavi + _ = unicode.Punct + _ = unicode.Quotation_Mark + _ = unicode.Radical + _ = unicode.Regional_Indicator + _ = unicode.Rejang + _ = unicode.Runic + _ = unicode.S + _ = unicode.STerm + _ = unicode.Samaritan + _ = unicode.Saurashtra + _ = unicode.Sc + _ = unicode.Scripts + _ = unicode.Sentence_Terminal + _ = unicode.Sharada + _ = unicode.Shavian + _ = unicode.Siddham + _ = unicode.SignWriting + _ = unicode.Sinhala + _ = unicode.Sk + _ = unicode.Sm + _ = unicode.So + _ = unicode.Soft_Dotted + _ = unicode.Sogdian + _ = unicode.Sora_Sompeng + _ = unicode.Soyombo + _ = unicode.Space + _ = unicode.Sundanese + _ = unicode.Syloti_Nagri + _ = unicode.Symbol + _ = unicode.Syriac + _ = unicode.Tagalog + _ = unicode.Tagbanwa + _ = unicode.Tai_Le + _ = unicode.Tai_Tham + _ = unicode.Tai_Viet + _ = unicode.Takri + _ = unicode.Tamil + _ = unicode.Tangsa + _ = unicode.Tangut + _ = unicode.Telugu + _ = unicode.Terminal_Punctuation + _ = unicode.Thaana + _ = unicode.Thai + _ = unicode.Tibetan + _ = unicode.Tifinagh + _ = unicode.Tirhuta + _ = unicode.Title + _ = unicode.Toto + _ = unicode.Ugaritic + _ = unicode.Unified_Ideograph + _ = unicode.Upper + _ = unicode.Vai + _ = unicode.Variation_Selector + _ = unicode.Vithkuqi + _ = unicode.Wancho + _ = unicode.Warang_Citi + _ = unicode.White_Space + _ = unicode.Yezidi + _ = unicode.Yi + _ = unicode.Z + _ = unicode.Zanabazar_Square + _ = unicode.Zl + _ = unicode.Zp + _ = unicode.Zs +) + +var ( + _ unicode.CaseRange + _ unicode.Range16 + _ unicode.Range32 + _ unicode.RangeTable + _ unicode.SpecialCase +) + +var ( + _ = unicode.In + _ = unicode.Is + _ = unicode.IsControl + _ = unicode.IsDigit + _ = unicode.IsGraphic + _ = unicode.IsLetter + _ = unicode.IsLower + _ = unicode.IsMark + _ = unicode.IsNumber + _ = unicode.IsOneOf + _ = unicode.IsPrint + _ = unicode.IsPunct + _ = unicode.IsSpace + _ = unicode.IsSymbol + _ = unicode.IsTitle + _ = unicode.IsUpper + _ = unicode.SimpleFold + _ = unicode.To + _ = unicode.ToLower + _ = unicode.ToTitle + _ = unicode.ToUpper +) + +var ( + _ = unicode.SpecialCase{}.ToLower + _ = unicode.SpecialCase{}.ToTitle + _ = unicode.SpecialCase{}.ToUpper +) diff --git a/test/std/unicode/unicode_test.go b/test/std/unicode/unicode_test.go new file mode 100644 index 0000000000..251244b16a --- /dev/null +++ b/test/std/unicode/unicode_test.go @@ -0,0 +1,73 @@ +package unicode_test + +import ( + "testing" + "unicode" +) + +func TestCategoryChecks(t *testing.T) { + if !unicode.IsLetter('A') { + t.Fatalf("IsLetter('A') = false, want true") + } + if unicode.IsLetter('5') { + t.Fatalf("IsLetter('5') = true, want false") + } + if !unicode.IsDigit('9') { + t.Fatalf("IsDigit('9') = false, want true") + } + if unicode.IsDigit('A') { + t.Fatalf("IsDigit('A') = true, want false") + } + if !unicode.IsSpace('\u00A0') { + t.Fatalf("IsSpace(NBSP) = false, want true") + } + if !unicode.Is(unicode.Latin, 'ñ') { + t.Fatalf("Is(Latin, 'ñ') = false, want true") + } + if unicode.Is(unicode.Greek, 'A') { + t.Fatalf("Is(Greek, 'A') = true, want false") + } + if !unicode.In('世', unicode.Han, unicode.Latin) { + t.Fatalf("In('世', Han, Latin) = false, want true") + } + if unicode.In('世', unicode.Latin) { + t.Fatalf("In('世', Latin) = true, want false") + } +} + +func TestCaseConversions(t *testing.T) { + if got := unicode.ToUpper('ß'); got != 'ß' { + t.Fatalf("ToUpper('ß') = %U, want %U", got, 'ß') + } + if got := unicode.ToLower('İ'); got != 'i' { + t.Fatalf("ToLower('İ') = %U, want %U", got, 'i') + } + if got := unicode.ToTitle('ß'); got != 'ß' { + t.Fatalf("ToTitle('ß') = %U, want %U", got, 'ß') + } + + if folded := unicode.SimpleFold('A'); folded != 'a' { + t.Fatalf("SimpleFold('A') = %U, want %U", folded, 'a') + } + if folded := unicode.SimpleFold('a'); folded != 'A' { + t.Fatalf("SimpleFold('a') = %U, want %U", folded, 'A') + } + if got := unicode.To(unicode.UpperCase, 'i'); got != 'I' { + t.Fatalf("To(UpperCase, 'i') = %U, want %U", got, 'I') + } + if got := unicode.To(unicode.TitleCase, 'β'); got != 'Β' { + t.Fatalf("To(TitleCase, 'β') = %U, want %U", got, 'Β') + } +} + +func TestSpecialCaseTurkish(t *testing.T) { + if got := unicode.TurkishCase.ToUpper('i'); got != 'İ' { + t.Fatalf("TurkishCase.ToUpper('i') = %U, want %U", got, 'İ') + } + if got := unicode.TurkishCase.ToLower('İ'); got != 'i' { + t.Fatalf("TurkishCase.ToLower('İ') = %U, want %U", got, 'i') + } + if got := unicode.AzeriCase.ToTitle('i'); got != 'İ' { + t.Fatalf("AzeriCase.ToTitle('i') = %U, want %U", got, 'İ') + } +} diff --git a/test/std/unicode/utf16/utf16_test.go b/test/std/unicode/utf16/utf16_test.go new file mode 100644 index 0000000000..93e9ded59a --- /dev/null +++ b/test/std/unicode/utf16/utf16_test.go @@ -0,0 +1,209 @@ +package utf16_test + +import ( + "testing" + "unicode/utf16" +) + +// Test Encode and Decode +func TestEncodeDecode(t *testing.T) { + tests := [][]rune{ + {}, + {'A'}, + {'A', 'B', 'C'}, + {'中', '文'}, + {'🌟', '✨'}, + {'H', 'e', 'l', 'l', 'o', ',', ' ', '世', '界'}, + } + + for _, runes := range tests { + encoded := utf16.Encode(runes) + decoded := utf16.Decode(encoded) + + if len(decoded) != len(runes) { + t.Errorf("Decode(Encode(%v)) length = %d, want %d", runes, len(decoded), len(runes)) + continue + } + + for i := range runes { + if decoded[i] != runes[i] { + t.Errorf("Decode(Encode(%v))[%d] = %U, want %U", runes, i, decoded[i], runes[i]) + } + } + } +} + +// Test EncodeRune and DecodeRune +func TestEncodeDecodeRune(t *testing.T) { + // EncodeRune only returns valid pairs for non-BMP characters + // For BMP characters, it returns U+FFFD, U+FFFD + tests := []struct { + r rune + r1 rune + r2 rune + desc string + }{ + {'A', 0xFFFD, 0xFFFD, "ASCII character (doesn't need encoding)"}, + {'中', 0xFFFD, 0xFFFD, "BMP character (doesn't need encoding)"}, + {'🌟', 0xD83C, 0xDF1F, "Non-BMP character (emoji)"}, + {'😀', 0xD83D, 0xDE00, "Non-BMP character (smiley)"}, + {0x10000, 0xD800, 0xDC00, "First non-BMP character"}, + {0x10FFFF, 0xDBFF, 0xDFFF, "Last valid Unicode character"}, + } + + for _, tt := range tests { + r1, r2 := utf16.EncodeRune(tt.r) + if r1 != tt.r1 || r2 != tt.r2 { + t.Errorf("EncodeRune(%U) = (%U, %U), want (%U, %U) [%s]", + tt.r, r1, r2, tt.r1, tt.r2, tt.desc) + } + + // Only test DecodeRune for valid surrogate pairs (not 0xFFFD) + if r1 != 0xFFFD && r2 != 0xFFFD { + decoded := utf16.DecodeRune(r1, r2) + if decoded != tt.r { + t.Errorf("DecodeRune(%U, %U) = %U, want %U [%s]", + r1, r2, decoded, tt.r, tt.desc) + } + } + } +} + +// Test IsSurrogate +func TestIsSurrogate(t *testing.T) { + tests := []struct { + r rune + want bool + }{ + {'A', false}, + {'中', false}, + {0xD7FF, false}, // Just before surrogate range + {0xD800, true}, // First high surrogate + {0xDBFF, true}, // Last high surrogate + {0xDC00, true}, // First low surrogate + {0xDFFF, true}, // Last low surrogate + {0xE000, false}, // Just after surrogate range + {'🌟', false}, + } + + for _, tt := range tests { + got := utf16.IsSurrogate(tt.r) + if got != tt.want { + t.Errorf("IsSurrogate(%U) = %v, want %v", tt.r, got, tt.want) + } + } +} + +// Test RuneLen +func TestRuneLen(t *testing.T) { + tests := []struct { + r rune + want int + }{ + {'A', 1}, + {'中', 1}, + {0xD7FF, 1}, // Last BMP character before surrogates + {0xE000, 1}, // First BMP character after surrogates + {0xFFFF, 1}, // Last BMP character + {0x10000, 2}, // First non-BMP character (requires surrogate pair) + {'🌟', 2}, + {0x10FFFF, 2}, // Last valid Unicode character + {0x110000, -1}, // Invalid (beyond Unicode range) + {-1, -1}, // Invalid (negative) + } + + for _, tt := range tests { + got := utf16.RuneLen(tt.r) + if got != tt.want { + t.Errorf("RuneLen(%U) = %d, want %d", tt.r, got, tt.want) + } + } +} + +// Test AppendRune +func TestAppendRune(t *testing.T) { + // Start with empty slice + a := []uint16{} + + // Append BMP character + a = utf16.AppendRune(a, 'A') + if len(a) != 1 || a[0] != 'A' { + t.Errorf("After AppendRune 'A', got %v, want [65]", a) + } + + // Append another BMP character + a = utf16.AppendRune(a, '中') + if len(a) != 2 || a[1] != '中' { + t.Errorf("After AppendRune '中', got %v", a) + } + + // Append non-BMP character (requires surrogate pair) + a = utf16.AppendRune(a, '🌟') + if len(a) != 4 { + t.Errorf("After AppendRune '🌟', length = %d, want 4", len(a)) + } + + // Verify we can decode it back + decoded := utf16.Decode(a) + expected := []rune{'A', '中', '🌟'} + if len(decoded) != len(expected) { + t.Errorf("Decoded length = %d, want %d", len(decoded), len(expected)) + } + for i := range expected { + if decoded[i] != expected[i] { + t.Errorf("Decoded[%d] = %U, want %U", i, decoded[i], expected[i]) + } + } +} + +// Test edge cases +func TestEdgeCases(t *testing.T) { + // Test empty slices + emptyRunes := []rune{} + encoded := utf16.Encode(emptyRunes) + if len(encoded) != 0 { + t.Errorf("Encode([]) = %v, want []", encoded) + } + + emptyUint16 := []uint16{} + decoded := utf16.Decode(emptyUint16) + if len(decoded) != 0 { + t.Errorf("Decode([]) = %v, want []", decoded) + } + + // Test invalid surrogate pairs + invalidPairs := [][]uint16{ + {0xD800}, // High surrogate without low surrogate + {0xDC00}, // Low surrogate without high surrogate + {0xD800, 'A'}, // High surrogate followed by non-surrogate + {0xDC00, 0xD800}, // Low surrogate followed by high surrogate + } + + for _, pair := range invalidPairs { + decoded := utf16.Decode(pair) + // Decode should handle invalid sequences gracefully + // (exact behavior may vary, but it shouldn't crash) + _ = decoded + } +} + +// Test with real-world text +func TestRealWorld(t *testing.T) { + testStrings := []string{ + "Hello, World!", + "你好,世界!", + "🌍🌎🌏", + "Hello, 世界! 🌟", + "Emoji: 😀😁😂🤣😃😄😅😆", + } + + for _, str := range testStrings { + runes := []rune(str) + encoded := utf16.Encode(runes) + decoded := utf16.Decode(encoded) + + if string(decoded) != str { + t.Errorf("Round-trip failed for %q: got %q", str, string(decoded)) + } + } +} diff --git a/test/std/unicode/utf8/utf8_test.go b/test/std/unicode/utf8/utf8_test.go new file mode 100644 index 0000000000..dd7b774d7d --- /dev/null +++ b/test/std/unicode/utf8/utf8_test.go @@ -0,0 +1,285 @@ +package utf8_test + +import ( + "testing" + "unicode/utf8" +) + +// Test constants +func TestConstants(t *testing.T) { + if utf8.RuneError != '\uFFFD' { + t.Errorf("RuneError should be U+FFFD, got %U", utf8.RuneError) + } + + if utf8.RuneSelf != 0x80 { + t.Errorf("RuneSelf should be 0x80, got %#x", utf8.RuneSelf) + } + + if utf8.MaxRune != '\U0010FFFF' { + t.Errorf("MaxRune should be U+10FFFF, got %U", utf8.MaxRune) + } + + if utf8.UTFMax != 4 { + t.Errorf("UTFMax should be 4, got %d", utf8.UTFMax) + } +} + +// Test EncodeRune and DecodeRune +func TestEncodeDecodeRune(t *testing.T) { + tests := []rune{ + 'A', + '中', + '文', + '🌟', + utf8.RuneError, + utf8.MaxRune, + } + + for _, r := range tests { + buf := make([]byte, utf8.UTFMax) + n := utf8.EncodeRune(buf, r) + if n == 0 { + t.Errorf("EncodeRune failed for %U", r) + continue + } + + decoded, size := utf8.DecodeRune(buf[:n]) + if size != n { + t.Errorf("DecodeRune size mismatch: encoded %d, decoded %d for %U", n, size, r) + } + if decoded != r { + t.Errorf("DecodeRune mismatch: encoded %U, decoded %U", r, decoded) + } + } +} + +// Test AppendRune +func TestAppendRune(t *testing.T) { + p := []byte("hello ") + p = utf8.AppendRune(p, '世') + p = utf8.AppendRune(p, '界') + + expected := "hello 世界" + if string(p) != expected { + t.Errorf("AppendRune result = %q, want %q", string(p), expected) + } +} + +// Test RuneLen +func TestRuneLen(t *testing.T) { + tests := []struct { + r rune + want int + }{ + {'A', 1}, + {'中', 3}, + {'🌟', 4}, + {utf8.MaxRune, 4}, + {utf8.MaxRune + 1, -1}, // Invalid rune + {-1, -1}, // Invalid rune + } + + for _, tt := range tests { + got := utf8.RuneLen(tt.r) + if got != tt.want { + t.Errorf("RuneLen(%U) = %d, want %d", tt.r, got, tt.want) + } + } +} + +// Test RuneCount and RuneCountInString +func TestRuneCount(t *testing.T) { + tests := []struct { + s string + want int + }{ + {"", 0}, + {"a", 1}, + {"abc", 3}, + {"中文", 2}, + {"Hello, 世界", 9}, + {"🌟✨", 2}, + } + + for _, tt := range tests { + // Test RuneCount + got := utf8.RuneCount([]byte(tt.s)) + if got != tt.want { + t.Errorf("RuneCount(%q) = %d, want %d", tt.s, got, tt.want) + } + + // Test RuneCountInString + got = utf8.RuneCountInString(tt.s) + if got != tt.want { + t.Errorf("RuneCountInString(%q) = %d, want %d", tt.s, got, tt.want) + } + } +} + +// Test FullRune and FullRuneInString +func TestFullRune(t *testing.T) { + tests := []struct { + s string + want bool + }{ + {"a", true}, + {"中", true}, + {"\xE4", false}, // Incomplete UTF-8 + {"\xE4\xB8", false}, // Incomplete UTF-8 + {"\xE4\xB8\xAD", true}, // Complete "中" + {"", false}, + {"\xF0\x9F\x8C\x9F", true}, // Complete "🌟" + {"\xF0\x9F\x8C", false}, // Incomplete emoji + } + + for _, tt := range tests { + // Test FullRune + got := utf8.FullRune([]byte(tt.s)) + if got != tt.want { + t.Errorf("FullRune(%q) = %v, want %v", tt.s, got, tt.want) + } + + // Test FullRuneInString + got = utf8.FullRuneInString(tt.s) + if got != tt.want { + t.Errorf("FullRuneInString(%q) = %v, want %v", tt.s, got, tt.want) + } + } +} + +// Test DecodeRuneInString +func TestDecodeRuneInString(t *testing.T) { + tests := []struct { + s string + wantRune rune + wantSize int + }{ + {"a", 'a', 1}, + {"中文", '中', 3}, + {"🌟", '🌟', 4}, + {"", utf8.RuneError, 0}, + {"\x80", utf8.RuneError, 1}, // Invalid UTF-8 + } + + for _, tt := range tests { + r, size := utf8.DecodeRuneInString(tt.s) + if r != tt.wantRune || size != tt.wantSize { + t.Errorf("DecodeRuneInString(%q) = (%U, %d), want (%U, %d)", + tt.s, r, size, tt.wantRune, tt.wantSize) + } + } +} + +// Test DecodeLastRune and DecodeLastRuneInString +func TestDecodeLastRune(t *testing.T) { + tests := []struct { + s string + wantRune rune + wantSize int + }{ + {"a", 'a', 1}, + {"abc", 'c', 1}, + {"中文", '文', 3}, + {"Hello世界", '界', 3}, + {"", utf8.RuneError, 0}, + {"\x80", utf8.RuneError, 1}, // Invalid UTF-8 + } + + for _, tt := range tests { + // Test DecodeLastRune + r, size := utf8.DecodeLastRune([]byte(tt.s)) + if r != tt.wantRune || size != tt.wantSize { + t.Errorf("DecodeLastRune(%q) = (%U, %d), want (%U, %d)", + tt.s, r, size, tt.wantRune, tt.wantSize) + } + + // Test DecodeLastRuneInString + r, size = utf8.DecodeLastRuneInString(tt.s) + if r != tt.wantRune || size != tt.wantSize { + t.Errorf("DecodeLastRuneInString(%q) = (%U, %d), want (%U, %d)", + tt.s, r, size, tt.wantRune, tt.wantSize) + } + } +} + +// Test Valid and ValidString +func TestValid(t *testing.T) { + tests := []struct { + s string + want bool + }{ + {"", true}, + {"a", true}, + {"abc", true}, + {"中文", true}, + {"Hello, 世界", true}, + {"🌟", true}, + {"\x80", false}, // Invalid UTF-8 + {"\xC0\x80", false}, // Invalid UTF-8 + {"\xE4\xB8", false}, // Incomplete UTF-8 + {"\xF0\x9F\x8C", false}, // Incomplete UTF-8 + {"valid\x80invalid", false}, // Mixed valid/invalid + } + + for _, tt := range tests { + // Test Valid + got := utf8.Valid([]byte(tt.s)) + if got != tt.want { + t.Errorf("Valid(%q) = %v, want %v", tt.s, got, tt.want) + } + + // Test ValidString + got = utf8.ValidString(tt.s) + if got != tt.want { + t.Errorf("ValidString(%q) = %v, want %v", tt.s, got, tt.want) + } + } +} + +// Test ValidRune +func TestValidRune(t *testing.T) { + tests := []struct { + r rune + want bool + }{ + {'a', true}, + {'中', true}, + {'🌟', true}, + {utf8.MaxRune, true}, + {utf8.MaxRune + 1, false}, + {-1, false}, + {0xD800, false}, // Surrogate pair (invalid in UTF-8) + {0xDFFF, false}, // Surrogate pair (invalid in UTF-8) + } + + for _, tt := range tests { + got := utf8.ValidRune(tt.r) + if got != tt.want { + t.Errorf("ValidRune(%U) = %v, want %v", tt.r, got, tt.want) + } + } +} + +// Test RuneStart +func TestRuneStart(t *testing.T) { + tests := []struct { + b byte + want bool + }{ + {0x00, true}, // ASCII + {0x7F, true}, // ASCII + {0x80, false}, // Continuation byte + {0xBF, false}, // Continuation byte + {0xC0, true}, // Start of 2-byte sequence + {0xE0, true}, // Start of 3-byte sequence + {0xF0, true}, // Start of 4-byte sequence + {0xF8, true}, // Invalid but still a start byte + } + + for _, tt := range tests { + got := utf8.RuneStart(tt.b) + if got != tt.want { + t.Errorf("RuneStart(%#x) = %v, want %v", tt.b, got, tt.want) + } + } +} diff --git a/test/std/unique/unique_test.go b/test/std/unique/unique_test.go new file mode 100644 index 0000000000..27e0c5dc5e --- /dev/null +++ b/test/std/unique/unique_test.go @@ -0,0 +1,534 @@ +package unique_test + +import ( + "testing" + "unique" +) + +func TestMakeBasicTypes(t *testing.T) { + // Test with int + h1 := unique.Make(42) + h2 := unique.Make(42) + h3 := unique.Make(43) + + if h1 != h2 { + t.Error("Make(42) handles should be equal") + } + if h1 == h3 { + t.Error("Make(42) and Make(43) handles should not be equal") + } + + // Test with string + s1 := unique.Make("hello") + s2 := unique.Make("hello") + s3 := unique.Make("world") + + if s1 != s2 { + t.Error("Make(\"hello\") handles should be equal") + } + if s1 == s3 { + t.Error("Make(\"hello\") and Make(\"world\") handles should not be equal") + } + + // Test with bool + b1 := unique.Make(true) + b2 := unique.Make(true) + b3 := unique.Make(false) + + if b1 != b2 { + t.Error("Make(true) handles should be equal") + } + if b1 == b3 { + t.Error("Make(true) and Make(false) handles should not be equal") + } + + // Test with float64 + f1 := unique.Make(3.14) + f2 := unique.Make(3.14) + f3 := unique.Make(2.71) + + if f1 != f2 { + t.Error("Make(3.14) handles should be equal") + } + if f1 == f3 { + t.Error("Make(3.14) and Make(2.71) handles should not be equal") + } +} + +func TestMakeStruct(t *testing.T) { + type Point struct { + X, Y int + } + + p1 := unique.Make(Point{1, 2}) + p2 := unique.Make(Point{1, 2}) + p3 := unique.Make(Point{2, 3}) + + if p1 != p2 { + t.Error("Make(Point{1,2}) handles should be equal") + } + if p1 == p3 { + t.Error("Make(Point{1,2}) and Make(Point{2,3}) handles should not be equal") + } +} + +func TestMakeArray(t *testing.T) { + a1 := unique.Make([3]int{1, 2, 3}) + a2 := unique.Make([3]int{1, 2, 3}) + a3 := unique.Make([3]int{1, 2, 4}) + + if a1 != a2 { + t.Error("Make([3]int{1,2,3}) handles should be equal") + } + if a1 == a3 { + t.Error("Make([3]int{1,2,3}) and Make([3]int{1,2,4}) handles should not be equal") + } +} + +func TestValue(t *testing.T) { + // Test with int + h := unique.Make(42) + v := h.Value() + if v != 42 { + t.Errorf("Handle.Value() = %d, want 42", v) + } + + // Test with string + s := unique.Make("test") + sv := s.Value() + if sv != "test" { + t.Errorf("Handle.Value() = %q, want %q", sv, "test") + } + + // Test with struct + type Person struct { + Name string + Age int + } + p := unique.Make(Person{"Alice", 30}) + pv := p.Value() + if pv.Name != "Alice" || pv.Age != 30 { + t.Errorf("Handle.Value() = %+v, want {Alice 30}", pv) + } +} + +func TestMultipleMakes(t *testing.T) { + // Create multiple handles for the same value + handles := make([]unique.Handle[int], 10) + for i := range handles { + handles[i] = unique.Make(100) + } + + // All handles should be equal + for i := 1; i < len(handles); i++ { + if handles[0] != handles[i] { + t.Errorf("Handle %d not equal to handle 0", i) + } + } +} + +func TestDifferentTypes(t *testing.T) { + // Test that handles of different types are independent + intHandle := unique.Make(42) + strHandle := unique.Make("42") + + // Get values back + if intHandle.Value() != 42 { + t.Error("int handle value incorrect") + } + if strHandle.Value() != "42" { + t.Error("string handle value incorrect") + } +} + +func TestEmptyString(t *testing.T) { + h1 := unique.Make("") + h2 := unique.Make("") + h3 := unique.Make("a") + + if h1 != h2 { + t.Error("Make(\"\") handles should be equal") + } + if h1 == h3 { + t.Error("Make(\"\") and Make(\"a\") handles should not be equal") + } +} + +func TestZeroValues(t *testing.T) { + // Test with zero value of int + h1 := unique.Make(0) + h2 := unique.Make(0) + h3 := unique.Make(1) + + if h1 != h2 { + t.Error("Make(0) handles should be equal") + } + if h1 == h3 { + t.Error("Make(0) and Make(1) handles should not be equal") + } + + // Test with zero value of struct + type Empty struct{} + e1 := unique.Make(Empty{}) + e2 := unique.Make(Empty{}) + + if e1 != e2 { + t.Error("Make(Empty{}) handles should be equal") + } +} + +func TestComplexStruct(t *testing.T) { + type Address struct { + Street string + City string + Zip int + } + + type Person struct { + Name string + Age int + Address Address + } + + p1 := Person{ + Name: "Bob", + Age: 25, + Address: Address{ + Street: "Main St", + City: "NYC", + Zip: 10001, + }, + } + + p2 := Person{ + Name: "Bob", + Age: 25, + Address: Address{ + Street: "Main St", + City: "NYC", + Zip: 10001, + }, + } + + p3 := Person{ + Name: "Bob", + Age: 25, + Address: Address{ + Street: "Main St", + City: "NYC", + Zip: 10002, + }, + } + + h1 := unique.Make(p1) + h2 := unique.Make(p2) + h3 := unique.Make(p3) + + if h1 != h2 { + t.Error("Handles for identical complex structs should be equal") + } + if h1 == h3 { + t.Error("Handles for different complex structs should not be equal") + } +} + +func TestHandleInMap(t *testing.T) { + m := make(map[unique.Handle[string]]int) + + h1 := unique.Make("key1") + h2 := unique.Make("key2") + h3 := unique.Make("key1") // Same as h1 + + m[h1] = 100 + m[h2] = 200 + + if m[h3] != 100 { + t.Errorf("m[h3] = %d, want 100 (h3 should be same as h1)", m[h3]) + } + + if len(m) != 2 { + t.Errorf("map length = %d, want 2", len(m)) + } +} + +func TestHandleInSlice(t *testing.T) { + handles := []unique.Handle[int]{ + unique.Make(1), + unique.Make(2), + unique.Make(3), + unique.Make(1), // Duplicate + } + + if handles[0] != handles[3] { + t.Error("Handles at index 0 and 3 should be equal") + } + if handles[0] == handles[1] { + t.Error("Handles at index 0 and 1 should not be equal") + } +} + +func TestLargeString(t *testing.T) { + large := "" + for i := 0; i < 1000; i++ { + large += "a" + } + + h1 := unique.Make(large) + h2 := unique.Make(large) + + if h1 != h2 { + t.Error("Handles for large identical strings should be equal") + } + + if h1.Value() != large { + t.Error("Value() should return the original large string") + } +} + +func TestPointerInStruct(t *testing.T) { + type Node struct { + Value int + // Note: We can't include pointer fields in comparable types + // This test uses only comparable fields + } + + n1 := Node{Value: 10} + n2 := Node{Value: 10} + n3 := Node{Value: 20} + + h1 := unique.Make(n1) + h2 := unique.Make(n2) + h3 := unique.Make(n3) + + if h1 != h2 { + t.Error("Handles for equal nodes should be equal") + } + if h1 == h3 { + t.Error("Handles for different nodes should not be equal") + } +} + +func TestByteArray(t *testing.T) { + a1 := unique.Make([4]byte{1, 2, 3, 4}) + a2 := unique.Make([4]byte{1, 2, 3, 4}) + a3 := unique.Make([4]byte{1, 2, 3, 5}) + + if a1 != a2 { + t.Error("Handles for equal byte arrays should be equal") + } + if a1 == a3 { + t.Error("Handles for different byte arrays should not be equal") + } +} + +func TestRune(t *testing.T) { + h1 := unique.Make('a') + h2 := unique.Make('a') + h3 := unique.Make('b') + + if h1 != h2 { + t.Error("Handles for equal runes should be equal") + } + if h1 == h3 { + t.Error("Handles for different runes should not be equal") + } + + if h1.Value() != 'a' { + t.Errorf("Value() = %c, want 'a'", h1.Value()) + } +} + +func TestUnicode(t *testing.T) { + h1 := unique.Make("Hello, 世界") + h2 := unique.Make("Hello, 世界") + h3 := unique.Make("Hello, World") + + if h1 != h2 { + t.Error("Handles for equal unicode strings should be equal") + } + if h1 == h3 { + t.Error("Handles for different strings should not be equal") + } + + if h1.Value() != "Hello, 世界" { + t.Errorf("Value() = %q, want %q", h1.Value(), "Hello, 世界") + } +} + +func TestComplex64(t *testing.T) { + c1 := unique.Make(complex(1.0, 2.0)) + c2 := unique.Make(complex(1.0, 2.0)) + c3 := unique.Make(complex(1.0, 3.0)) + + if c1 != c2 { + t.Error("Handles for equal complex numbers should be equal") + } + if c1 == c3 { + t.Error("Handles for different complex numbers should not be equal") + } +} + +func TestComplex128(t *testing.T) { + c1 := unique.Make(complex128(complex(1.0, 2.0))) + c2 := unique.Make(complex128(complex(1.0, 2.0))) + c3 := unique.Make(complex128(complex(1.0, 3.0))) + + if c1 != c2 { + t.Error("Handles for equal complex128 numbers should be equal") + } + if c1 == c3 { + t.Error("Handles for different complex128 numbers should not be equal") + } +} + +func TestUintTypes(t *testing.T) { + // uint + u1 := unique.Make(uint(42)) + u2 := unique.Make(uint(42)) + if u1 != u2 { + t.Error("Handles for equal uint should be equal") + } + + // uint8 + u8_1 := unique.Make(uint8(255)) + u8_2 := unique.Make(uint8(255)) + if u8_1 != u8_2 { + t.Error("Handles for equal uint8 should be equal") + } + + // uint16 + u16_1 := unique.Make(uint16(65535)) + u16_2 := unique.Make(uint16(65535)) + if u16_1 != u16_2 { + t.Error("Handles for equal uint16 should be equal") + } + + // uint32 + u32_1 := unique.Make(uint32(4294967295)) + u32_2 := unique.Make(uint32(4294967295)) + if u32_1 != u32_2 { + t.Error("Handles for equal uint32 should be equal") + } + + // uint64 + u64_1 := unique.Make(uint64(18446744073709551615)) + u64_2 := unique.Make(uint64(18446744073709551615)) + if u64_1 != u64_2 { + t.Error("Handles for equal uint64 should be equal") + } + + // uintptr + up1 := unique.Make(uintptr(0x1234)) + up2 := unique.Make(uintptr(0x1234)) + if up1 != up2 { + t.Error("Handles for equal uintptr should be equal") + } +} + +func TestIntTypes(t *testing.T) { + // int8 + i8_1 := unique.Make(int8(-128)) + i8_2 := unique.Make(int8(-128)) + if i8_1 != i8_2 { + t.Error("Handles for equal int8 should be equal") + } + + // int16 + i16_1 := unique.Make(int16(-32768)) + i16_2 := unique.Make(int16(-32768)) + if i16_1 != i16_2 { + t.Error("Handles for equal int16 should be equal") + } + + // int32 + i32_1 := unique.Make(int32(-2147483648)) + i32_2 := unique.Make(int32(-2147483648)) + if i32_1 != i32_2 { + t.Error("Handles for equal int32 should be equal") + } + + // int64 + i64_1 := unique.Make(int64(-9223372036854775808)) + i64_2 := unique.Make(int64(-9223372036854775808)) + if i64_1 != i64_2 { + t.Error("Handles for equal int64 should be equal") + } +} + +func TestFloat32(t *testing.T) { + f1 := unique.Make(float32(3.14)) + f2 := unique.Make(float32(3.14)) + f3 := unique.Make(float32(2.71)) + + if f1 != f2 { + t.Error("Handles for equal float32 should be equal") + } + if f1 == f3 { + t.Error("Handles for different float32 should not be equal") + } +} + +func TestNestedStructs(t *testing.T) { + type Inner struct { + A int + B string + } + type Outer struct { + X Inner + Y int + Z [2]int + Empty struct{} + } + + o1 := Outer{ + X: Inner{A: 1, B: "test"}, + Y: 2, + Z: [2]int{3, 4}, + Empty: struct{}{}, + } + + o2 := Outer{ + X: Inner{A: 1, B: "test"}, + Y: 2, + Z: [2]int{3, 4}, + Empty: struct{}{}, + } + + o3 := Outer{ + X: Inner{A: 1, B: "test"}, + Y: 99, // Different + Z: [2]int{3, 4}, + Empty: struct{}{}, + } + + h1 := unique.Make(o1) + h2 := unique.Make(o2) + h3 := unique.Make(o3) + + if h1 != h2 { + t.Error("Handles for equal nested structs should be equal") + } + if h1 == h3 { + t.Error("Handles for different nested structs should not be equal") + } +} + +func TestConsistentHashing(t *testing.T) { + // Create many handles and verify consistency + const iterations = 100 + values := []int{1, 2, 3, 4, 5} + + for _, v := range values { + var handles []unique.Handle[int] + for i := 0; i < iterations; i++ { + handles = append(handles, unique.Make(v)) + } + + // All handles for the same value should be equal + for i := 1; i < len(handles); i++ { + if handles[0] != handles[i] { + t.Errorf("Inconsistent handles for value %d at iteration %d", v, i) + } + } + } +} diff --git a/test/std/unsafe/unsafe_test.go b/test/std/unsafe/unsafe_test.go new file mode 100644 index 0000000000..a9e066cb0a --- /dev/null +++ b/test/std/unsafe/unsafe_test.go @@ -0,0 +1,222 @@ +package unsafe_test + +import ( + "testing" + "unsafe" +) + +func TestSizeof(t *testing.T) { + var i int + var b byte + var s string + var arr [10]int + + sizeInt := unsafe.Sizeof(i) + if sizeInt != 8 && sizeInt != 4 { + t.Errorf("Sizeof(int) = %d, want 4 or 8", sizeInt) + } + + sizeByte := unsafe.Sizeof(b) + if sizeByte != 1 { + t.Errorf("Sizeof(byte) = %d, want 1", sizeByte) + } + + sizeString := unsafe.Sizeof(s) + if sizeString != 16 && sizeString != 8 { + t.Errorf("Sizeof(string) = %d, want 8 or 16", sizeString) + } + + sizeArr := unsafe.Sizeof(arr) + expectedSize := sizeInt * 10 + if sizeArr != expectedSize { + t.Errorf("Sizeof([10]int) = %d, want %d", sizeArr, expectedSize) + } +} + +func TestAlignof(t *testing.T) { + var i int + var b byte + var s string + + alignInt := unsafe.Alignof(i) + if alignInt == 0 || alignInt > 16 { + t.Errorf("Alignof(int) = %d, should be between 1 and 16", alignInt) + } + + alignByte := unsafe.Alignof(b) + if alignByte != 1 { + t.Errorf("Alignof(byte) = %d, want 1", alignByte) + } + + alignString := unsafe.Alignof(s) + if alignString == 0 || alignString > 16 { + t.Errorf("Alignof(string) = %d, should be between 1 and 16", alignString) + } +} + +func TestOffsetof(t *testing.T) { + type TestStruct struct { + a byte + b int32 + c int64 + } + + var ts TestStruct + + offsetA := unsafe.Offsetof(ts.a) + if offsetA != 0 { + t.Errorf("Offsetof(ts.a) = %d, want 0", offsetA) + } + + offsetB := unsafe.Offsetof(ts.b) + if offsetB < 1 { + t.Errorf("Offsetof(ts.b) = %d, should be >= 1", offsetB) + } + + offsetC := unsafe.Offsetof(ts.c) + if offsetC <= offsetB { + t.Errorf("Offsetof(ts.c) = %d, should be > Offsetof(ts.b) = %d", offsetC, offsetB) + } +} + +func TestPointer(t *testing.T) { + var i int = 42 + ptr := unsafe.Pointer(&i) + if ptr == nil { + t.Error("Pointer(&i) returned nil") + } + + iPtr := (*int)(ptr) + if *iPtr != 42 { + t.Errorf("*iPtr = %d, want 42", *iPtr) + } +} + +func TestAdd(t *testing.T) { + arr := [5]int{1, 2, 3, 4, 5} + ptr := unsafe.Pointer(&arr[0]) + + ptr2 := unsafe.Add(ptr, unsafe.Sizeof(arr[0])) + val := *(*int)(ptr2) + if val != 2 { + t.Errorf("After Add, got %d, want 2", val) + } + + ptr3 := unsafe.Add(ptr, unsafe.Sizeof(arr[0])*2) + val3 := *(*int)(ptr3) + if val3 != 3 { + t.Errorf("After Add(2), got %d, want 3", val3) + } +} + +func TestSlice(t *testing.T) { + arr := [5]int{10, 20, 30, 40, 50} + ptr := &arr[0] + + slice := unsafe.Slice(ptr, 3) + if len(slice) != 3 { + t.Errorf("len(slice) = %d, want 3", len(slice)) + } + if cap(slice) != 3 { + t.Errorf("cap(slice) = %d, want 3", cap(slice)) + } + if slice[0] != 10 || slice[1] != 20 || slice[2] != 30 { + t.Errorf("slice = %v, want [10 20 30]", slice) + } +} + +func TestSliceData(t *testing.T) { + slice := []int{100, 200, 300} + ptr := unsafe.SliceData(slice) + if ptr == nil { + t.Fatal("SliceData returned nil") + } + + val := *ptr + if val != 100 { + t.Errorf("*SliceData = %d, want 100", val) + } + + emptySlice := []int{} + emptyPtr := unsafe.SliceData(emptySlice) + _ = emptyPtr +} + +func TestString(t *testing.T) { + bytes := []byte{'h', 'e', 'l', 'l', 'o'} + ptr := &bytes[0] + + str := unsafe.String(ptr, len(bytes)) + if str != "hello" { + t.Errorf("String = %q, want hello", str) + } + + str2 := unsafe.String(ptr, 2) + if str2 != "he" { + t.Errorf("String(2) = %q, want he", str2) + } +} + +func TestStringData(t *testing.T) { + str := "world" + ptr := unsafe.StringData(str) + if ptr == nil { + t.Fatal("StringData returned nil") + } + + val := *ptr + if val != 'w' { + t.Errorf("*StringData = %c, want w", val) + } + + reconstructed := unsafe.String(ptr, len(str)) + if reconstructed != str { + t.Errorf("reconstructed = %q, want %q", reconstructed, str) + } + + emptyStr := "" + emptyPtr := unsafe.StringData(emptyStr) + _ = emptyPtr +} + +func TestArbitraryType(t *testing.T) { + // ArbitraryType is a type used in function signatures + // It's not directly instantiable but we can reference it through unsafe.Pointer + var x int = 42 + var p unsafe.Pointer = unsafe.Pointer(&x) + + // ArbitraryType is used in the signature of unsafe.Pointer + // We just need to reference it to ensure coverage + _ = p + + // Verify we can use it with type assertions and conversions + if p == nil { + t.Error("Expected non-nil pointer") + } +} + +func TestIntegerType(t *testing.T) { + // IntegerType is a type used in function signatures for Add + // It represents any integer type that can be used as an offset + arr := [3]int{1, 2, 3} + ptr := unsafe.Pointer(&arr[0]) + + // Test with different integer types (all valid for IntegerType) + var offset1 int = 1 + var offset2 uintptr = 1 + var offset3 int64 = 1 + + ptr1 := unsafe.Add(ptr, offset1*int(unsafe.Sizeof(arr[0]))) + ptr2 := unsafe.Add(ptr, offset2*unsafe.Sizeof(arr[0])) + ptr3 := unsafe.Add(ptr, offset3*int64(unsafe.Sizeof(arr[0]))) + + if *(*int)(ptr1) != 2 { + t.Errorf("Using int offset: got %d, want 2", *(*int)(ptr1)) + } + if *(*int)(ptr2) != 2 { + t.Errorf("Using uintptr offset: got %d, want 2", *(*int)(ptr2)) + } + if *(*int)(ptr3) != 2 { + t.Errorf("Using int64 offset: got %d, want 2", *(*int)(ptr3)) + } +} diff --git a/test/std/weak/weak_test.go b/test/std/weak/weak_test.go new file mode 100644 index 0000000000..0ef590752c --- /dev/null +++ b/test/std/weak/weak_test.go @@ -0,0 +1,98 @@ +package weak_test + +import ( + "runtime" + "testing" + "weak" +) + +func TestMake(t *testing.T) { + x := new(int) + *x = 42 + + wp := weak.Make(x) + if wp == (weak.Pointer[int]{}) { + t.Error("Make returned zero value") + } + + val := wp.Value() + if val == nil { + t.Error("Value() returned nil for live object") + } + if *val != 42 { + t.Errorf("*Value() = %d, want 42", *val) + } +} + +func TestPointerValue(t *testing.T) { + x := new(string) + *x = "hello" + + wp := weak.Make(x) + val := wp.Value() + if val == nil { + t.Fatal("Value() returned nil") + } + if *val != "hello" { + t.Errorf("*Value() = %q, want hello", *val) + } +} + +func TestPointerGC(t *testing.T) { + var wp weak.Pointer[int] + + func() { + x := new(int) + *x = 123 + wp = weak.Make(x) + + val := wp.Value() + if val == nil || *val != 123 { + t.Fatal("weak pointer should be valid before GC") + } + }() + + runtime.GC() + runtime.GC() + + val := wp.Value() + if val != nil { + t.Log("Note: weak pointer still valid after GC (may happen)") + } +} + +func TestPointerZeroValue(t *testing.T) { + var wp weak.Pointer[int] + + val := wp.Value() + if val != nil { + t.Errorf("zero Pointer.Value() = %v, want nil", val) + } +} + +func TestPointerMultipleTypes(t *testing.T) { + type MyStruct struct { + Field int + } + + s := &MyStruct{Field: 99} + wp := weak.Make(s) + + val := wp.Value() + if val == nil { + t.Fatal("Value() returned nil") + } + if val.Field != 99 { + t.Errorf("val.Field = %d, want 99", val.Field) + } +} + +func TestPointerNilInput(t *testing.T) { + var nilPtr *int + wp := weak.Make(nilPtr) + + val := wp.Value() + if val != nil { + t.Errorf("Make(nil).Value() = %v, want nil", val) + } +} diff --git a/test/syncpool/sync_pool_test.go b/test/syncpool/sync_pool_test.go new file mode 100644 index 0000000000..01915abda4 --- /dev/null +++ b/test/syncpool/sync_pool_test.go @@ -0,0 +1,41 @@ +package syncpool + +import ( + "sync" + "sync/atomic" + "testing" +) + +func TestPoolDoesNotReturnItemConcurrently(t *testing.T) { + type item struct { + inUse int32 + } + + var p sync.Pool + p.New = func() any { + return new(item) + } + + var failed int32 + var wg sync.WaitGroup + for g := 0; g < 32; g++ { + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 1000; i++ { + x := p.Get().(*item) + if !atomic.CompareAndSwapInt32(&x.inUse, 0, 1) { + atomic.StoreInt32(&failed, 1) + return + } + atomic.StoreInt32(&x.inUse, 0) + p.Put(x) + } + }() + } + wg.Wait() + + if atomic.LoadInt32(&failed) != 0 { + t.Fatal("sync.Pool returned an item while it was still in use") + } +} diff --git a/test/timer_test.go b/test/timer_test.go new file mode 100644 index 0000000000..454785a782 --- /dev/null +++ b/test/timer_test.go @@ -0,0 +1,131 @@ +package test + +import ( + "sync/atomic" + "testing" + "time" +) + +// Ensure AfterFunc runs within a reasonable time. +func TestAfterFuncFires(t *testing.T) { + done := make(chan struct{}) + time.AfterFunc(30*time.Millisecond, func() { close(done) }) + + select { + case <-done: + case <-time.After(500 * time.Millisecond): + t.Fatalf("AfterFunc timeout") + } +} + +// Verify Stop/Reset on Timer still fire after reset. +func TestTimerResetFires(t *testing.T) { + timer := time.NewTimer(100 * time.Millisecond) + active := timer.Stop() + if !active { + // Drain if it already fired + select { + case <-timer.C: + default: + } + } + // Reset returns whether timer was active before reset; both true/false are acceptable. + timer.Reset(40 * time.Millisecond) + + select { + case <-timer.C: + case <-time.After(400 * time.Millisecond): + t.Fatalf("timer did not fire after Reset") + } +} + +// Stop should prevent a timer from firing. +func TestTimerStopPreventsFire(t *testing.T) { + timer := time.NewTimer(50 * time.Millisecond) + if !timer.Stop() { + // It already fired; drain to avoid leakage. + select { + case <-timer.C: + default: + } + t.Skip("timer fired earlier than expected") + } + + select { + case <-timer.C: + t.Fatalf("timer fired after Stop") + case <-time.After(120 * time.Millisecond): + } +} + +// After delivers exactly one event and channel does not block forever. +func TestAfterSingleFire(t *testing.T) { + ch := time.After(20 * time.Millisecond) + select { + case <-ch: + case <-time.After(200 * time.Millisecond): + t.Fatalf("after timeout") + } + // Ensure channel doesn't deliver twice. + select { + case <-ch: + t.Fatalf("after channel delivered more than once") + default: + } +} + +// AfterFunc Stop returns correct boolean and prevents callback. +func TestAfterFuncStop(t *testing.T) { + triggered := make(chan struct{}, 1) + tmr := time.AfterFunc(50*time.Millisecond, func() { triggered <- struct{}{} }) + if !tmr.Stop() { + // It might already be running; drain best-effort. + select { + case <-triggered: + default: + } + } + // Wait longer than the timer to check it doesn't fire. + select { + case <-triggered: + t.Fatalf("AfterFunc fired after Stop") + case <-time.After(150 * time.Millisecond): + } +} + +// Reset on an active AfterFunc should reschedule the callback. +func TestAfterFuncReset(t *testing.T) { + var count atomic.Int32 + tmr := time.AfterFunc(100*time.Millisecond, func() { + count.Add(1) + }) + + time.Sleep(20 * time.Millisecond) + if !tmr.Reset(30 * time.Millisecond) { + // Even if Reset returns false, Go's semantics allow the callback to run twice. + } + + // Wait long enough to observe executions triggered before/after Reset. + time.Sleep(150 * time.Millisecond) + if got := count.Load(); got < 1 || got > 2 { + t.Fatalf("expected callback 1 or 2 times after reset, got %d", got) + } +} + +// Concurrent stops should be safe. +func TestTimerConcurrentStop(t *testing.T) { + tmr := time.NewTimer(40 * time.Millisecond) + done := make(chan struct{}) + + go func() { + tmr.Stop() + close(done) + }() + + select { + case <-tmr.C: + case <-done: + case <-time.After(300 * time.Millisecond): + t.Fatalf("timer or stop did not complete") + } +} diff --git a/test/typeunion_test.go b/test/typeunion_test.go new file mode 100644 index 0000000000..a7a4fa4f71 --- /dev/null +++ b/test/typeunion_test.go @@ -0,0 +1,54 @@ +package test + +import "testing" + +type PublicKey any + +type VerificationKey interface { + PublicKey | []uint8 +} + +type VerificationKeySet struct { + Keys []VerificationKey +} + +func checkVerificationKey(got any) int { + switch have := got.(type) { + case VerificationKeySet: + return len(have.Keys) + + case VerificationKey: + + _ = have + return 100 + + default: + return -1 + } +} + +func TestVerificationKeyUnionDegenerate(t *testing.T) { + set := VerificationKeySet{ + Keys: []VerificationKey{ + []uint8{1, 2, 3}, + 123, + "abc", + }, + } + + if got := checkVerificationKey(set); got != 3 { + t.Fatalf("checkVerificationKey(VerificationKeySet) = %d, want 3", got) + } + + if got := checkVerificationKey([]uint8{1, 2}); got != 100 { + t.Fatalf("checkVerificationKey([]uint8) = %d, want 100", got) + } + + if got := checkVerificationKey(123); got != 100 { + t.Fatalf("checkVerificationKey(int) = %d, want 100", got) + } + + if got := checkVerificationKey("hello"); got != 100 { + t.Fatalf("checkVerificationKey(string) = %d, want 100", got) + } +} diff --git a/xtool/ar/common.go b/xtool/ar/common.go index 54db223762..5365dc1490 100644 --- a/xtool/ar/common.go +++ b/xtool/ar/common.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +// Portions of this file are derived from github.com/blakesmith/ar. +// Copyright (c) 2013 Blake Smith . +// See ../../LICENSES/BlakeSmith-AR-MIT.txt for license terms. + package ar import ( diff --git a/xtool/ar/reader.go b/xtool/ar/reader.go index b551ac1785..0e4c466391 100644 --- a/xtool/ar/reader.go +++ b/xtool/ar/reader.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +// Portions of this file are derived from github.com/blakesmith/ar. +// Copyright (c) 2013 Blake Smith . +// See ../../LICENSES/BlakeSmith-AR-MIT.txt for license terms. + package ar import ( diff --git a/xtool/ar/writer.go b/xtool/ar/writer.go index e30853346e..d297773876 100644 --- a/xtool/ar/writer.go +++ b/xtool/ar/writer.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +// Portions of this file are derived from github.com/blakesmith/ar. +// Copyright (c) 2013 Blake Smith . +// See ../../LICENSES/BlakeSmith-AR-MIT.txt for license terms. + package ar import ( diff --git a/xtool/clang/_types/parser/_parser_test.go b/xtool/clang/_types/parser/_parser_test.go index be1a722e43..bd96c547be 100644 --- a/xtool/clang/_types/parser/_parser_test.go +++ b/xtool/clang/_types/parser/_parser_test.go @@ -2,7 +2,7 @@ // +build !llgo /* - * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import ( "go/types" "testing" - ctypes "github.com/goplus/llgo/x/clang/types" + ctypes "github.com/xgo-dev/llgo/x/clang/types" ) // ----------------------------------------------------------------------------- diff --git a/xtool/clang/_types/parser/parser.go b/xtool/clang/_types/parser/parser.go index 3826ccda43..004dc36cb5 100644 --- a/xtool/clang/_types/parser/parser.go +++ b/xtool/clang/_types/parser/parser.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,9 +26,9 @@ import ( "strconv" "github.com/goplus/gogen" - "github.com/goplus/llgo/xtool/clang/types/scanner" + "github.com/xgo-dev/llgo/xtool/clang/types/scanner" - ctypes "github.com/goplus/llgo/xtool/clang/types" + ctypes "github.com/xgo-dev/llgo/xtool/clang/types" ) const ( diff --git a/xtool/clang/_types/scanner/scanner.go b/xtool/clang/_types/scanner/scanner.go index 842372f15e..988b4b621f 100644 --- a/xtool/clang/_types/scanner/scanner.go +++ b/xtool/clang/_types/scanner/scanner.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/clang/_types/types.go b/xtool/clang/_types/types.go index 3c6171595a..376dd2bba1 100644 --- a/xtool/clang/_types/types.go +++ b/xtool/clang/_types/types.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/clang/ast/ast.go b/xtool/clang/ast/ast.go index dfef4cc71a..493552ec86 100644 --- a/xtool/clang/ast/ast.go +++ b/xtool/clang/ast/ast.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/clang/clang.go b/xtool/clang/clang.go index 9ac3974da8..c6e21999ee 100644 --- a/xtool/clang/clang.go +++ b/xtool/clang/clang.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/clang/pathutil/pathutil.go b/xtool/clang/pathutil/pathutil.go index 6b83df07de..62939b0699 100644 --- a/xtool/clang/pathutil/pathutil.go +++ b/xtool/clang/pathutil/pathutil.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/clang/preprocessor/preprocessor.go b/xtool/clang/preprocessor/preprocessor.go index 6bf72a425c..b2150948ec 100644 --- a/xtool/clang/preprocessor/preprocessor.go +++ b/xtool/clang/preprocessor/preprocessor.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import ( "os/exec" "path/filepath" - "github.com/goplus/llgo/xtool/clang/pathutil" + "github.com/xgo-dev/llgo/xtool/clang/pathutil" ) const ( diff --git a/xtool/env/env.go b/xtool/env/env.go index 0f3eb8cc2b..cef7858a3a 100644 --- a/xtool/env/env.go +++ b/xtool/env/env.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,10 +20,11 @@ import ( "fmt" "os" "os/exec" + "path/filepath" "regexp" "strings" - "github.com/goplus/llgo/xtool/safesplit" + "github.com/xgo-dev/llgo/xtool/safesplit" ) var ( @@ -32,7 +33,19 @@ var ( ) func ExpandEnvToArgs(s string) []string { - r, config := expandEnvWithCmd(s) + r, config := expandEnvWithCmd(s, "", nil) + return expandedArgs(r, config) +} + +// ExpandEnvToArgsWith expands variables and supported helper commands using +// the supplied request directory and environment. A non-nil environ prevents +// subprocesses and variable expansion from consulting process-global state. +func ExpandEnvToArgsWith(s, dir string, environ []string) []string { + r, config := expandEnvWithCmd(s, dir, environ) + return expandedArgs(r, config) +} + +func expandedArgs(r string, config bool) []string { if r == "" { return nil } @@ -43,11 +56,11 @@ func ExpandEnvToArgs(s string) []string { } func ExpandEnv(s string) string { - r, _ := expandEnvWithCmd(s) + r, _ := expandEnvWithCmd(s, "", nil) return r } -func expandEnvWithCmd(s string) (string, bool) { +func expandEnvWithCmd(s, dir string, environ []string) (string, bool) { var config bool expanded := reSubcmd.ReplaceAllStringFunc(s, func(m string) string { subcmd := strings.TrimSpace(m[2 : len(m)-1]) @@ -61,7 +74,16 @@ func expandEnvWithCmd(s string) (string, bool) { var out []byte var err error - out, err = exec.Command(cmd, args[1:]...).Output() + executable := cmd + if environ != nil { + executable = lookPathInEnvironment(cmd, dir, environ) + } + command := exec.Command(executable, args[1:]...) + command.Dir = dir + if environ != nil { + command.Env = append([]string(nil), environ...) + } + out, err = command.Output() if err != nil { // TODO(kindy): log in verbose mode @@ -70,7 +92,46 @@ func expandEnvWithCmd(s string) (string, bool) { return strings.Replace(strings.TrimSpace(string(out)), "\n", " ", -1) }) - return strings.TrimSpace(os.Expand(expanded, os.Getenv)), config + lookup := os.Getenv + if environ != nil { + lookup = func(key string) string { + prefix := key + "=" + for i := len(environ) - 1; i >= 0; i-- { + if strings.HasPrefix(environ[i], prefix) { + return strings.TrimPrefix(environ[i], prefix) + } + } + return "" + } + } + return strings.TrimSpace(os.Expand(expanded, lookup)), config +} + +func lookPathInEnvironment(name, dir string, environ []string) string { + if strings.ContainsRune(name, filepath.Separator) { + return name + } + path := "" + prefix := "PATH=" + for i := len(environ) - 1; i >= 0; i-- { + if strings.HasPrefix(environ[i], prefix) { + path = strings.TrimPrefix(environ[i], prefix) + break + } + } + for _, entry := range filepath.SplitList(path) { + if entry == "" { + entry = "." + } + if !filepath.IsAbs(entry) && dir != "" { + entry = filepath.Join(dir, entry) + } + candidate := filepath.Join(entry, name) + if info, err := os.Stat(candidate); err == nil && !info.IsDir() && info.Mode()&0o111 != 0 { + return candidate + } + } + return name } func parseSubcmd(s string) []string { diff --git a/xtool/env/env_test.go b/xtool/env/env_test.go new file mode 100644 index 0000000000..51c485733d --- /dev/null +++ b/xtool/env/env_test.go @@ -0,0 +1,75 @@ +package env + +import ( + "os" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +func TestExpandEnvToArgsWithUsesExplicitEnvironment(t *testing.T) { + t.Setenv("LLGO_ENV_TEST", "ambient") + got := ExpandEnvToArgsWith("$LLGO_ENV_TEST", "", []string{"LLGO_ENV_TEST=request"}) + if want := []string{"request"}; !reflect.DeepEqual(got, want) { + t.Fatalf("ExpandEnvToArgsWith = %q, want %q", got, want) + } +} + +func TestExpandEnvUsesProcessEnvironment(t *testing.T) { + t.Setenv("LLGO_ENV_TEST", "ambient") + if got := ExpandEnv("$LLGO_ENV_TEST"); got != "ambient" { + t.Fatalf("ExpandEnv = %q, want %q", got, "ambient") + } + if got := ExpandEnvToArgs("$LLGO_ENV_TEST"); !reflect.DeepEqual(got, []string{"ambient"}) { + t.Fatalf("ExpandEnvToArgs = %q, want %q", got, []string{"ambient"}) + } + if got := ExpandEnvToArgs(""); got != nil { + t.Fatalf("ExpandEnvToArgs(empty) = %q, want nil", got) + } +} + +func TestExpandEnvToArgsWithConfiguresSubprocess(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("shell fixture is Unix-only") + } + dir := t.TempDir() + tool := filepath.Join(dir, "pkg-config") + script := "#!/bin/sh\nprintf '%s' \"-L$LLGO_ENV_TEST -I$PWD\"\n" + if err := os.WriteFile(tool, []byte(script), 0o755); err != nil { + t.Fatal(err) + } + got := ExpandEnvToArgsWith( + "$(pkg-config --libs fixture)", + dir, + []string{"PATH=" + dir, "LLGO_ENV_TEST=request"}, + ) + resolvedDir, err := filepath.EvalSymlinks(dir) + if err != nil { + t.Fatal(err) + } + want := []string{"-Lrequest", "-I" + resolvedDir} + if !reflect.DeepEqual(got, want) { + t.Fatalf("ExpandEnvToArgsWith = %q, want %q", got, want) + } +} + +func TestLookPathInEnvironmentBoundaries(t *testing.T) { + dir := t.TempDir() + tool := filepath.Join(dir, "fixture-tool") + if err := os.WriteFile(tool, []byte("#!/bin/sh\n"), 0o755); err != nil { + t.Fatal(err) + } + if got := lookPathInEnvironment("fixture-tool", dir, []string{"PATH=" + string(os.PathListSeparator)}); got != tool { + t.Fatalf("lookPathInEnvironment with empty entry = %q, want %q", got, tool) + } + if got := lookPathInEnvironment(filepath.Join("bin", "tool"), dir, nil); got != filepath.Join("bin", "tool") { + t.Fatalf("lookPathInEnvironment with separator = %q", got) + } + if got := lookPathInEnvironment("missing-tool", dir, []string{"PATH=" + t.TempDir()}); got != "missing-tool" { + t.Fatalf("lookPathInEnvironment missing tool = %q", got) + } + if got := ExpandEnvToArgsWith("$LLGO_ENV_MISSING", dir, []string{"PATH=" + dir}); got != nil { + t.Fatalf("missing explicit environment variable = %q, want nil", got) + } +} diff --git a/xtool/env/llvm/llvm.go b/xtool/env/llvm/llvm.go index a34757c94c..02777107d7 100644 --- a/xtool/env/llvm/llvm.go +++ b/xtool/env/llvm/llvm.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,15 +17,26 @@ package llvm import ( + "fmt" "os" "os/exec" "path/filepath" + "runtime" + "sort" "strings" - "github.com/goplus/llgo/xtool/clang" - "github.com/goplus/llgo/xtool/llvm/install_name_tool" - "github.com/goplus/llgo/xtool/llvm/llvmlink" - "github.com/goplus/llgo/xtool/nm" + "github.com/xgo-dev/llgo/internal/env" + "github.com/xgo-dev/llgo/xtool/clang" + "github.com/xgo-dev/llgo/xtool/llvm/install_name_tool" + "github.com/xgo-dev/llgo/xtool/llvm/llvmlink" + "github.com/xgo-dev/llgo/xtool/nm" +) + +// ----------------------------------------------------------------------------- + +const ( + // CrosscompileClangPath is the relative path from LLGO_ROOT to the clang installation + CrosscompileClangPath = "crosscompile/clang" ) // ----------------------------------------------------------------------------- @@ -42,6 +53,13 @@ func defaultLLVMConfigBin() string { if bin != "" { return bin } + + llgoRoot := env.LLGoROOT() + // Check LLGO_ROOT/crosscompile/clang for llvm-config + crossLLVMConfigBin := filepath.Join(llgoRoot, CrosscompileClangPath, "bin", "llvm-config") + if _, err := os.Stat(crossLLVMConfigBin); err == nil { + return crossLLVMConfigBin + } return ldLLVMConfigBin } @@ -70,6 +88,35 @@ func New(llvmConfigBin string) *Env { // means LLVM executables are assumed to be in PATH. func (e *Env) BinDir() string { return e.binDir } +// SetupPath makes the selected LLVM installation part of the process +// environment. Command entry points call it before starting builds; build +// requests and workers then inherit LLVM through the ordinary PATH snapshot. +func SetupPath() { + binDir := New("").BinDir() + if binDir == "" { + return + } + + path := os.Getenv("PATH") + for _, dir := range filepath.SplitList(path) { + if samePath(dir, binDir) { + return + } + } + if path != "" { + binDir += string(os.PathListSeparator) + path + } + _ = os.Setenv("PATH", binDir) +} + +func samePath(x, y string) bool { + x, y = filepath.Clean(x), filepath.Clean(y) + if runtime.GOOS == "windows" { + return strings.EqualFold(x, y) + } + return x == y +} + // Clang returns a new [clang.Cmd] instance. func (e *Env) Clang() *clang.Cmd { bin := filepath.Join(e.BinDir(), "clang++") @@ -93,4 +140,71 @@ func (e *Env) InstallNameTool() *install_name_tool.Cmd { return install_name_tool.New(bin) } +// FileCheck returns a command to execute LLVM FileCheck with given arguments. +func (e *Env) FileCheck(args ...string) (*exec.Cmd, error) { + path, err := e.toolPath("FileCheck") + if err != nil { + return nil, err + } + return exec.Command(path, args...), nil +} + +// Readelf returns a command to execute llvm-readelf with given arguments. +func (e *Env) Readelf(args ...string) (*exec.Cmd, error) { + path, err := e.toolPath("llvm-readelf") + if err != nil { + return nil, err + } + return exec.Command(path, args...), nil +} + +func (e *Env) toolPath(base string) (string, error) { + if tool := searchTool(e.binDir, base); tool != "" { + return tool, nil + } + if tool, err := exec.LookPath(base); err == nil { + return tool, nil + } + if tool := searchToolInPath(base); tool != "" { + return tool, nil + } + return "", fmt.Errorf("%s not found", base) +} + +func searchTool(dir, base string) string { + if dir == "" { + return "" + } + candidate := filepath.Join(dir, base) + if isExecutable(candidate) { + return candidate + } + pattern := filepath.Join(dir, base+"-*") + matches, _ := filepath.Glob(pattern) + sort.Sort(sort.Reverse(sort.StringSlice(matches))) + for _, match := range matches { + if isExecutable(match) { + return match + } + } + return "" +} + +func searchToolInPath(base string) string { + for _, dir := range filepath.SplitList(os.Getenv("PATH")) { + if tool := searchTool(dir, base); tool != "" { + return tool + } + } + return "" +} + +func isExecutable(path string) bool { + if path == "" { + return false + } + info, err := os.Stat(path) + return err == nil && !info.IsDir() +} + // ----------------------------------------------------------------------------- diff --git a/xtool/env/llvm/llvm_config_byollvm.go b/xtool/env/llvm/llvm_config_byollvm.go index 54aeedd3e4..a00a72e6e1 100644 --- a/xtool/env/llvm/llvm_config_byollvm.go +++ b/xtool/env/llvm/llvm_config_byollvm.go @@ -1,7 +1,7 @@ //go:build byollvm /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_darwin_amd64_llvm14.go b/xtool/env/llvm/llvm_config_darwin_amd64_llvm14.go index 0fd0369158..326a42b8ca 100644 --- a/xtool/env/llvm/llvm_config_darwin_amd64_llvm14.go +++ b/xtool/env/llvm/llvm_config_darwin_amd64_llvm14.go @@ -1,7 +1,7 @@ //go:build !byollvm && darwin && amd64 && llvm14 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_darwin_amd64_llvm15.go b/xtool/env/llvm/llvm_config_darwin_amd64_llvm15.go index 1d4d04a12e..256ac0078a 100644 --- a/xtool/env/llvm/llvm_config_darwin_amd64_llvm15.go +++ b/xtool/env/llvm/llvm_config_darwin_amd64_llvm15.go @@ -1,7 +1,7 @@ //go:build !byollvm && darwin && amd64 && llvm15 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_darwin_amd64_llvm16.go b/xtool/env/llvm/llvm_config_darwin_amd64_llvm16.go index 7dc84ec00f..b996ac05d8 100644 --- a/xtool/env/llvm/llvm_config_darwin_amd64_llvm16.go +++ b/xtool/env/llvm/llvm_config_darwin_amd64_llvm16.go @@ -1,7 +1,7 @@ //go:build !byollvm && darwin && amd64 && llvm16 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_darwin_amd64_llvm17.go b/xtool/env/llvm/llvm_config_darwin_amd64_llvm17.go index 003d46583e..b67ccbaa9d 100644 --- a/xtool/env/llvm/llvm_config_darwin_amd64_llvm17.go +++ b/xtool/env/llvm/llvm_config_darwin_amd64_llvm17.go @@ -1,7 +1,7 @@ //go:build !byollvm && darwin && amd64 && llvm17 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_darwin_amd64_llvm18.go b/xtool/env/llvm/llvm_config_darwin_amd64_llvm18.go index 07f56bc9e1..0e0668949d 100644 --- a/xtool/env/llvm/llvm_config_darwin_amd64_llvm18.go +++ b/xtool/env/llvm/llvm_config_darwin_amd64_llvm18.go @@ -1,7 +1,7 @@ //go:build !byollvm && darwin && amd64 && llvm18 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_darwin_amd64_llvm19.go b/xtool/env/llvm/llvm_config_darwin_amd64_llvm19.go index 3f1dd3333b..c1ef8cd433 100644 --- a/xtool/env/llvm/llvm_config_darwin_amd64_llvm19.go +++ b/xtool/env/llvm/llvm_config_darwin_amd64_llvm19.go @@ -1,7 +1,7 @@ //go:build !byollvm && darwin && amd64 && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_darwin_llvm14.go b/xtool/env/llvm/llvm_config_darwin_llvm14.go index ac271e53a4..20080b2555 100644 --- a/xtool/env/llvm/llvm_config_darwin_llvm14.go +++ b/xtool/env/llvm/llvm_config_darwin_llvm14.go @@ -1,7 +1,7 @@ //go:build !byollvm && darwin && !amd64 && llvm14 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_darwin_llvm15.go b/xtool/env/llvm/llvm_config_darwin_llvm15.go index a0df2cad3d..2164c1e9d0 100644 --- a/xtool/env/llvm/llvm_config_darwin_llvm15.go +++ b/xtool/env/llvm/llvm_config_darwin_llvm15.go @@ -1,7 +1,7 @@ //go:build !byollvm && darwin && !amd64 && llvm15 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_darwin_llvm16.go b/xtool/env/llvm/llvm_config_darwin_llvm16.go index bdb91b5ff6..9abbcd7ad2 100644 --- a/xtool/env/llvm/llvm_config_darwin_llvm16.go +++ b/xtool/env/llvm/llvm_config_darwin_llvm16.go @@ -1,7 +1,7 @@ //go:build !byollvm && darwin && !amd64 && llvm16 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_darwin_llvm17.go b/xtool/env/llvm/llvm_config_darwin_llvm17.go index 250ac22f28..83482032eb 100644 --- a/xtool/env/llvm/llvm_config_darwin_llvm17.go +++ b/xtool/env/llvm/llvm_config_darwin_llvm17.go @@ -1,7 +1,7 @@ //go:build !byollvm && darwin && !amd64 && llvm17 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_darwin_llvm18.go b/xtool/env/llvm/llvm_config_darwin_llvm18.go index 4cbc27e5dc..cd3e1ad38b 100644 --- a/xtool/env/llvm/llvm_config_darwin_llvm18.go +++ b/xtool/env/llvm/llvm_config_darwin_llvm18.go @@ -1,7 +1,7 @@ //go:build !byollvm && darwin && !amd64 && llvm18 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_darwin_llvm19.go b/xtool/env/llvm/llvm_config_darwin_llvm19.go index d63c924caa..08baa210c9 100644 --- a/xtool/env/llvm/llvm_config_darwin_llvm19.go +++ b/xtool/env/llvm/llvm_config_darwin_llvm19.go @@ -1,7 +1,7 @@ //go:build !byollvm && darwin && !amd64 && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_linux_llvm14.go b/xtool/env/llvm/llvm_config_linux_llvm14.go index 8c0330f2d7..688f4edc36 100644 --- a/xtool/env/llvm/llvm_config_linux_llvm14.go +++ b/xtool/env/llvm/llvm_config_linux_llvm14.go @@ -1,7 +1,7 @@ //go:build !byollvm && linux && llvm14 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_linux_llvm15.go b/xtool/env/llvm/llvm_config_linux_llvm15.go index 9e29d745c4..c09d0ae94f 100644 --- a/xtool/env/llvm/llvm_config_linux_llvm15.go +++ b/xtool/env/llvm/llvm_config_linux_llvm15.go @@ -1,7 +1,7 @@ //go:build !byollvm && linux && llvm15 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_linux_llvm16.go b/xtool/env/llvm/llvm_config_linux_llvm16.go index 71abb184c0..4f2c4484a7 100644 --- a/xtool/env/llvm/llvm_config_linux_llvm16.go +++ b/xtool/env/llvm/llvm_config_linux_llvm16.go @@ -1,7 +1,7 @@ //go:build !byollvm && linux && llvm16 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_linux_llvm17.go b/xtool/env/llvm/llvm_config_linux_llvm17.go index 49e82e4985..a285763d72 100644 --- a/xtool/env/llvm/llvm_config_linux_llvm17.go +++ b/xtool/env/llvm/llvm_config_linux_llvm17.go @@ -1,7 +1,7 @@ //go:build !byollvm && linux && llvm17 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_linux_llvm18.go b/xtool/env/llvm/llvm_config_linux_llvm18.go index 5d32249311..79cc83a74d 100644 --- a/xtool/env/llvm/llvm_config_linux_llvm18.go +++ b/xtool/env/llvm/llvm_config_linux_llvm18.go @@ -1,7 +1,7 @@ //go:build !byollvm && linux && llvm18 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_linux_llvm19.go b/xtool/env/llvm/llvm_config_linux_llvm19.go index 70e2e4d829..6fca306d4d 100644 --- a/xtool/env/llvm/llvm_config_linux_llvm19.go +++ b/xtool/env/llvm/llvm_config_linux_llvm19.go @@ -1,7 +1,7 @@ //go:build !byollvm && linux && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_config_windows_llvm20.go b/xtool/env/llvm/llvm_config_windows_llvm20.go index 6f5f77ea7a..36bac6ca39 100644 --- a/xtool/env/llvm/llvm_config_windows_llvm20.go +++ b/xtool/env/llvm/llvm_config_windows_llvm20.go @@ -1,7 +1,7 @@ //go:build !byollvm && windows && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19 /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/env/llvm/llvm_test.go b/xtool/env/llvm/llvm_test.go new file mode 100644 index 0000000000..16a45005fe --- /dev/null +++ b/xtool/env/llvm/llvm_test.go @@ -0,0 +1,56 @@ +//go:build !llgo + +package llvm + +import ( + "os" + "path/filepath" + "runtime" + "testing" +) + +func TestSetupPath(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("test helper uses a shell script") + } + + binDir := t.TempDir() + llvmConfig := filepath.Join(t.TempDir(), "llvm-config") + if err := os.WriteFile(llvmConfig, []byte("#!/bin/sh\nprintf '%s\\n' \"${LLGO_TEST_LLVM_BINDIR}\"\n"), 0o755); err != nil { + t.Fatal(err) + } + t.Setenv("LLVM_CONFIG", llvmConfig) + t.Setenv("LLGO_TEST_LLVM_BINDIR", binDir) + original := filepath.Join(t.TempDir(), "original") + t.Setenv("PATH", original) + + SetupPath() + want := binDir + string(os.PathListSeparator) + original + if got := os.Getenv("PATH"); got != want { + t.Fatalf("PATH = %q, want %q", got, want) + } + + SetupPath() + if got := os.Getenv("PATH"); got != want { + t.Fatalf("second setup changed PATH to %q, want %q", got, want) + } +} + +func TestSetupPathIgnoresMissingBinDir(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("test helper uses a shell script") + } + + llvmConfig := filepath.Join(t.TempDir(), "llvm-config") + if err := os.WriteFile(llvmConfig, []byte("#!/bin/sh\nexit 1\n"), 0o755); err != nil { + t.Fatal(err) + } + t.Setenv("LLVM_CONFIG", llvmConfig) + t.Setenv("PATH", filepath.Join(t.TempDir(), "original")) + before := os.Getenv("PATH") + + SetupPath() + if got := os.Getenv("PATH"); got != before { + t.Fatalf("PATH changed from %q to %q without an LLVM bin directory", before, got) + } +} diff --git a/xtool/llvm/install_name_tool/rpath.go b/xtool/llvm/install_name_tool/rpath.go index 01fe7b3497..dc397715fa 100644 --- a/xtool/llvm/install_name_tool/rpath.go +++ b/xtool/llvm/install_name_tool/rpath.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/llvm/llvmlink/link.go b/xtool/llvm/llvmlink/link.go index b52f3c4cd0..bba97e1db2 100644 --- a/xtool/llvm/llvmlink/link.go +++ b/xtool/llvm/llvmlink/link.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/nm/nm.go b/xtool/nm/nm.go index a1c44e0d1b..e849ac81d6 100644 --- a/xtool/nm/nm.go +++ b/xtool/nm/nm.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/nm/nmindex/index.go b/xtool/nm/nmindex/index.go index 5f3254447b..8a4f3e7713 100644 --- a/xtool/nm/nmindex/index.go +++ b/xtool/nm/nmindex/index.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ import ( "path/filepath" "strings" - "github.com/goplus/llgo/xtool/nm" + "github.com/xgo-dev/llgo/xtool/nm" ) type IndexBuilder struct { diff --git a/xtool/nm/nmindex/query.go b/xtool/nm/nmindex/query.go index d86c4d526c..afc29602f1 100644 --- a/xtool/nm/nmindex/query.go +++ b/xtool/nm/nmindex/query.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import ( "os" "strings" - "github.com/goplus/llgo/xtool/nm" + "github.com/xgo-dev/llgo/xtool/nm" ) // MatchedItem represents a matched item diff --git a/xtool/safesplit/safesplit.go b/xtool/safesplit/safesplit.go index 71783bcdb2..f705868dca 100644 --- a/xtool/safesplit/safesplit.go +++ b/xtool/safesplit/safesplit.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/xtool/safesplit/safesplit_test.go b/xtool/safesplit/safesplit_test.go index ccc603186d..30e605f08e 100644 --- a/xtool/safesplit/safesplit_test.go +++ b/xtool/safesplit/safesplit_test.go @@ -2,7 +2,7 @@ // +build !llgo /* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -78,7 +78,7 @@ func TestSplitPkgConfigFlags(t *testing.T) { ftest("-D VERSION=2.1 -D DEBUG=1", `["-DVERSION=2.1" "-DDEBUG=1"]`) }) - // case for https://github.com/goplus/llgo/issues/1244 + // case for https://github.com/xgo-dev/llgo/issues/1244 t.Run("w_pipe", func(t *testing.T) { ftest("-w -pipe", `["-w" "-pipe"]`) ftest("-Os -w -pipe", `["-Os" "-w" "-pipe"]`)