From 2f0bda1c04869caa09d5b8dc9b51e4b99d184a10 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 26 Aug 2026 14:52:37 +0200 Subject: [PATCH 1/4] Allow binding sockets in the macOS sandbox when networking is blocked The sandbox profile generated for block-network spawns denied `network-bind`: it allowed accepting and establishing loopback connections, but binding a socket - what any local test server does first - was still rejected by `(deny network*)`. As a result, tests using e.g. OkHttp MockWebServer or Dropwizard failed on macOS while passing inside the Linux sandbox's network namespace. Allow `network-bind` for any local address: servers commonly bind to the wildcard address (0.0.0.0 or ::), which the "localhost" filter does not match, and a bound socket still cannot exchange traffic with other hosts because inbound and outbound traffic remains restricted to loopback. Also allow local Unix domain sockets, which are filesystem-scoped IPC, in both directions. This matches the behavior of the Linux sandbox, where arbitrary binds succeed but only the loopback interface exists. Adds a regression test that inspects the generated sandbox profile. Fixes https://github.com/bazelbuild/bazel/issues/14828 --- .../lib/sandbox/DarwinSandboxedSpawnRunner.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java index 84ed0b15de1f1b..8aa591852d75bc 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java @@ -282,8 +282,25 @@ private static void writeConfig( if (!allowNetwork) { out.println("(deny network*)"); + // Binding to any local address must be allowed: servers commonly bind to the wildcard + // address (0.0.0.0 or ::), which the "localhost" filter does not match. Outbound + // traffic stays restricted to loopback by the rules below, so a sandboxed spawn still + // cannot reach another host. + // + // Inbound traffic cannot be restricted the same way. macOS decides network-inbound + // from the local address alone, and "localhost" there matches every address assigned + // to the machine rather than just 127.0.0.1 and ::1, so a peer elsewhere on the + // network can reach a spawn that listens on a non-loopback address. Filtering + // network-inbound on the remote address is not an option: such a rule compiles but + // has no effect. The Linux sandbox does not have this gap because its network + // namespace only ever has a loopback interface. + // See https://github.com/bazelbuild/bazel/issues/14828. + out.println("(allow network-bind (local ip \"*:*\"))"); out.println("(allow network-inbound (local ip \"localhost:*\"))"); out.println("(allow network* (remote ip \"localhost:*\"))"); + // Unix domain sockets are filesystem-scoped IPC and cannot reach other hosts, so + // allow them in both directions, just like the Linux network namespace does. + out.println("(allow network* (local unix-socket))"); out.println("(allow network* (remote unix-socket))"); } From b015ac7ec3eb28d1e6b66a66c3132a5612ecbcf7 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 26 Aug 2026 14:52:37 +0200 Subject: [PATCH 2/4] Add cross-platform integration coverage for sandbox socket binding Extend bazel_sandboxing_networking_test.sh, which runs on both Linux and macOS, with in-sandbox server coverage for the various kinds of localhost binding: * binding to 127.0.0.1, ::1, and the "localhost" hostname (in addition to the existing wildcard-address coverage via :loopback), * binding and connecting to a Unix domain socket inside the sandbox. Exercising real binds through the sandbox is what actually establishes that the profile works; asserting on the generated profile text would not. testing_server.py gains a --bind_address flag so the test server can bind a specific address instead of the wildcard address. The :loopback target now also carries the tags under test so that the tag-based block-network scenario exercises in-sandbox servers as well. --- .../bazel/bazel_sandboxing_networking_test.sh | 70 +++++++++++++++++++ src/test/shell/bazel/testing_server.py | 8 ++- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/test/shell/bazel/bazel_sandboxing_networking_test.sh b/src/test/shell/bazel/bazel_sandboxing_networking_test.sh index 0d5a10c278c274..60be76bfcbfdb5 100755 --- a/src/test/shell/bazel/bazel_sandboxing_networking_test.sh +++ b/src/test/shell/bazel/bazel_sandboxing_networking_test.sh @@ -85,6 +85,56 @@ genrule( + "port=\$\$(head -n 1 port.txt); " + "curl -fo \$@ localhost:\$\$port; " + "kill \$\$pid", + tags = [ ${tags} ], +) + +genrule( + name = "bind-ipv4", + outs = [ "bind-ipv4.txt" ], + cmd = "python3 $python_server --bind_address=127.0.0.1 always $(pwd)/file_to_serve >bind-ipv4-port.txt & " + + "pid=\$\$!; " + + "while ! grep started bind-ipv4-port.txt; do sleep 1; done; " + + "port=\$\$(head -n 1 bind-ipv4-port.txt); " + + "curl -fo \$@ 127.0.0.1:\$\$port; " + + "kill \$\$pid", + tags = [ ${tags} ], +) + +genrule( + name = "bind-ipv6", + outs = [ "bind-ipv6.txt" ], + cmd = "python3 $python_server --bind_address=::1 always $(pwd)/file_to_serve >bind-ipv6-port.txt & " + + "pid=\$\$!; " + + "while ! grep started bind-ipv6-port.txt; do sleep 1; done; " + + "port=\$\$(head -n 1 bind-ipv6-port.txt); " + + "curl -g -fo \$@ 'http://[::1]:'\$\$port; " + + "kill \$\$pid", + tags = [ ${tags} ], +) + +genrule( + name = "bind-localhost", + outs = [ "bind-localhost.txt" ], + cmd = "python3 $python_server --bind_address=localhost always $(pwd)/file_to_serve >bind-localhost-port.txt & " + + "pid=\$\$!; " + + "while ! grep started bind-localhost-port.txt; do sleep 1; done; " + + "port=\$\$(head -n 1 bind-localhost-port.txt); " + + "curl -fo \$@ localhost:\$\$port; " + + "kill \$\$pid", + tags = [ ${tags} ], +) + +genrule( + name = "bind-unix-socket", + outs = [ "bind-unix-socket.txt" ], + # The socket is bound at a relative path to stay below the sun_path length + # limit, which the absolute path of the sandboxed execroot can exceed. + cmd = "python3 $python_server --unix_socket=server.sock always $(pwd)/file_to_serve & " + + "pid=\$\$!; " + + "while ! [ -S server.sock ]; do sleep 1; done; " + + "curl --unix-socket server.sock -fo \$@ irrelevant-url; " + + "kill \$\$pid", + tags = [ ${tags} ], ) EOF @@ -161,6 +211,10 @@ function test_sandbox_network_access() { check_network_ok localhost check_network_ok unix-socket check_network_ok loopback + check_network_ok bind-ipv4 + check_network_ok bind-ipv6 + check_network_ok bind-localhost + check_network_ok bind-unix-socket if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then check_network_ok remote-ip check_network_ok remote-name @@ -180,6 +234,10 @@ function test_sandbox_block_network_access() { check_network_ok unix-socket --experimental_sandbox_default_allow_network=false check_network_ok loopback --experimental_sandbox_default_allow_network=false + check_network_ok bind-ipv4 --experimental_sandbox_default_allow_network=false + check_network_ok bind-ipv6 --experimental_sandbox_default_allow_network=false + check_network_ok bind-localhost --experimental_sandbox_default_allow_network=false + check_network_ok bind-unix-socket --experimental_sandbox_default_allow_network=false if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then check_network_not_ok remote-ip --experimental_sandbox_default_allow_network=false check_network_not_ok remote-name --experimental_sandbox_default_allow_network=false @@ -196,6 +254,10 @@ EOF check_network_ok localhost check_network_ok unix-socket check_network_ok loopback + check_network_ok bind-ipv4 + check_network_ok bind-ipv6 + check_network_ok bind-localhost + check_network_ok bind-unix-socket if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then check_network_ok remote-ip check_network_ok remote-name @@ -208,6 +270,10 @@ function test_sandbox_network_access_with_requires_network() { check_network_ok localhost --experimental_sandbox_default_allow_network=false check_network_ok unix-socket --experimental_sandbox_default_allow_network=false check_network_ok loopback --experimental_sandbox_default_allow_network=false + check_network_ok bind-ipv4 --experimental_sandbox_default_allow_network=false + check_network_ok bind-ipv6 --experimental_sandbox_default_allow_network=false + check_network_ok bind-localhost --experimental_sandbox_default_allow_network=false + check_network_ok bind-unix-socket --experimental_sandbox_default_allow_network=false if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then check_network_ok remote-ip --experimental_sandbox_default_allow_network=false check_network_ok remote-name --experimental_sandbox_default_allow_network=false @@ -226,6 +292,10 @@ function test_sandbox_network_access_with_block_network() { fi check_network_ok unix-socket --experimental_sandbox_default_allow_network=true check_network_ok loopback --experimental_sandbox_default_allow_network=true + check_network_ok bind-ipv4 --experimental_sandbox_default_allow_network=true + check_network_ok bind-ipv6 --experimental_sandbox_default_allow_network=true + check_network_ok bind-localhost --experimental_sandbox_default_allow_network=true + check_network_ok bind-unix-socket --experimental_sandbox_default_allow_network=true if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then check_network_not_ok remote-ip --experimental_sandbox_default_allow_network=true check_network_not_ok remote-name --experimental_sandbox_default_allow_network=true diff --git a/src/test/shell/bazel/testing_server.py b/src/test/shell/bazel/testing_server.py index 42946c418ed741..738476aaf77afd 100644 --- a/src/test/shell/bazel/testing_server.py +++ b/src/test/shell/bazel/testing_server.py @@ -134,6 +134,7 @@ def serve_file(self): def main(argv): parser = argparse.ArgumentParser() parser.add_argument('--unix_socket', action='store') + parser.add_argument('--bind_address', action='store') parser.add_argument('--dump_headers', action='store') parser.add_argument('mode', type=str, nargs='?') @@ -165,7 +166,12 @@ def main(argv): while port is None: try: port = random.randrange(32760, 59760) - if socket.has_dualstack_ipv6(): + if args.bind_address: + if ':' in args.bind_address: + httpd = TCPServerV6((args.bind_address, port), Handler) + else: + httpd = TCPServer((args.bind_address, port), Handler) + elif socket.has_dualstack_ipv6(): httpd = TCPServerDualStack(('::', port), Handler) elif socket.has_ipv6: httpd = TCPServerV6(('::', port), Handler) From 6bd14bec6382b0fd4ad5499f203b88b56efe8b4c Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 25 Aug 2026 22:32:27 +0000 Subject: [PATCH 3/4] Validate that sandbox network blocking stops non-loopback access The negative direction - blocked spawns must not be able to reach anything beyond loopback - was previously only covered when REMOTE_NETWORK_ADDRESS was explicitly set, which CI does not do. Add hermetic checks that do not require Internet connectivity, using a non-loopback address of the local machine: one genrule connects to the existing wildcard-bound test server through that address, another binds its own server to it. The expected outcome differs per platform, so assert it either way instead of skipping. The linux-sandbox runs blocked spawns in a network namespace that only has a loopback interface, so the address is unreachable there and the bind fails. The macOS sandbox cannot express "loopback only": the host part of an SBPL "ip" filter may only be "*" or "localhost", and "localhost" matches every address assigned to the local machine rather than just 127.0.0.1 and ::1, so both checks still succeed there. Neither sandbox lets a blocked spawn reach any other host, which remains covered by the remote-ip and remote-name checks. Both checks are skipped on machines without a non-loopback address. Also make testing_server.py fail fast when a bind cannot succeed: retrying a permanent failure with a different port would loop forever and hang the caller waiting for the "started" line. --- .../bazel/bazel_sandboxing_networking_test.sh | 101 +++++++++++++++++- src/test/shell/bazel/testing_server.py | 9 +- 2 files changed, 105 insertions(+), 5 deletions(-) diff --git a/src/test/shell/bazel/bazel_sandboxing_networking_test.sh b/src/test/shell/bazel/bazel_sandboxing_networking_test.sh index 60be76bfcbfdb5..1c039790bf7204 100755 --- a/src/test/shell/bazel/bazel_sandboxing_networking_test.sh +++ b/src/test/shell/bazel/bazel_sandboxing_networking_test.sh @@ -138,6 +138,54 @@ genrule( ) EOF + # A non-loopback address of this machine allows validating that the sandbox + # blocks all network access beyond loopback - the same boundary that keeps + # out the Internet - without requiring actual Internet connectivity in the + # test environment: the server behind nc_port listens on the wildcard + # address and is thus also reachable via this address, but only if the + # sandbox does not block non-loopback traffic. + non_loopback_ip="$(python3 -c 'import socket +s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +try: + s.connect(("10.255.255.255", 1)) + print(s.getsockname()[0]) +except OSError: + pass')" + if [[ "${non_loopback_ip}" == 127.* ]]; then + non_loopback_ip= + fi + + if [[ -n "${non_loopback_ip}" ]]; then + cat <>pkg/BUILD +genrule( + name = "non-loopback", + outs = [ "non-loopback.txt" ], + cmd = "curl --connect-timeout 5 -fo \$@ ${non_loopback_ip}:${nc_port}", + tags = [ ${tags} ], +) + +genrule( + name = "bind-non-loopback", + outs = [ "bind-non-loopback.txt" ], + # Unlike the loopback binds above, this bind can legitimately fail - a network + # namespace has no such address - so stop waiting once the server is gone and + # cap the wait, rather than hanging until the test times out. + cmd = "python3 $python_server --bind_address=${non_loopback_ip} always $(pwd)/file_to_serve >bind-non-loopback-port.txt & " + + "pid=\$\$!; " + + "for _ in \$\$(seq 1 30); do " + + "grep -q started bind-non-loopback-port.txt && break; " + + "kill -0 \$\$pid 2>/dev/null || break; sleep 1; done; " + + "port=\$\$(head -n 1 bind-non-loopback-port.txt); " + + "curl -fo \$@ ${non_loopback_ip}:\$\$port; " + + "kill \$\$pid", + tags = [ ${tags} ], +) +EOF + else + echo "Not registering tests for non-loopback network sandboxing;" \ + "could not determine a non-loopback address of this machine" + fi + if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then local hostname="${REMOTE_NETWORK_ADDRESS%:*}" local remote_ip @@ -205,6 +253,29 @@ function check_network_not_ok() { || fail "'${target}' produced output but was expected to fail" } +# Checks that the given target, which must have been created by a previous call +# to setup_network_tests and which uses a non-loopback address of this machine, +# behaves as expected in a scenario in which the sandbox blocks networking. +# +# The macOS sandbox cannot express "loopback only": the host part of an SBPL +# "ip" filter may only be "*" or "localhost", and "localhost" matches every +# address assigned to the local machine rather than just 127.0.0.1 and ::1. +# Blocked spawns can therefore still reach this machine through its non-loopback +# addresses. The linux-sandbox instead runs the spawn in a network namespace +# that only has a loopback interface, so the address does not exist there at +# all. Neither sandbox lets a blocked spawn reach any *other* host, which is +# what the remote-ip and remote-name checks verify when REMOTE_NETWORK_ADDRESS +# is set. +function check_non_loopback_blocked() { + local target="${1}"; shift + + if is_linux; then + check_network_not_ok "${target}" "${@}" + else + check_network_ok "${target}" "${@}" + fi +} + function test_sandbox_network_access() { setup_network_tests '"some-tag"' @@ -215,6 +286,10 @@ function test_sandbox_network_access() { check_network_ok bind-ipv6 check_network_ok bind-localhost check_network_ok bind-unix-socket + if [[ -n "${non_loopback_ip}" ]]; then + check_network_ok non-loopback + check_network_ok bind-non-loopback + fi if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then check_network_ok remote-ip check_network_ok remote-name @@ -225,8 +300,9 @@ function test_sandbox_block_network_access() { setup_network_tests '"some-tag"' if is_linux; then - # TODO(jmmv): The linux-sandbox claims to allow localhost connectivity - # within the network namespace... but that doesn't seem to be the case. + # The server behind nc_port runs outside the sandbox and is thus not + # reachable from the linux-sandbox's network namespace. Connectivity within + # the namespace itself does work, as the loopback check below shows. check_network_not_ok localhost --experimental_sandbox_default_allow_network=false else check_network_ok localhost --experimental_sandbox_default_allow_network=false @@ -238,6 +314,10 @@ function test_sandbox_block_network_access() { check_network_ok bind-ipv6 --experimental_sandbox_default_allow_network=false check_network_ok bind-localhost --experimental_sandbox_default_allow_network=false check_network_ok bind-unix-socket --experimental_sandbox_default_allow_network=false + if [[ -n "${non_loopback_ip}" ]]; then + check_non_loopback_blocked non-loopback --experimental_sandbox_default_allow_network=false + check_non_loopback_blocked bind-non-loopback --experimental_sandbox_default_allow_network=false + fi if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then check_network_not_ok remote-ip --experimental_sandbox_default_allow_network=false check_network_not_ok remote-name --experimental_sandbox_default_allow_network=false @@ -258,6 +338,10 @@ EOF check_network_ok bind-ipv6 check_network_ok bind-localhost check_network_ok bind-unix-socket + if [[ -n "${non_loopback_ip}" ]]; then + check_network_ok non-loopback + check_network_ok bind-non-loopback + fi if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then check_network_ok remote-ip check_network_ok remote-name @@ -274,6 +358,10 @@ function test_sandbox_network_access_with_requires_network() { check_network_ok bind-ipv6 --experimental_sandbox_default_allow_network=false check_network_ok bind-localhost --experimental_sandbox_default_allow_network=false check_network_ok bind-unix-socket --experimental_sandbox_default_allow_network=false + if [[ -n "${non_loopback_ip}" ]]; then + check_network_ok non-loopback --experimental_sandbox_default_allow_network=false + check_network_ok bind-non-loopback --experimental_sandbox_default_allow_network=false + fi if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then check_network_ok remote-ip --experimental_sandbox_default_allow_network=false check_network_ok remote-name --experimental_sandbox_default_allow_network=false @@ -284,8 +372,9 @@ function test_sandbox_network_access_with_block_network() { setup_network_tests '"block-network"' if is_linux; then - # TODO(jmmv): The linux-sandbox claims to allow localhost connectivity - # within the network namespace... but that doesn't seem to be the case. + # The server behind nc_port runs outside the sandbox and is thus not + # reachable from the linux-sandbox's network namespace. Connectivity within + # the namespace itself does work, as the loopback check below shows. check_network_not_ok localhost --experimental_sandbox_default_allow_network=true else check_network_ok localhost --experimental_sandbox_default_allow_network=true @@ -296,6 +385,10 @@ function test_sandbox_network_access_with_block_network() { check_network_ok bind-ipv6 --experimental_sandbox_default_allow_network=true check_network_ok bind-localhost --experimental_sandbox_default_allow_network=true check_network_ok bind-unix-socket --experimental_sandbox_default_allow_network=true + if [[ -n "${non_loopback_ip}" ]]; then + check_non_loopback_blocked non-loopback --experimental_sandbox_default_allow_network=true + check_non_loopback_blocked bind-non-loopback --experimental_sandbox_default_allow_network=true + fi if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then check_network_not_ok remote-ip --experimental_sandbox_default_allow_network=true check_network_not_ok remote-name --experimental_sandbox_default_allow_network=true diff --git a/src/test/shell/bazel/testing_server.py b/src/test/shell/bazel/testing_server.py index 738476aaf77afd..76210fb2e3ea1b 100644 --- a/src/test/shell/bazel/testing_server.py +++ b/src/test/shell/bazel/testing_server.py @@ -17,6 +17,7 @@ # pylint: disable=g-import-not-at-top,g-importing-member import argparse import base64 +import errno import json try: @@ -177,7 +178,13 @@ def main(argv): httpd = TCPServerV6(('::', port), Handler) else: httpd = TCPServer(('', port), Handler) - except socket.error: + except socket.error as e: + # A port collision is the only bind failure worth retrying with a + # different port. Retrying a permanent failure - e.g. the requested + # address not existing in this network namespace - would loop forever + # and hang the caller waiting for the 'started' line below. + if e.errno != errno.EADDRINUSE: + raise port = None sys.stdout.write('%d\nstarted\n' % (port,)) sys.stdout.flush() From 1b8679e2dcc7aaa81125b49c65cb27a7cbf9cf34 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 26 Aug 2026 12:26:56 +0200 Subject: [PATCH 4/4] Assert the effect of --sandbox_fake_hostname on every platform The flag is only implemented by the linux-sandbox, which sets the hostname to "localhost" inside a UTS namespace. Everywhere else it is silently accepted and does nothing, and the test returned early outside Linux, leaving that behavior unasserted. Run the same test with and without the flag and compare the hostname it observes: on Linux it must become "localhost", everywhere else it must be unchanged. Comparing against the hostname seen by the very same test avoids having to predict how the machine's hostname resolves. --- .../bazel/bazel_sandboxing_networking_test.sh | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/test/shell/bazel/bazel_sandboxing_networking_test.sh b/src/test/shell/bazel/bazel_sandboxing_networking_test.sh index 1c039790bf7204..2eceba909a49ba 100755 --- a/src/test/shell/bazel/bazel_sandboxing_networking_test.sh +++ b/src/test/shell/bazel/bazel_sandboxing_networking_test.sh @@ -429,16 +429,14 @@ EOF || fail "test should have passed" } -function test_hostname_inside_sandbox_is_localhost_when_using_sandbox_fake_hostname_flag() { - if ! is_linux; then - echo "Skipping test: fake hostnames not supported in this system" 1>&2 - return 0 - fi - +# --sandbox_fake_hostname is only implemented by the linux-sandbox. Everywhere +# else the flag is silently accepted and does nothing, so compare against the +# hostname that the very same test observes without the flag. +function test_sandbox_fake_hostname_flag() { add_rules_java "MODULE.bazel" setup_javatest_support mkdir -p src/test/java/com/example - cat > src/test/java/com/example/HostNameIsLocalhostTest.java <<'EOF' + cat > src/test/java/com/example/FakeHostNameTest.java <<'EOF' package com.example; import static org.junit.Assert.*; @@ -447,25 +445,45 @@ import org.junit.Test; import java.net.*; import java.io.*; -public class HostNameIsLocalhostTest { +public class FakeHostNameTest { @Test - public void testHostNameIsLocalhost() throws Exception { + public void testGetHostName() throws Exception { // This will throw an exception, if the local hostname cannot be resolved via DNS. - assertEquals("localhost", InetAddress.getLocalHost().getHostName()); + String hostName = InetAddress.getLocalHost().getHostName(); + assertNotNull(hostName); + System.out.println("HOSTNAME=[" + hostName + "]"); } } EOF cat > src/test/java/com/example/BUILD <<'EOF' load("@rules_java//java:java_test.bzl", "java_test") java_test( - name = "HostNameIsLocalhostTest", - srcs = ["HostNameIsLocalhostTest.java"], + name = "FakeHostNameTest", + srcs = ["FakeHostNameTest.java"], deps = ['//third_party:junit4'], ) EOF - bazel test --sandbox_fake_hostname --test_output=streamed src/test/java/com/example:HostNameIsLocalhostTest &> $TEST_log \ - || fail "test should have passed" + # Both runs must actually execute the test, not replay a cached result. + bazel test --nocache_test_results --test_output=streamed \ + src/test/java/com/example:FakeHostNameTest &> $TEST_log \ + || fail "test should have passed without --sandbox_fake_hostname" + local real_hostname + real_hostname="$(sed -n 's/.*HOSTNAME=\[\([^]]*\)\].*/\1/p' $TEST_log | head -n 1)" + [[ -n "${real_hostname}" ]] \ + || fail "could not determine the hostname seen inside the sandbox" + + bazel test --sandbox_fake_hostname --nocache_test_results --test_output=streamed \ + src/test/java/com/example:FakeHostNameTest &> $TEST_log \ + || fail "test should have passed with --sandbox_fake_hostname" + local faked_hostname + faked_hostname="$(sed -n 's/.*HOSTNAME=\[\([^]]*\)\].*/\1/p' $TEST_log | head -n 1)" + + if is_linux; then + assert_equals "localhost" "${faked_hostname}" + else + assert_equals "${real_hostname}" "${faked_hostname}" + fi } # The test shouldn't fail if the environment doesn't support running it.