diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8a179006ce18..d49e81213f71 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,6 +38,12 @@ on: required: false manual_netlify_site_id: required: false + sentry_auth_token: + required: false + sentry_org: + required: false + sentry_project: + required: false jobs: build: @@ -52,6 +58,14 @@ jobs: - uses: DeterminateSystems/flakehub-cache-action@main - run: nix build .#packages.${{ inputs.system }}.default .#packages.${{ inputs.system }}.binaryTarball --no-link -L - run: nix build .#packages.${{ inputs.system }}.binaryTarball --out-link tarball + - run: nix build .#^debug,out + - name: Upload debug info to Sentry + run: ./maintainers/upload-debug-info-to-sentry.py --debug-dir ./result-debug ./result/bin/nix + if: env.SENTRY_AUTH_TOKEN != '' + env: + SENTRY_AUTH_TOKEN: ${{ secrets.sentry_auth_token }} + SENTRY_ORG: ${{ secrets.sentry_org }} + SENTRY_PROJECT: ${{ secrets.sentry_project }} - uses: actions/upload-artifact@v4 with: name: ${{ inputs.system }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08000ac4c871..37711caa66a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,6 +44,9 @@ jobs: secrets: manual_netlify_auth_token: ${{ secrets.NETLIFY_AUTH_TOKEN }} manual_netlify_site_id: ${{ secrets.NETLIFY_SITE_ID }} + sentry_auth_token: ${{ secrets.SENTRY_AUTH_TOKEN }} + sentry_org: ${{ secrets.SENTRY_ORG }} + sentry_project: ${{ secrets.SENTRY_PROJECT }} build_aarch64-linux: uses: ./.github/workflows/build.yml @@ -62,6 +65,10 @@ jobs: runner: UbuntuLatest32Cores128GArm runner_for_virt: UbuntuLatest32Cores128GArm runner_small: UbuntuLatest32Cores128GArm + secrets: + sentry_auth_token: ${{ secrets.SENTRY_AUTH_TOKEN }} + sentry_org: ${{ secrets.SENTRY_ORG }} + sentry_project: ${{ secrets.SENTRY_PROJECT }} build_aarch64-darwin: uses: ./.github/workflows/build.yml @@ -70,6 +77,10 @@ jobs: runner: namespace-profile-mac-m2-12c28g runner_for_virt: namespace-profile-mac-m2-12c28g runner_small: macos-latest-xlarge + secrets: + sentry_auth_token: ${{ secrets.SENTRY_AUTH_TOKEN }} + sentry_org: ${{ secrets.SENTRY_ORG }} + sentry_project: ${{ secrets.SENTRY_PROJECT }} success: runs-on: ubuntu-latest diff --git a/doc/manual/generate-manpage.nix b/doc/manual/generate-manpage.nix index efdf28cd7b53..f258c6cb80ff 100644 --- a/doc/manual/generate-manpage.nix +++ b/doc/manual/generate-manpage.nix @@ -67,16 +67,22 @@ let `${command}` [*option*...] ${arguments} ''; - maybeSubcommands = optionalString (details ? commands && details.commands != { }) '' + filteredCommands = filterAttrs (n: v: v.category.id != 103) (details.commands or { }); + + maybeSubcommands = optionalString (filteredCommands != { }) '' where *subcommand* is one of the following: ${subcommands} ''; - subcommands = if length categories > 1 then listCategories else listSubcommands details.commands; + subcommands = if length categories > 1 then listCategories else listSubcommands filteredCommands; categories = sort (x: y: x.id < y.id) ( - unique (map (cmd: { inherit (cmd.category) id description; }) (attrValues details.commands)) + unique ( + map (cmd: { inherit (cmd.category) id description; }) ( + attrValues filteredCommands + ) + ) ); listCategories = concatStrings (map showCategory categories); @@ -84,7 +90,7 @@ let showCategory = cat: '' **${toString cat.description}:** - ${listSubcommands (filterAttrs (n: v: v.category.id == cat.id) details.commands)} + ${listSubcommands (filterAttrs (n: v: v.category.id == cat.id) filteredCommands)} ''; listSubcommands = cmds: concatStrings (attrValues (mapAttrs showSubcommand cmds)); @@ -202,6 +208,8 @@ let ; }; }; + filteredCommands = filterAttrs (n: v: v.category.id != 103) (details.commands or { }); + subcommand = subCmd: processCommand { @@ -211,7 +219,7 @@ let inherit toplevel; }; in - [ cmd ] ++ concatMap subcommand (attrNames details.commands or { }); + [ cmd ] ++ concatMap subcommand (attrNames filteredCommands); manpages = processCommand { command = "nix"; diff --git a/maintainers/upload-debug-info-to-sentry.py b/maintainers/upload-debug-info-to-sentry.py new file mode 100755 index 000000000000..e1c242fd2fee --- /dev/null +++ b/maintainers/upload-debug-info-to-sentry.py @@ -0,0 +1,161 @@ +#!/usr/bin/env nix +#!nix shell --inputs-from . nixpkgs#sentry-cli --command python3 + +import argparse +import json +import os +import platform +import re +import subprocess +import sys +import urllib.error +import urllib.parse +import urllib.request + +NAR_DIR = "/tmp/nars" +DEBUG_INFO_DIR = "/tmp/debug-info" + + +def get_dynamic_libraries(executable: str) -> list[str]: + if platform.system() == "Darwin": + result = subprocess.run(["otool", "-L", executable], capture_output=True, text=True, check=True) + libs = [] + for line in result.stdout.splitlines()[1:]: # skip first line (the binary path itself) + # otool -L output lines look like: + # /nix/store/.../libfoo.dylib (compatibility version X.Y.Z, current version A.B.C) + m = re.match(r"\s+(\S+)\s+\(", line) + if m: + libs.append(m.group(1)) + return libs + else: + result = subprocess.run(["ldd", executable], capture_output=True, text=True, check=True) + libs = [] + for line in result.stdout.splitlines(): + # ldd output lines look like: + # libfoo.so.1 => /nix/store/.../libfoo.so.1 (0x...) + # /lib64/ld-linux-x86-64.so.2 (0x...) + m = re.search(r"=> (/\S+)", line) + if m: + libs.append(m.group(1)) + elif line.strip().startswith("/"): + path = line.strip().split()[0] + libs.append(path) + return libs + + +def get_build_id(path: str) -> str | None: + result = subprocess.run(["readelf", "-n", path], capture_output=True, text=True) + m = re.search(r"Build ID:\s+([0-9a-f]+)", result.stdout) + return m.group(1) if m else None + + +def download_nar(build_id: str, archive: str) -> str: + """Download a NAR to /tmp/nars and return the local path. Skips if already present.""" + base_url = f"https://cache.nixos.org/debuginfo/{build_id}" + nar_url = urllib.parse.urljoin(base_url, archive) + filename = nar_url.split("/")[-1] + local_path = os.path.join(NAR_DIR, filename) + if not os.path.exists(local_path): + os.makedirs(NAR_DIR, exist_ok=True) + print(f" downloading {nar_url} ...", file=sys.stderr) + urllib.request.urlretrieve(nar_url, local_path) + else: + print(f" already have {filename}", file=sys.stderr) + return local_path + + +def extract_debug_symbols(nar_path: str, member: str, build_id: str) -> str: + """Extract a member from a .nar.xz into /tmp/debug-info/.debug. Returns the output path.""" + out_path = os.path.join(DEBUG_INFO_DIR, f"{build_id}.debug") + if os.path.exists(out_path): + print(f" already extracted {out_path}", file=sys.stderr) + return out_path + os.makedirs(DEBUG_INFO_DIR, exist_ok=True) + print(f" extracting {member} -> {out_path} ...", file=sys.stderr) + xz = subprocess.Popen(["xz", "-d"], stdin=open(nar_path, "rb"), stdout=subprocess.PIPE) + nar_cat = subprocess.run( + ["nix", "nar", "cat", "/dev/stdin", member], + stdin=xz.stdout, + capture_output=True, + check=True, + ) + xz.wait() + with open(out_path, "wb") as f: + f.write(nar_cat.stdout) + return out_path + + +def find_debug_file_in_dirs(build_id: str, debug_dirs: list[str]) -> str | None: + """Look for a .debug file by build ID under /lib/debug/.build-id/NN/NNN.debug.""" + subpath = os.path.join("lib", "debug", ".build-id", build_id[:2], build_id[2:] + ".debug") + for d in debug_dirs: + candidate = os.path.join(d, subpath) + if os.path.exists(candidate): + return candidate + return None + + +def fetch_debuginfo(build_id: str) -> dict | None: + url = f"https://cache.nixos.org/debuginfo/{build_id}" + try: + with urllib.request.urlopen(url) as resp: + return json.loads(resp.read()) + except urllib.error.HTTPError as e: + if e.code == 404: + return None + raise + + +def main(): + parser = argparse.ArgumentParser( + description="Upload debug symbols to Sentry." + ) + parser.add_argument("executable", help="Path to the executable (e.g. ./result/bin/nix)") + parser.add_argument("--project", help="Sentry project ID") + parser.add_argument("--debug-dir", action="append", default=[], metavar="DIR", + help="Directory to search for debug files (may be repeated, Linux only)") + args = parser.parse_args() + + libs = [args.executable] + get_dynamic_libraries(args.executable) + + if platform.system() == "Darwin": + # On macOS there are no separate debug info files; upload the binaries directly. + print("Files to upload:", file=sys.stderr) + for lib in libs: + print(f" {lib}", file=sys.stderr) + files_to_upload = libs + else: + debug_files = [] + print("ELF files to process:", file=sys.stderr) + for lib in libs: + build_id = get_build_id(lib) + if build_id is None: + print(f" {lib} (no build ID)", file=sys.stderr) + continue + + local = find_debug_file_in_dirs(build_id, args.debug_dir) + if local: + print(f" {lib} ({build_id}): found locally at {local}", file=sys.stderr) + debug_files.append(local) + continue + + debuginfo = fetch_debuginfo(build_id) + if debuginfo is None: + print(f" {lib} ({build_id}, no debug info in cache)", file=sys.stderr) + continue + print(f" {lib} ({build_id}): member={debuginfo['member']}", file=sys.stderr) + nar_path = download_nar(build_id, debuginfo["archive"]) + debug_file = extract_debug_symbols(nar_path, debuginfo["member"], build_id) + debug_files.append(debug_file) + files_to_upload = debug_files + + if files_to_upload: + print(f"Uploading {len(files_to_upload)} file(s) to Sentry...", file=sys.stderr) + cmd = ["sentry-cli", "debug-files", "upload"] + if args.project: + cmd += ["--project", args.project] + subprocess.run(cmd + files_to_upload, check=True) + + +if __name__ == "__main__": + main() diff --git a/packaging/dependencies.nix b/packaging/dependencies.nix index f5e39e6e005f..3c68ed1e749b 100644 --- a/packaging/dependencies.nix +++ b/packaging/dependencies.nix @@ -147,4 +147,9 @@ scope: { pslSupport = !stdenv.hostPlatform.isStatic; idnSupport = !stdenv.hostPlatform.isStatic; }; + + sentry-native = (pkgs.callPackage ./sentry-native.nix { }).override { + # Avoid having two curls in our closure. + inherit (scope) curl; + }; } diff --git a/packaging/dev-shell.nix b/packaging/dev-shell.nix index 8f963f961fb1..d13a8d0e23cb 100644 --- a/packaging/dev-shell.nix +++ b/packaging/dev-shell.nix @@ -290,7 +290,8 @@ pkgs.nixComponents2.nix-util.overrideAttrs ( map (transformFlag "perl") (ignoreCrossFile pkgs.nixComponents2.nix-perl-bindings.mesonFlags) ) ++ map (transformFlag "libexpr") (ignoreCrossFile pkgs.nixComponents2.nix-expr.mesonFlags) - ++ map (transformFlag "libcmd") (ignoreCrossFile pkgs.nixComponents2.nix-cmd.mesonFlags); + ++ map (transformFlag "libcmd") (ignoreCrossFile pkgs.nixComponents2.nix-cmd.mesonFlags) + ++ map (transformFlag "nix") (ignoreCrossFile pkgs.nixComponents2.nix-cli.mesonFlags); nativeBuildInputs = let diff --git a/packaging/everything.nix b/packaging/everything.nix index 3206b8ba4235..1a9c948e8e2e 100644 --- a/packaging/everything.nix +++ b/packaging/everything.nix @@ -44,6 +44,10 @@ testers, patchedSrc ? null, + + curl, + boehmgc, + sentry-native, }: let @@ -102,6 +106,7 @@ stdenv.mkDerivation (finalAttrs: { "dev" "doc" "man" + "debug" ]; /** @@ -153,9 +158,18 @@ stdenv.mkDerivation (finalAttrs: { installPhase = let devPaths = lib.mapAttrsToList (_k: lib.getDev) finalAttrs.finalPackage.libs; + debugPaths = lib.map (lib.getOutput "debug") ( + lib.attrValues finalAttrs.finalPackage.libs + ++ [ + nix-cli + curl + boehmgc + ] + ++ lib.optional (stdenv.hostPlatform.isLinux && !stdenv.hostPlatform.isStatic) sentry-native + ); in '' - mkdir -p $out $dev/nix-support + mkdir -p $out $dev/nix-support $debug/lib/debug # Custom files echo $libs >> $dev/nix-support/propagated-build-inputs @@ -168,6 +182,12 @@ stdenv.mkDerivation (finalAttrs: { lndir $lib $dev done + for d in ${lib.escapeShellArgs debugPaths}; do + if [[ -d $d/lib/debug ]]; then + lndir $d/lib/debug $debug/lib/debug + fi + done + # Forwarded outputs ln -sT ${nix-manual} $doc ln -sT ${nix-manual.man} $man diff --git a/packaging/sentry-native.nix b/packaging/sentry-native.nix new file mode 100644 index 000000000000..06accdd5b18a --- /dev/null +++ b/packaging/sentry-native.nix @@ -0,0 +1,52 @@ +{ + lib, + stdenv, + fetchgit, + cmake, + curl, + pkg-config, + python3, + darwin, +}: + +stdenv.mkDerivation rec { + pname = "sentry-native"; + version = "0.13.5"; + + src = fetchgit { + url = "https://github.com/getsentry/sentry-native"; + tag = version; + hash = "sha256-vDBI6lB1DMLleAgRCfsHvTSdtmXOzvJSaNAt+NwOd3c="; + fetchSubmodules = true; + }; + + dontFixCmake = true; + + nativeBuildInputs = [ + cmake + pkg-config + ] + ++ lib.optionals stdenv.hostPlatform.isDarwin [ + python3 + darwin.bootstrap_cmds + ]; + + postPatch = '' + # Borrowed from psutil: stick to the old SDK name for now. + substituteInPlace external/crashpad/util/mac/mac_util.cc \ + --replace-fail kIOMainPortDefault kIOMasterPortDefault + ''; + + buildInputs = [ + curl + ]; + + cmakeBuildType = "RelWithDebInfo"; + + cmakeFlags = [ ]; + + outputs = [ + "out" + "dev" + ]; +} diff --git a/src/libcmd/include/nix/cmd/command.hh b/src/libcmd/include/nix/cmd/command.hh index ec2e0d9add4c..326b05c6274a 100644 --- a/src/libcmd/include/nix/cmd/command.hh +++ b/src/libcmd/include/nix/cmd/command.hh @@ -24,6 +24,7 @@ static constexpr Command::Category catHelp = -1; static constexpr Command::Category catSecondary = 100; static constexpr Command::Category catUtility = 101; static constexpr Command::Category catNixInstallation = 102; +static constexpr Command::Category catUndocumented = 103; static constexpr auto installablesCategory = "Options that change the interpretation of [installables](@docroot@/command-ref/new-cli/nix.md#installables)"; diff --git a/src/libfetchers/fetchers.cc b/src/libfetchers/fetchers.cc index 19018aaff9d0..fa62cc32ab07 100644 --- a/src/libfetchers/fetchers.cc +++ b/src/libfetchers/fetchers.cc @@ -13,6 +13,7 @@ #include "nix/util/environment-variables.hh" #include +#include namespace nix::fetchers { diff --git a/src/libmain/unix/stack.cc b/src/libmain/unix/stack.cc index 458693407270..0b0b5ae92888 100644 --- a/src/libmain/unix/stack.cc +++ b/src/libmain/unix/stack.cc @@ -10,6 +10,8 @@ namespace nix { +static struct sigaction savedSigsegvAction; + static void sigsegvHandler(int signo, siginfo_t * info, void * ctx) { /* Detect stack overflows by comparing the faulting address with @@ -34,12 +36,8 @@ static void sigsegvHandler(int signo, siginfo_t * info, void * ctx) } } - /* Restore default behaviour (i.e. segfault and dump core). */ - struct sigaction act; - sigfillset(&act.sa_mask); - act.sa_handler = SIG_DFL; - act.sa_flags = 0; - if (sigaction(SIGSEGV, &act, 0)) + /* Restore the original SIGSEGV handler. */ + if (sigaction(SIGSEGV, &savedSigsegvAction, 0)) abort(); } @@ -63,7 +61,7 @@ void detectStackOverflow() sigfillset(&act.sa_mask); act.sa_sigaction = sigsegvHandler; act.sa_flags = SA_SIGINFO | SA_ONSTACK; - if (sigaction(SIGSEGV, &act, 0)) + if (sigaction(SIGSEGV, &act, &savedSigsegvAction)) throw SysError("resetting SIGSEGV"); #endif } diff --git a/src/libutil/args.cc b/src/libutil/args.cc index bd3dc9c95dfa..66210a3e56f4 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -632,7 +632,7 @@ MultiCommand::MultiCommand(std::string_view commandName, const Commands & comman }}, .completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) { for (auto & [name, command] : commands) - if (hasPrefix(name, prefix)) + if (hasPrefix(name, prefix) && !hasPrefix(name, "__")) completions.add(name); }}}); diff --git a/src/nix/crash.cc b/src/nix/crash.cc new file mode 100644 index 000000000000..3ed491ce75e4 --- /dev/null +++ b/src/nix/crash.cc @@ -0,0 +1,50 @@ +#include "nix/cmd/command.hh" + +#include + +using namespace nix; + +struct CmdCrash : Command +{ + std::string type; + + CmdCrash() + { + expectArg("type", &type); + } + + Category category() override + { + return catUndocumented; + } + + std::string description() override + { + return "crash the program to test crash reporting"; + } + + void run() override + { + if (type == "segfault") { + printError("Triggering a segfault..."); + volatile int * p = nullptr; + *p = 123; + } + + else if (type == "assert") { + printError("Triggering an assertion failure..."); + assert(false && "This is an assertion failure"); + } + + else if (type == "logic-error") { + printError("Triggering a C++ logic error..."); + std::bitset<4>{"012"}; + } + + else { + throw Error("unknown crash type '%s'", type); + } + } +}; + +static auto rCrash = registerCommand("__crash"); diff --git a/src/nix/main.cc b/src/nix/main.cc index 0711804a5447..a0f2455f7e51 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -29,6 +29,9 @@ #include #include #include +#if HAVE_SENTRY +# include +#endif #ifndef _WIN32 # include @@ -121,6 +124,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs, virtual RootArgs categories[catSecondary] = "Infrequently used commands"; categories[catUtility] = "Utility/scripting commands"; categories[catNixInstallation] = "Commands for upgrading or troubleshooting your Nix installation"; + categories[catUndocumented] = "Undocumented commands"; addFlag({ .longName = "help", @@ -377,7 +381,41 @@ void mainWrapped(int argc, char ** argv) { savedArgv = argv; - registerCrashHandler(); + bool sentryEnabled = false; + +#if HAVE_SENTRY + auto sentryEndpoint = getEnv("NIX_SENTRY_ENDPOINT"); + + if (!sentryEndpoint && getEnv("DETSYS_IDS_TELEMETRY") != "disabled") { + try { + auto p = settings.nixConfDir / "sentry-endpoint"; + if (pathExists(p)) + sentryEndpoint = trim(readFile(p)); + } catch (...) { + ignoreExceptionExceptInterrupt(); + } + } + + if (sentryEndpoint && sentryEndpoint != "") { + sentry_options_t * options = sentry_options_new(); + sentry_options_set_dsn(options, sentryEndpoint->c_str()); + sentry_options_set_database_path(options, (getCacheDir() / "sentry").string().c_str()); + sentry_options_set_release(options, fmt("nix@%s", determinateNixVersion).c_str()); + sentry_options_set_traces_sample_rate(options, 0); + sentry_options_set_auto_session_tracking(options, false); + sentry_options_set_handler_path(options, CRASHPAD_HANDLER_PATH); + sentry_init(options); + sentryEnabled = true; + } + + Finally cleanupSentry([&]() { + if (sentryEnabled) + sentry_shutdown(); + }); +#endif + + if (!sentryEnabled) + registerCrashHandler(); /* The chroot helper needs to be run before any threads have been started. */ diff --git a/src/nix/meson.build b/src/nix/meson.build index 3b343614e421..137eb0767a23 100644 --- a/src/nix/meson.build +++ b/src/nix/meson.build @@ -50,6 +50,17 @@ mandir = get_option('mandir') mandir = fs.is_absolute(mandir) ? mandir : prefix / mandir configdata.set_quoted('NIX_MAN_DIR', mandir) +sentry_required = get_option('sentry') +configdata.set('HAVE_SENTRY', sentry_required.enabled().to_int()) +if sentry_required.enabled() + crashpad_handler_path = get_option('crashpad-handler') + assert( + crashpad_handler_path != '', + 'crashpad-handler path must be set when sentry is enabled', + ) + configdata.set_quoted('CRASHPAD_HANDLER_PATH', crashpad_handler_path) +endif + config_priv_h = configure_file( configuration : configdata, output : 'cli-config-private.hh', @@ -68,6 +79,7 @@ nix_sources = [ config_priv_h ] + files( 'config.cc', 'copy.cc', 'crash-handler.cc', + 'crash.cc', 'derivation-add.cc', 'derivation-show.cc', 'derivation.cc', @@ -190,7 +202,7 @@ this_exe = executable( sources, dependencies : deps_private_subproject + deps_private + deps_other, include_directories : include_dirs, - link_args : linker_export_flags, + link_args : linker_export_flags + (sentry_required.enabled() ? [ '-lsentry' ] : []), install : true, cpp_pch : do_pch ? [ 'pch/precompiled-headers.hh' ] : [], ) diff --git a/src/nix/meson.options b/src/nix/meson.options index 0fc680cfe4c7..61da4fad7eed 100644 --- a/src/nix/meson.options +++ b/src/nix/meson.options @@ -7,3 +7,17 @@ option( value : 'etc/profile.d', description : 'the path to install shell profile files', ) + +option( + 'sentry', + type : 'feature', + value : 'auto', + description : 'Enable Sentry crash reporting', +) + +option( + 'crashpad-handler', + type : 'string', + value : '', + description : 'Path to the crashpad_handler binary (required when sentry is enabled)', +) diff --git a/src/nix/package.nix b/src/nix/package.nix index 9c223bceb2ba..a1e94726d5f3 100644 --- a/src/nix/package.nix +++ b/src/nix/package.nix @@ -8,6 +8,7 @@ nix-expr, nix-main, nix-cmd, + sentry-native, # Configuration Options @@ -16,6 +17,7 @@ let inherit (lib) fileset; + enableSentry = !stdenv.hostPlatform.isStatic; in mkMesonExecutable (finalAttrs: { @@ -76,10 +78,15 @@ mkMesonExecutable (finalAttrs: { && stdenv.hostPlatform.isStatic && stdenv.cc.libcxx != null && stdenv.cc.libcxx.isLLVM - ) llvmPackages.libunwind; + ) llvmPackages.libunwind + ++ lib.optional enableSentry sentry-native; mesonFlags = [ - ]; + (lib.mesonEnable "sentry" enableSentry) + ] + ++ lib.optional enableSentry ( + lib.mesonOption "crashpad-handler" "${sentry-native}/bin/crashpad_handler" + ); postInstall = lib.optionalString stdenv.hostPlatform.isStatic '' mkdir -p $out/nix-support diff --git a/tests/functional/common/init.sh b/tests/functional/common/init.sh index 6d98b7ae357e..9b2bed678ec4 100755 --- a/tests/functional/common/init.sh +++ b/tests/functional/common/init.sh @@ -3,6 +3,9 @@ # for shellcheck : "${test_nix_conf_dir?}" "${test_nix_conf?}" +# Don't upload crashes from tests to Sentry. +export NIX_SENTRY_ENDPOINT= + if isTestOnNixOS; then mkdir -p "$test_nix_conf_dir" "$TEST_HOME" diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 42965995807d..d1777cb80dae 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -176,6 +176,7 @@ suites = [ 'symlinks.sh', 'external-builders.sh', 'wasm.sh', + 'sentry.sh', ], 'workdir' : meson.current_source_dir(), }, diff --git a/tests/functional/sentry.sh b/tests/functional/sentry.sh new file mode 100644 index 000000000000..5f61d86dc57d --- /dev/null +++ b/tests/functional/sentry.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +source common.sh + +# Enable sentry with a fake endpoint. +unset NIX_SENTRY_ENDPOINT +echo -n "file://$TEST_ROOT/sentry-endpoint" > "$NIX_CONF_DIR/sentry-endpoint" + +ulimit -c 0 + +sentryDir="$TEST_HOME/.cache/nix/sentry" + +nix --version +if ! [[ -d $sentryDir ]]; then + skipTest "not built with sentry support" +fi + +for type in segfault assert logic-error; do + if [[ $type = logic-error && $(uname) = Darwin ]]; then continue; fi + + rm -rf "$sentryDir" + + (! nix __crash "$type") + + envelopes=("$sentryDir"/pending/*.dmp) + if [[ ! -e "${envelopes[0]}" ]]; then + fail "No crash dump found in $sentryDir after crash" + fi +done