#!/bin/bash
#
# Copyright 2015 The Bazel Authors. 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.
#
# Test //external mechanisms
#

# Load the test setup defined in the parent directory
CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${CURRENT_DIR}/../integration_test_setup.sh" \
  || { echo "integration_test_setup.sh not found!" >&2; exit 1; }
source "${CURRENT_DIR}/remote_helpers.sh" \
  || { echo "remote_helpers.sh not found!" >&2; exit 1; }

set_up() {
  bazel clean --expunge >& $TEST_log
  mkdir -p zoo
  cat > zoo/BUILD <<EOF
java_binary(
    name = "ball-pit",
    srcs = ["BallPit.java"],
    main_class = "BallPit",
    deps = ["//external:mongoose"],
)
EOF

  cat > zoo/BallPit.java <<EOF
import carnivore.Mongoose;

public class BallPit {
    public static void main(String args[]) {
        Mongoose.frolic();
    }
}
EOF
}

tear_down() {
  shutdown_server
  if [ -d "${TEST_TMPDIR}/server_dir" ]; then
    rm -fr "${TEST_TMPDIR}/server_dir"
  fi
}

function zip_up() {
  repo2_zip=$TEST_TMPDIR/fox.zip
  zip -0 -ry $repo2_zip WORKSPACE fox
}

function tar_gz_up() {
  repo2_zip=$TEST_TMPDIR/fox.tar.gz
  tar czf $repo2_zip WORKSPACE fox
}

function tar_xz_up() {
  repo2_zip=$TEST_TMPDIR/fox.tar.xz
  tar cJf $repo2_zip WORKSPACE fox
}

# Test downloading a file from a repository.
# This creates a simple repository containing:
#
# repo/
#   WORKSPACE
#   zoo/
#     BUILD
#     female.sh
#
# And a .zip file, which contains:
#
# WORKSPACE
# fox/
#   BUILD
#   male
#   male_relative -> male
#   male_absolute -> /fox/male
function http_archive_helper() {
  zipper=$1
  local write_workspace
  [[ $# -gt 1 ]] && [[ "$2" = "nowrite" ]] && write_workspace=1 || write_workspace=0

  if [[ $write_workspace -eq 0 ]]; then
    # Create a zipped-up repository HTTP response.
    repo2=$TEST_TMPDIR/repo2
    rm -rf $repo2
    mkdir -p $repo2/fox
    cd $repo2
    create_workspace_with_default_repos WORKSPACE
    cat > fox/BUILD <<EOF
filegroup(
    name = "fox",
    srcs = ["male"],
    visibility = ["//visibility:public"],
)
EOF
    what_does_the_fox_say="Fraka-kaka-kaka-kaka-kow"
    cat > fox/male <<EOF
#!/bin/sh
echo $what_does_the_fox_say
EOF
    chmod +x fox/male
    touch -t 200403010021.42 fox/male
    ln -s male fox/male_relative
    ln -s /fox/male fox/male_absolute
    # Add some padding to the .zip to test that Bazel's download logic can
    # handle breaking a response into chunks.
    dd if=/dev/zero of=fox/padding bs=1024 count=10240 >& $TEST_log
    $zipper >& $TEST_log
    repo2_name=$(basename $repo2_zip)
    sha256=$(sha256sum $repo2_zip | cut -f 1 -d ' ')
  fi
  serve_file $repo2_zip

  cd ${WORKSPACE_DIR}
  if [[ $write_workspace = 0 ]]; then
    cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
http_archive(
    name = 'endangered',
    url = 'http://127.0.0.1:$nc_port/$repo2_name',
    sha256 = '$sha256'
)
EOF

    cat > zoo/BUILD <<EOF
sh_binary(
    name = "breeding-program",
    srcs = ["female.sh"],
    data = ["@endangered//fox"],
)
EOF

    cat > zoo/female.sh <<EOF
#!/bin/sh
../endangered/fox/male
EOF
    chmod +x zoo/female.sh
fi

  bazel run //zoo:breeding-program >& $TEST_log --show_progress_rate_limit=0 \
    || echo "Expected build/run to succeed"
  kill_nc
  expect_log $what_does_the_fox_say

  base_external_path=bazel-out/../external/endangered/fox
  assert_files_same ${base_external_path}/male ${base_external_path}/male_relative
  assert_files_same ${base_external_path}/male ${base_external_path}/male_absolute
  case "${PLATFORM}" in
    darwin)
      ts="$(stat -f %m ${base_external_path}/male)"
      ;;
    *)
      ts="$(stat -c %Y ${base_external_path}/male)"
      ;;
  esac
  assert_equals "1078100502" "$ts"
}

function assert_files_same() {
  assert_contains "$(cat $1)" $2 && return 0
  echo "Expected these to be the same:"
  echo "---------------------------"
  cat $1
  echo "==========================="
  cat $2
  echo "---------------------------"
  return 1
}

function test_http_archive_zip() {
  http_archive_helper zip_up

  # Test with the extension
  serve_file $repo2_zip
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = 'endangered',
    url = 'http://127.0.0.1:$nc_port/bleh',
    sha256 = '$sha256',
    type = 'zip',
)
EOF
  bazel run //zoo:breeding-program >& $TEST_log \
    || echo "Expected build/run to succeed"
  kill_nc
  expect_log $what_does_the_fox_say
}

function test_http_archive_tgz() {
  http_archive_helper tar_gz_up
  bazel shutdown
  http_archive_helper tar_gz_up
}

function test_http_archive_tar_xz() {
  http_archive_helper tar_xz_up
}

function test_http_archive_tar_zstd() {
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = 'test_zstd_repo',
    url = 'file://$(rlocation io_bazel/src/test/shell/bazel/testdata/zstd_test_archive.tar.zst)',
    sha256 = '12b0116f2a3c804859438e102a8a1d5f494c108d1b026da9f6ca55fb5107c7e9',
    build_file_content = 'filegroup(name="x", srcs=glob(["*"]))',
)
EOF
  bazel build @test_zstd_repo//...

  base_external_path=bazel-out/../external/test_zstd_repo
  assert_contains "test content" "${base_external_path}/test_dir/test_file"
}

function test_http_archive_upper_case_sha() {
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = 'test_zstd_repo',
    url = 'file://$(rlocation io_bazel/src/test/shell/bazel/testdata/zstd_test_archive.tar.zst)',
    sha256 = '12B0116F2A3C804859438E102A8A1D5F494C108D1B026DA9F6CA55FB5107C7E9',
    build_file_content = 'filegroup(name="x", srcs=glob(["*"]))',
)
EOF
  bazel build @test_zstd_repo//...

  base_external_path=bazel-out/../external/test_zstd_repo
  assert_contains "test content" "${base_external_path}/test_dir/test_file"
}

function test_http_archive_no_server() {
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(name = 'endangered', url = 'http://bad.example/repo.zip',
    sha256 = '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9826')
EOF

  cat > zoo/BUILD <<EOF
sh_binary(
    name = "breeding-program",
    srcs = ["female.sh"],
    data = ["@endangered//fox"],
)
EOF

  cat > zoo/female.sh <<EOF
#!/bin/sh
cat fox/male
EOF
  chmod +x zoo/female.sh

  bazel fetch //zoo:breeding-program >& $TEST_log && fail "Expected fetch to fail"
  expect_log "Unknown host: bad.example"
}

function test_http_archive_mismatched_sha256() {
  # Create a zipped-up repository HTTP response.
  repo2=$TEST_TMPDIR/repo2
  rm -rf $repo2
  mkdir -p $repo2
  cd $repo2
  create_workspace_with_default_repos WORKSPACE
  repo2_zip=$TEST_TMPDIR/fox.zip
  zip -r $repo2_zip WORKSPACE
  serve_file $repo2_zip
  wrong_sha256=0000000000000000000000000000000000000000

  cd ${WORKSPACE_DIR}
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = 'endangered',
    url = 'http://127.0.0.1:$nc_port/repo.zip',
    sha256 = '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9826',
)
EOF

  cat > zoo/BUILD <<EOF
sh_binary(
    name = "breeding-program",
    srcs = ["female.sh"],
    data = ["@endangered//fox"],
)
EOF

  cat > zoo/female.sh <<EOF
#!/bin/sh
cat fox/male
EOF
  chmod +x zoo/female.sh

  bazel fetch //zoo:breeding-program >& $TEST_log && echo "Expected fetch to fail"
  kill_nc
  expect_log "Checksum"
}

# Bazel should not re-download the .zip unless the user requests it or the
# WORKSPACE file changes, so doing a BUILD where the "wrong" .zip is available
# on the server should work if the correct .zip is already available.
function test_sha256_caching() {
  # Download with correct sha256.
  http_archive_helper zip_up

  echo 'not a zip file' > "${TEST_TMPDIR}/wrong.zip"
  serve_file "${TEST_TMPDIR}/wrong.zip"

  bazel fetch //zoo:breeding-program || fail "Fetch failed"
  bazel run //zoo:breeding-program >& $TEST_log \
    || echo "Expected run to succeed"
  kill_nc
  expect_log $what_does_the_fox_say
}

function test_cached_across_server_restart() {
  http_archive_helper zip_up
  local marker_file=$(bazel info output_base)/external/\@endangered.marker
  echo "<MARKER>"
  cat "${marker_file}"
  echo "</MARKER>"
  bazel shutdown >& $TEST_log || fail "Couldn't shut down"
  bazel run //zoo:breeding-program >& $TEST_log --show_progress_rate_limit=0 \
    || echo "Expected build/run to succeed"
  echo "<MARKER>"
  cat "${marker_file}"
  echo "</MARKER>"
  expect_log $what_does_the_fox_say
  expect_not_log "Downloading from"
}

# Tests downloading a jar and using it as a Java dependency.
function test_jar_download() {
  serve_jar

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_jar")
http_jar(name = 'endangered', url = 'http://127.0.0.1:$nc_port/lib.jar',
         sha256='$sha256', downloaded_file_name="foo.jar")
EOF

  mkdir -p zoo
  cat > zoo/BUILD <<EOF
java_binary(
    name = "ball-pit",
    srcs = ["BallPit.java"],
    main_class = "BallPit",
    deps = ["@endangered//jar"],
)
EOF

  cat > zoo/BallPit.java <<EOF
import carnivore.Mongoose;

public class BallPit {
    public static void main(String args[]) {
        Mongoose.frolic();
    }
}
EOF

  bazel run //zoo:ball-pit >& $TEST_log || echo "Expected run to succeed"
  kill_nc
  expect_log "Tra-la!"
  output_base=$(bazel info output_base)
  jar_dir=$output_base/external/endangered/jar
  [[ -f ${jar_dir}/foo.jar ]] || fail "${jar_dir}/foo.jar not found"
}

function test_http_to_https_redirect() {
  serve_redirect https://127.0.0.1:123456789/bad-port-shouldnt-work

  cd ${WORKSPACE_DIR}
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
http_file(
    name = 'toto',
    urls = ['http://127.0.0.1:$redirect_port/toto'],
    sha256 = '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9826'
)
EOF
  bazel build @toto//file &> $TEST_log && fail "Expected run to fail"
  kill_nc
  # Observes that we tried to follow redirect, but failed due to ridiculous
  # port.
  expect_log "port out of range"
}

function test_http_404() {
  serve_not_found "Help, I'm lost!"

  cd ${WORKSPACE_DIR}
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
http_file(
    name = 'toto',
    urls = ['http://127.0.0.1:$nc_port/toto'],
    sha256 = '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9826'
)
EOF
  bazel build @toto//file &> $TEST_log && fail "Expected run to fail"
  kill_nc
  expect_log "404 Not Found"
}

function test_deferred_download_unwaited() {
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<'EOF'
load("hang.bzl", "hang")

hang(name="hang")
EOF

  cat > hang.bzl <<'EOF'
def _hang_impl(rctx):
  hangs = rctx.download(
    # This URL will definitely not work, but that's OK -- we don't need a
    # successful request for this test
    url = "https://127.0.0.1:0/does_not_exist",
    output = "does_not_exist",
    block = False)

hang = repository_rule(implementation = _hang_impl)
EOF

  touch BUILD
  bazel query @hang//:all >& $TEST_log && fail "Bazel unexpectedly succeeded"
  expect_log "Pending asynchronous work"
}

function test_deferred_download_two_parallel_downloads() {
  local server_dir="${TEST_TMPDIR}/server_dir"
  local gate_socket="${server_dir}/gate_socket"
  local served_apple="APPLE"
  local served_banana="BANANA"
  local apple_sha256=$(echo "${served_apple}" | sha256sum | cut -f1 -d' ')
  local banana_sha256=$(echo "${served_banana}" | sha256sum | cut -f1 -d' ')

  mkdir -p "${server_dir}"

  mkfifo "${server_dir}/apple" || fail "cannot mkfifo"
  mkfifo "${server_dir}/banana" || fail "cannot mkfifo"
  mkfifo $gate_socket || fail "cannot mkfifo"

  startup_server "${server_dir}"

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<'EOF'
load("defer.bzl", "defer")

defer(name="defer")
EOF

  cat > defer.bzl <<EOF
def _defer_impl(rctx):
  requests = [
    ["apple", "${apple_sha256}"],
    ["banana", "${banana_sha256}"]
  ]
  pending = [
    rctx.download(
        url = "http://127.0.0.1:${fileserver_port}/" + name,
        sha256 = sha256,
        output = name,
        block = False)
    for name, sha256 in requests]

  # Tell the rest of the test to unblock the HTTP server
  rctx.execute(["/bin/sh", "-c", "echo ok > ${server_dir}/gate_socket"])

  # Wait until the requess are done
  [p.wait() for p in pending]

  rctx.file("WORKSPACE", "")
  rctx.file("BUILD", "filegroup(name='f', srcs=glob(['**']))")

defer = repository_rule(implementation = _defer_impl)
EOF

  touch BUILD

  # Start Bazel
  bazel query @defer//:all >& $TEST_log &
  local bazel_pid=$!

  # Wait until the .download() calls return
  cat "${server_dir}/gate_socket"

  # Tell the test server the strings it should serve. In parallel because the
  # test server apparently cannot serve two HTTP requests in parallel, so if we
  # wait for request A to be completely served while unblocking request B, it is
  # possible that the test server wants to serve request B first, which is a
  # deadlock.
  echo "${served_apple}" > "${server_dir}/apple" &
  local apple_pid=$!
  echo "${served_banana}" > "${server_dir}/banana" &
  local banana_pid=$!
  wait $apple_pid
  wait $banana_pid

  # Wait until Bazel returns
  wait "${bazel_pid}" || fail "Bazel failed"
  expect_log "@defer//:f"
}

function test_deferred_download_error() {
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<'EOF'
load("defer.bzl", "defer")

defer(name="defer")
EOF

  cat > defer.bzl <<EOF
def _defer_impl(rctx):
  deferred = rctx.download(
    url = "https://127.0.0.1:0/doesnotexist",
    output = "deferred",
    block = False)

  deferred.wait()
  print("survived wait")
  rctx.file("WORKSPACE", "")
  rctx.file("BUILD", "filegroup(name='f', srcs=glob(['**']))")

defer = repository_rule(implementation = _defer_impl)
EOF

  touch BUILD

  # Start Bazel
  bazel query @defer//:all >& $TEST_log && fail "Bazel unexpectedly succeeded"
  expect_log "Error downloading.*doesnotexist"
  expect_not_log "survived wait"
}

function test_deferred_download_smoke() {
  local server_dir="${TEST_TMPDIR}/server_dir"
  local served_socket="${server_dir}/served_socket"
  local gate_socket="${server_dir}/gate_socket"
  local served_string="DEFERRED"
  local served_sha256=$(echo "${served_string}" | sha256sum | cut -f1 -d' ')

  mkdir -p "${server_dir}"

  mkfifo $served_socket || fail "cannot mkfifo"
  mkfifo $gate_socket || fail "cannot mkfifo"

  startup_server "${server_dir}"

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<'EOF'
load("defer.bzl", "defer")

defer(name="defer")
EOF

  cat > defer.bzl <<EOF
def _defer_impl(rctx):
  deferred = rctx.download(
    url = "http://127.0.0.1:${fileserver_port}/served_socket",
    sha256 = "${served_sha256}",
    output = "deferred",
    block = False)

  # Tell the rest of the test to unblock the HTTP server
  rctx.execute(["/bin/sh", "-c", "echo ok > ${server_dir}/gate_socket"])
  deferred.wait()
  rctx.file("WORKSPACE", "")
  rctx.file("BUILD", "filegroup(name='f', srcs=glob(['**']))")

defer = repository_rule(implementation = _defer_impl)
EOF

  touch BUILD

  # Start Bazel
  bazel query @defer//:all-targets >& $TEST_log &
  local bazel_pid=$!

  # Wait until the .download() call returns
  cat "${server_dir}/gate_socket"

  # Tell the test server the string it should serve
  echo "${served_string}" > "${server_dir}/served_socket"

  # Wait until Bazel returns
  wait "${bazel_pid}" || fail "Bazel failed"
  expect_log "@defer//:deferred"
}

# Tests downloading a file and using it as a dependency.
function test_http_download() {
  local test_file=$TEST_TMPDIR/toto
  cat > $test_file <<EOF
#!/bin/sh
echo "Tra-la!"
EOF
  local sha256=$(sha256sum $test_file | cut -f 1 -d ' ')
  serve_file $test_file
  cd ${WORKSPACE_DIR}

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
http_file(name = 'toto', urls = ['http://127.0.0.1:$nc_port/toto'],
    sha256 = '$sha256', executable = True)
EOF

  mkdir -p test
  cat > test/BUILD <<'EOF'
sh_binary(
    name = "test",
    srcs = ["test.sh"],
    data = ["@toto//file"],
)

genrule(
  name = "test_sh",
  outs = ["test.sh"],
  srcs = ["@toto//file"],
  cmd = "echo '#!/bin/sh' > $@ && echo $(rootpath @toto//file) >> $@",
)
EOF

  bazel run //test >& $TEST_log || echo "Expected run to succeed"
  kill_nc
  expect_log "Tra-la!"
}

function test_http_timeout() {
  serve_timeout

  cd ${WORKSPACE_DIR}
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
http_file(
    name = 'toto',
    urls = ['http://127.0.0.1:$nc_port/toto'],
    sha256 = "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b",
)
EOF
  date
  bazel build --http_timeout_scaling=0.03 @toto//file > $TEST_log 2>&1 \
      && fail "Expected failure" || :
  date
  kill_nc

  expect_log '[Tt]imed\? \?out'
  expect_not_log "interrupted"
}

# Tests downloading a file with a redirect.
function test_http_redirect() {
  local test_file=$TEST_TMPDIR/toto
  echo "Tra-la!" >$test_file
  local sha256=$(sha256sum $test_file | cut -f 1 -d ' ')
  serve_file $test_file
  cd ${WORKSPACE_DIR}
  serve_redirect "http://127.0.0.1:$nc_port/toto"

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
http_file(name = 'toto', urls = ['http://127.0.0.1:$redirect_port/toto'],
    sha256 = '$sha256')
EOF

  mkdir -p test
  cat > test/BUILD <<'EOF'
sh_binary(
    name = "test",
    srcs = ["test.sh"],
    data = ["@toto//file"],
)

genrule(
  name = "test_sh",
  outs = ["test.sh"],
  srcs = ["@toto//file"],
  cmd = "echo '#!/bin/sh' > $@ && echo cat $(rootpath @toto//file) >> $@",
)
EOF

  bazel run //test >& $TEST_log || echo "Expected run to succeed"
  kill_nc
  expect_log "Tra-la!"
}

function test_empty_file() {
  rm -f empty
  touch empty
  tar czf x.tar.gz empty
  local sha256=$(sha256sum x.tar.gz | cut -f 1 -d ' ')
  serve_file x.tar.gz

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "x",
    url = "http://127.0.0.1:$nc_port/x.tar.gz",
    sha256 = "$sha256",
    build_file = "@//:x.BUILD",
)
EOF
  cat > x.BUILD <<EOF
exports_files(["empty"])
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "rule",
  srcs = ["@x//:empty"],
  outs = ["timestamp"],
  cmd = "date > $@",
)
EOF

  bazel build //:rule || fail "Build failed"
  bazel shutdown || fail "Shutdown failed"
  cp bazel-genfiles/timestamp first_timestamp || fail "No output"
  sleep 1 # Make sure we're on a different second to avoid false passes.
  bazel build //:rule || fail "Build failed"
  diff bazel-genfiles/timestamp first_timestamp || fail "Output was built again"
}

function test_new_remote_repo_with_build_file() {
  do_new_remote_repo_test "build_file"
}

function test_new_remote_repo_with_build_file_content() {
  do_new_remote_repo_test "build_file_content"
}

function test_new_remote_repo_with_workspace_file() {
  do_new_remote_repo_test "workspace_file"
}

function test_new_remote_repo_with_workspace_file_content() {
  do_new_remote_repo_test "workspace_file_content"
}

function do_new_remote_repo_test() {
  # Create a zipped-up repository HTTP response.
  local repo2=$TEST_TMPDIR/repo2
  rm -rf $repo2
  mkdir -p $repo2/fox
  cd $repo2
  local what_does_the_fox_say="Fraka-kaka-kaka-kaka-kow"
  echo $what_does_the_fox_say > fox/male
  local repo2_zip=$TEST_TMPDIR/fox.zip
  rm -f $repo2_zip
  zip -r $repo2_zip fox
  local sha256=$(sha256sum $repo2_zip | cut -f 1 -d ' ')
  serve_file $repo2_zip

  cd ${WORKSPACE_DIR}

  # Create the build file for the http archive based on the requested attr style.
  local build_file_attr=""
  local workspace_file_attr=""

  local build_file_content="
filegroup(
    name = \"fox\",
    srcs = [\"fox/male\"],
    visibility = [\"//visibility:public\"],
)
  "

  if [ "$1" = "build_file" ] ; then
    touch BUILD
    echo ${build_file_content} > fox.BUILD
    build_file_attr="build_file = '@//:fox.BUILD'"
  else
    build_file_attr="build_file_content=\"\"\"${build_file_content}\"\"\""
  fi

  if [ "$1" = "workspace_file" ]; then
    touch BUILD
    cat > fox.WORKSPACE <<EOF
workspace(name="endangered-fox")
EOF
    workspace_file_attr="workspace_file = '@//:fox.WORKSPACE'"
  elif [ "$1" = "workspace_file_content" ]; then
    workspace_file_attr="workspace_file_content = 'workspace(name=\"endangered-fox\")'"
  fi

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = 'endangered',
    url = 'http://127.0.0.1:$nc_port/repo.zip',
    sha256 = '$sha256',
    ${build_file_attr},
    ${workspace_file_attr}
)
EOF

  mkdir -p zoo
  cat > zoo/BUILD <<EOF
sh_binary(
    name = "breeding-program",
    srcs = ["female.sh"],
    data = ["@endangered//:fox"],
)
EOF

  cat > zoo/female.sh <<EOF
#!/bin/sh
cat ../endangered/fox/male
EOF
  chmod +x zoo/female.sh

  bazel run //zoo:breeding-program >& $TEST_log \
    || echo "Expected build/run to succeed"
  kill_nc
  expect_log $what_does_the_fox_say
}

function test_fetch() {
  serve_jar

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:jvm.bzl", "jvm_maven_import_external")
jvm_maven_import_external(
    name = 'endangered',
    artifact = "com.example.carnivore:carnivore:1.23",
    server_urls = ['http://127.0.0.1:$nc_port/'],
    artifact_sha256 = '$sha256',
)
bind(name = 'mongoose', actual = '@endangered//jar')
EOF

  output_base=$(bazel info output_base)
  external_dir=$output_base/external
  needle=endangered
  [[ -d $external_dir/$needle ]] \
      && fail "$needle already exists in $external_dir" || true
  bazel fetch //zoo:ball-pit >& $TEST_log || fail "Fetch failed"
  [[ $(ls $external_dir | grep $needle) ]] || fail "$needle not added to $external_dir"

  bazel query --output=build --nohost_deps --noimplicit_deps 'deps(//zoo:ball-pit)' >& $TEST_log \
    || fail "bazel query failed"
  expect_log "maven_coordinates=com.example.carnivore:carnivore:1.23"

  # Rerun fetch while nc isn't serving anything to make sure the fetched result
  # is cached.
  bazel fetch //zoo:ball-pit >& $TEST_log || fail "Incremental fetch failed"

  # Make sure fetch isn't needed after a bazel restart.
  bazel shutdown
  bazel build //zoo:ball-pit >& $TEST_log || fail "Fetch shouldn't be required"

  # But it is required after a clean.
  bazel clean --expunge || fail "Clean failed"
  bazel build --fetch=false //zoo:ball-pit >& $TEST_log && fail "Expected build to fail"
  expect_log "fetching repositories is disabled"
}

function test_prefix_stripping_tar_gz() {
  mkdir -p x/y/z
  echo "abc" > x/y/z/w
  tar czf x.tar.gz x
  local sha256=$(sha256sum x.tar.gz | cut -f 1 -d ' ')
  serve_file x.tar.gz

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "x",
    url = "http://127.0.0.1:$nc_port/x.tar.gz",
    sha256 = "$sha256",
    strip_prefix = "x/y/z",
    build_file = "@//:x.BUILD",
)
EOF
  cat > x.BUILD <<EOF
genrule(
    name = "catter",
    cmd = "cat \$< > \$@",
    outs = ["catter.out"],
    srcs = ["w"],
)
EOF
  touch BUILD

  bazel build @x//:catter &> $TEST_log || fail "Build failed"
  assert_contains "abc" bazel-genfiles/external/x/catter.out
}

function test_prefix_stripping_zip() {
  mkdir -p x/y/z
  echo "abc" > x/y/z/w
  zip -r x x
  local sha256=$(sha256sum x.zip | cut -f 1 -d ' ')
  serve_file x.zip

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "x",
    url = "http://127.0.0.1:$nc_port/x.zip",
    sha256 = "$sha256",
    strip_prefix = "x/y/z",
    build_file = "@//:x.BUILD",
)
EOF
  cat > x.BUILD <<EOF
genrule(
    name = "catter",
    cmd = "cat \$< > \$@",
    outs = ["catter.out"],
    srcs = ["w"],
)
EOF
  touch BUILD

  bazel build @x//:catter &> $TEST_log || fail "Build failed"
  assert_contains "abc" bazel-genfiles/external/x/catter.out
}

function test_prefix_stripping_existing_repo() {
  mkdir -p x/y/z
  touch x/y/z/WORKSPACE
  echo "abc" > x/y/z/w
  cat > x/y/z/BUILD <<EOF
genrule(
    name = "catter",
    cmd = "cat \$< > \$@",
    outs = ["catter.out"],
    srcs = ["w"],
)
EOF
  zip -r x x
  local sha256=$(sha256sum x.zip | cut -f 1 -d ' ')
  serve_file x.zip

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "x",
    url = "http://127.0.0.1:$nc_port/x.zip",
    sha256 = "$sha256",
    strip_prefix = "x/y/z",
)
EOF

  bazel build @x//:catter &> $TEST_log || fail "Build failed"
  assert_contains "abc" bazel-genfiles/external/x/catter.out
}

function test_adding_prefix_zip() {
  mkdir -p z
  echo "abc" > z/w
  zip -r z z
  local sha256=$(sha256sum z.zip | cut -f 1 -d ' ')
  serve_file z.zip

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "ws",
    url = "http://127.0.0.1:$nc_port/z.zip",
    sha256 = "$sha256",
    add_prefix = "x/y",
    build_file = "@//:ws.BUILD",
)
EOF
  cat > ws.BUILD <<EOF
genrule(
    name = "catter",
    cmd = "cat \$< > \$@",
    outs = ["catter.out"],
    srcs = ["x/y/z/w"],
)
EOF
  touch BUILD

  bazel build @ws//:catter &> $TEST_log || fail "Build failed"
  assert_contains "abc" bazel-genfiles/external/ws/catter.out
}

function test_adding_and_stripping_prefix_zip() {
  mkdir -p z
  echo "abc" > z/w
  zip -r z z
  local sha256=$(sha256sum z.zip | cut -f 1 -d ' ')
  serve_file z.zip

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "ws",
    url = "http://127.0.0.1:$nc_port/z.zip",
    sha256 = "$sha256",
    strip_prefix = "z",
    add_prefix = "x",
    build_file = "@//:ws.BUILD",
)
EOF
  cat > ws.BUILD <<EOF
genrule(
    name = "catter",
    cmd = "cat \$< > \$@",
    outs = ["catter.out"],
    srcs = ["x/w"],
)
EOF
  touch BUILD

  bazel build @ws//:catter &> $TEST_log || fail "Build failed"
  assert_contains "abc" bazel-genfiles/external/ws/catter.out
}

function test_moving_build_file() {
  echo "abc" > w
  tar czf x.tar.gz w
  local sha256=$(sha256sum x.tar.gz | cut -f 1 -d ' ')
  serve_file x.tar.gz

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "x",
    url = "http://127.0.0.1:$nc_port/x.tar.gz",
    sha256 = "$sha256",
    build_file = "@//:x.BUILD",
)
EOF
  touch BUILD
  cat > x.BUILD <<EOF
genrule(
    name = "catter",
    cmd = "cat \$< > \$@",
    outs = ["catter.out"],
    srcs = ["w"],
)
EOF

  bazel build @x//:catter &> $TEST_log || fail "Build 1 failed"
  assert_contains "abc" bazel-genfiles/external/x/catter.out
  mv x.BUILD x.BUILD.new || fail "Moving x.BUILD failed"
  sed 's/x.BUILD/x.BUILD.new/g' WORKSPACE > WORKSPACE.tmp || \
    fail "Editing WORKSPACE failed"
  mv WORKSPACE.tmp WORKSPACE
  bazel build @x//:catter &> $TEST_log || fail "Build 2 failed"
  assert_contains "abc" bazel-genfiles/external/x/catter.out
}

function test_changing_build_file() {
  echo "abc" > w
  echo "def" > w.new
  echo "I'm a build file" > BUILD
  tar czf x.tar.gz w w.new BUILD
  local sha256=$(sha256sum x.tar.gz | cut -f 1 -d ' ')
  serve_file x.tar.gz

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "x",
    url = "http://127.0.0.1:$nc_port/x.tar.gz",
    sha256 = "$sha256",
    build_file = "@//:x.BUILD",
)
EOF
  cat > x.BUILD <<EOF
genrule(
    name = "catter",
    cmd = "cat \$< > \$@",
    outs = ["catter.out"],
    srcs = ["w"],
)
EOF

  cat > x.BUILD.new <<EOF
genrule(
    name = "catter",
    cmd = "cat \$< > \$@",
    outs = ["catter.out"],
    srcs = ["w.new"],
)
EOF

  bazel build @x//:catter || fail "Build 1 failed"
  assert_contains "abc" bazel-genfiles/external/x/catter.out
  sed 's/x.BUILD/x.BUILD.new/g' WORKSPACE > WORKSPACE.tmp || \
    fail "Editing WORKSPACE failed"
  mv WORKSPACE.tmp WORKSPACE
  bazel build @x//:catter &> $TEST_log || fail "Build 2 failed"
  assert_contains "def" bazel-genfiles/external/x/catter.out
}

function test_android_sdk_basic_load() {
  cat >> WORKSPACE <<'EOF' || fail "Couldn't cat"
android_sdk_repository(
    name = "androidsdk",
    path = "/fake/path",
    api_level = 23,
    build_tools_version="23.0.0"
)
EOF

  bazel query "//external:androidsdk" 2> "$TEST_log" > "$TEST_TMPDIR/queryout" \
      || fail "Expected success"
  cat "$TEST_TMPDIR/queryout" > "$TEST_log"
  expect_log "//external:androidsdk"
}

function test_use_bind_as_repository() {
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<'EOF'
local_repository(name = 'foobar', path = 'foo')
bind(name = 'foo', actual = '@foobar//:test')
EOF
  mkdir foo
  touch foo/WORKSPACE
  touch foo/test
  echo 'exports_files(["test"])' > foo/BUILD
  cat > BUILD <<'EOF'
genrule(
    name = "foo",
    srcs = ["@foo//:test"],
    cmd = "echo $< | tee $@",
    outs = ["foo.txt"],
)
EOF
  bazel build :foo &> "$TEST_log" && fail "Expected failure" || true
  expect_log "No repository visible as '@foo' from main repository"
}

function test_bind_repo_mapping() {
  cat >> $(create_workspace_with_default_repos WORKSPACE myws) <<'EOF'
load('//:foo.bzl', 'foo')
foo()
bind(name='bar', actual='@myws//:something')
EOF
  cat > foo.bzl <<'EOF'
def foo():
  native.bind(name='foo', actual='@myws//:something')
EOF
  cat > BUILD <<'EOF'
filegroup(name='something', visibility=["//visibility:public"])
EOF
  bazel build //external:foo //external:bar &> "$TEST_log" || fail "don't fail!"
}

function test_flip_flopping() {
  REPO_PATH=$TEST_TMPDIR/repo
  mkdir -p "$REPO_PATH"
  cd "$REPO_PATH"
  create_workspace_with_default_repos WORKSPACE
  touch BUILD foo
  zip -r repo.zip *
  sha256=$(sha256sum repo.zip | head -c 64)
  startup_server $PWD
  # Make the remote repo and local repo slightly different.
  rm foo
  touch bar
  cd -

  cat > local_ws <<EOF
local_repository(
    name = "repo",
    path = "$REPO_PATH",
)
EOF
  cat > remote_ws <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "repo",
    url = "http://127.0.0.1:$fileserver_port/repo.zip",
    sha256 = "$sha256",
)
EOF
  external_dir=$(bazel info output_base)/external
  for i in $(seq 1 3); do
    cp local_ws WORKSPACE
    bazel build @repo//:all &> $TEST_log || fail "Build failed"
    test -L "$external_dir/repo" || fail "creating local symlink failed"
    test -a "$external_dir/repo/bar" || fail "bar not found"
    cp remote_ws WORKSPACE
    bazel build @repo//:all &> $TEST_log || fail "Build failed"
    test -d "$external_dir//repo" || fail "creating remote repo failed"
    test -a "$external_dir/repo/foo" || fail "foo not found"
  done

  shutdown_server
}

function test_sha256_weird() {
  REPO_PATH=$TEST_TMPDIR/repo
  mkdir -p "$REPO_PATH"
  cd "$REPO_PATH"
  create_workspace_with_default_repos WORKSPACE
  zip -r repo.zip *
  startup_server $PWD
  cd -

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "repo",
    sha256 = "a random string",
    url = "http://127.0.0.1:$fileserver_port/repo.zip",
)
EOF
  bazel build @repo//... &> $TEST_log && fail "Expected to fail"
  expect_log "[Ii]nvalid SHA-256 checksum"
  shutdown_server
}

function test_sha256_incorrect() {
  REPO_PATH=$TEST_TMPDIR/repo
  mkdir -p "$REPO_PATH"
  cd "$REPO_PATH"
  create_workspace_with_default_repos WORKSPACE
  zip -r repo.zip *
  startup_server $PWD
  cd -

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "repo",
    sha256 = "61a6f762aaf60652cbf332879b8dcc2cfd81be2129a061da957d039eae77f0b0",
    url = "http://127.0.0.1:$fileserver_port/repo.zip",
)
EOF
  bazel build @repo//... &> $TEST_log 2>&1 && fail "Expected to fail"
  expect_log "Error downloading \\[http://127.0.0.1:$fileserver_port/repo.zip\\] to"
  expect_log "but wanted 61a6f762aaf60652cbf332879b8dcc2cfd81be2129a061da957d039eae77f0b0"
  shutdown_server
}

function test_integrity_correct() {
  REPO_PATH=$TEST_TMPDIR/repo
  mkdir -p "$REPO_PATH"
  cd "$REPO_PATH"
  create_workspace_with_default_repos WORKSPACE
  touch BUILD
  zip -r repo.zip *
  integrity="sha256-$(cat repo.zip | openssl dgst -sha256 -binary | openssl base64 -A)"
  startup_server $PWD
  cd -

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "repo",
    integrity = "$integrity",
    url = "http://127.0.0.1:$fileserver_port/repo.zip",
)
EOF
  bazel build @repo//... || fail "Expected integrity check to succeed"
  shutdown_server
}

function test_integrity_weird() {
  REPO_PATH=$TEST_TMPDIR/repo
  mkdir -p "$REPO_PATH"
  cd "$REPO_PATH"
  create_workspace_with_default_repos WORKSPACE
  touch BUILD
  zip -r repo.zip *
  startup_server $PWD
  cd -

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "repo",
    integrity = "a random string",
    url = "http://127.0.0.1:$fileserver_port/repo.zip",
)
EOF
  bazel build @repo//... &> $TEST_log 2>&1 && fail "Expected to fail"
  expect_log "Unsupported checksum algorithm: 'a random string'"
  shutdown_server
}

function test_integrity_incorrect() {
  REPO_PATH=$TEST_TMPDIR/repo
  mkdir -p "$REPO_PATH"
  cd "$REPO_PATH"
  create_workspace_with_default_repos WORKSPACE
  touch BUILD
  zip -r repo.zip *
  integrity="sha256-$(cat repo.zip | openssl dgst -sha256 -binary | openssl base64 -A)"
  startup_server $PWD
  cd -

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "repo",
    integrity = "sha256-Yab3Yqr2BlLL8zKHm43MLP2BviEpoGHalX0Dnq538LA=",
    url = "http://127.0.0.1:$fileserver_port/repo.zip",
)
EOF
  bazel build @repo//... &> $TEST_log 2>&1 && fail "Expected to fail"
  expect_log "Error downloading \\[http://127.0.0.1:$fileserver_port/repo.zip\\] to"
  # Bazel translates the integrity value back to the sha256 checksum.
  expect_log "Checksum was $integrity but wanted sha256-Yab3Yqr2BlLL8zKHm43MLP2BviEpoGHalX0Dnq538LA="
  shutdown_server
}

function test_same_name() {
  mkdir ext
  echo foo> ext/foo
  EXTREPODIR=`pwd`
  zip ext.zip ext/*
  rm -rf ext

  rm -rf main
  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  url="file://${EXTREPODIR}/ext.zip",
  build_file_content="exports_files([\"foo\"])",
)
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "localfoo",
  srcs = ["@ext//:foo"],
  outs = ["foo"],
  cmd = "cp $< $@",
)
EOF

  bazel build //:localfoo \
    || fail 'Expected @ext//:foo and //:foo not to conflict'
}

function test_missing_build() {
  mkdir ext
  echo foo> ext/foo
  EXTREPODIR=`pwd`
  rm -f ext.zip
  zip ext.zip ext/*
  rm -rf ext

  rm -rf main
  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["file://${EXTREPODIR}/ext.zip"],
  build_file="@//:ext.BUILD",
)
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "localfoo",
  srcs = ["@ext//:foo"],
  outs = ["foo"],
  cmd = "cp $< $@",
)
EOF
  bazel build //:localfoo && fail 'Expected failure' || :

  cat > ext.BUILD <<'EOF'
exports_files(["foo"])
EOF

  bazel build //:localfoo || fail 'Expected success'

  # Changes to the BUILD file in the external repository should be tracked
  rm -f ext.BUILD && touch ext.BUILD

  bazel build //:localfoo && fail 'Expected failure' || :

  # Verify that we don't call out unconditionally
  cat > ext.BUILD <<'EOF'
exports_files(["foo"])
EOF
  bazel build //:localfoo || fail 'Expected success'
  rm -f "${EXTREPODIR}/ext.zip"
  bazel build //:localfoo || fail 'Expected success'
}


function test_inherit_build() {
  # Verify that http_archive can use a BUILD file shipped with the
  # external archive.
  mkdir ext
  cat > ext/BUILD <<'EOF'
genrule(
  name="hello",
  outs=["hello.txt"],
  cmd="echo Hello World > $@",
)
EOF
  EXTREPODIR=`pwd`
  rm -f ext.zip
  zip ext.zip ext/*
  rm -rf ext

  rm -rf main
  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["file://${EXTREPODIR}/ext.zip"],
)
EOF

  bazel build '@ext//:hello' || fail "expected success"
}

function test_failing_fetch_with_keep_going() {
  create_workspace_with_default_repos WORKSPACE
  cat > BUILD <<'EOF'
package(default_visibility = ["//visibility:public"])

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = ["@fake//:fake"],
)
EOF
  touch hello-world.cc

  bazel fetch --keep_going //... >& $TEST_log && fail "Expected to fail" || true
}


function test_query_cached() {
  # Verify that external repositories are cached after being used once.
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"
  mkdir ext
  cat > ext/BUILD <<'EOF'
genrule(
  name="foo",
  outs=["foo.txt"],
  cmd="echo Hello World > $@",
)
genrule(
  name="bar",
  outs=["bar.txt"],
  srcs=[":foo"],
  cmd="cp $< $@",
)
EOF
  EXTREPODIR=`pwd`
  rm -f ext.zip
  zip ext.zip ext/*
  rm -rf ext

  rm -rf main
  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["file://${EXTREPODIR}/ext.zip"],
)
EOF
  bazel build '@ext//:bar' || fail "expected success"

  # Simulate going offline by removing the external archive
  rm -f "${EXTREPODIR}/ext.zip"
  bazel query 'deps("@ext//:bar")' > "${TEST_log}" 2>&1 \
    || fail "expected success"
  expect_log '@ext//:foo'
  bazel shutdown
  bazel query 'deps("@ext//:bar")' > "${TEST_log}" 2>&1 \
    || fail "expected success"
  expect_log '@ext//:foo'
}

function test_repository_cache_relative_path() {
  # Verify that --repository_cache works for query and caches soly
  # based on the predicted hash, for a repository-cache location given as path
  # relative to the WORKSPACE
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"
  mkdir ext
  cat > ext/BUILD <<'EOF'
genrule(
  name="foo",
  outs=["foo.txt"],
  cmd="echo Hello World > $@",
)
genrule(
  name="bar",
  outs=["bar.txt"],
  srcs=[":foo"],
  cmd="cp $< $@",
)
EOF
  zip ext.zip ext/*
  rm -rf ext
  sha256=$(sha256sum ext.zip | head -c 64)

  rm -rf cache
  mkdir cache

  rm -rf main
  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["file://${WRKDIR}/ext.zip"],
  sha256="${sha256}",
)
EOF
  # Use the external repository once to make sure it is cached.
  bazel build --repository_cache="../cache" '@ext//:bar' \
      || fail "expected success"

  # Now "go offline" and clean local resources.
  rm -f "${WRKDIR}/ext.zip"
  bazel clean --expunge
  bazel query 'deps("@ext//:bar")' && fail "Couldn't clean local cache" || :

  # The value should still be available from the repository cache
  bazel query 'deps("@ext//:bar")' \
        --repository_cache="../cache" > "${TEST_log}" \
      || fail "Expected success"
  expect_log '@ext//:foo'

  # Clean again.
  bazel clean --expunge
  # Even with a different source URL, the cache should be consulted.

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["http://doesnotexist.example.com/invalidpath/othername.zip"],
  sha256="${sha256}",
)
EOF
  bazel query 'deps("@ext//:bar")' \
        --repository_cache="../cache" > "${TEST_log}" \
      || fail "Expected success"
  expect_log '@ext//:foo'
}

test_default_cache()
{
  # Verify that the default cache works for query and caches soly
  # based on the predicted hash, for a repository-cache location given as path
  # relative to the WORKSPACE
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"
  mkdir ext
  cat > ext/BUILD <<'EOF'
genrule(
  name="foo",
  outs=["foo.txt"],
  cmd="echo Hello World > $@",
)
genrule(
  name="bar",
  outs=["bar.txt"],
  srcs=[":foo"],
  cmd="cp $< $@",
)
EOF
  zip ext.zip ext/*
  rm -rf ext
  sha256=$(sha256sum ext.zip | head -c 64)

  rm -rf main
  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["file://${WRKDIR}/ext.zip"],
  sha256="${sha256}",
)
EOF
  # Use the external repository once to make sure it is cached.
  bazel build '@ext//:bar' || fail "expected success"

  # Now "go offline" and clean local resources.
  rm -f "${WRKDIR}/ext.zip"
  bazel clean --expunge

  # The value should still be available from the repository cache
  bazel query 'deps("@ext//:bar")' > "${TEST_log}" || fail "Expected success"
  expect_log '@ext//:foo'

  # Clean again.
  bazel clean --expunge
  # Even with a different source URL, the cache should be consulted.

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["http://doesnotexist.example.com/invalidpath/othername.zip"],
  sha256="${sha256}",
)
EOF
  bazel query 'deps("@ext//:bar")' > "${TEST_log}" || fail "Expected success"
  expect_log '@ext//:foo'
}

function test_repository_cache_default() {
  # Verify that the repository cache is enabled by default.
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"
  mkdir ext
  cat > ext/BUILD <<'EOF'
genrule(
  name="foo",
  outs=["foo.txt"],
  cmd="echo Hello World > $@",
  visibility = ["//visibility:public"],
)
EOF
  TOPDIR=`pwd`
  zip ext.zip ext/*
  rm -rf ext
  sha256=$(sha256sum ext.zip | head -c 64)

  rm -rf main
  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["file://${TOPDIR}/ext.zip"],
  sha256="${sha256}",
)
EOF
  # Use the external repository once to make sure it is cached.
  bazel build '@ext//:foo' || fail "expected success"

  # Now "go offline" and clean local resources.
  rm -f "${TOPDIR}/ext.zip"
  bazel clean --expunge

  # Still, the file should be cached.
  bazel build '@ext//:foo' || fail "expected success"
}

function test_cache_split() {
  # Verify that the canonical_id is honored to logically split the cache
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"
  mkdir ext
  cat > ext/BUILD <<'EOF'
genrule(
  name="foo",
  outs=["foo.txt"],
  cmd="echo Hello World > $@",
  visibility = ["//visibility:public"],
)
EOF
  zip ext.zip ext/*
  rm -rf ext
  sha256=$(sha256sum ext.zip | head -c 64)

  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["file://${WRKDIR}/ext.zip"],
  canonical_id = "canonical_ext_zip",
  sha256="${sha256}",
)
EOF
  # Use the external repository once to make sure it is cached.
  bazel build '@ext//:foo' || fail "expected success"

  # Now "go offline" and clean local resources.
  rm -f "${WRKDIR}/ext.zip"
  bazel clean --expunge

  # Still, the file should be cached.
  bazel build '@ext//:foo' || fail "expected success"

  # Now, change the canonical_id
  ed WORKSPACE <<'EOF'
/canonical_id
s|"|"modified_
w
q
EOF
  # The build should fail now
  bazel build '@ext//:foo' && fail "should not have a cache hit now" || :

  # However, removing the canonical_id, we should get a cache hit again
  ed WORKSPACE <<'EOF'
/canonical_id
d
w
q
EOF
  bazel build '@ext//:foo' || fail "expected success"
}

function test_cache_disable {
  # Verify that the repository cache can be disabled.
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"
  mkdir ext
  cat > ext/BUILD <<'EOF'
genrule(
  name="foo",
  outs=["foo.txt"],
  cmd="echo Hello World > $@",
  visibility = ["//visibility:public"],
)
EOF
  TOPDIR=`pwd`
  zip ext.zip ext/*
  rm -rf ext
  sha256=$(sha256sum ext.zip | head -c 64)

  rm -rf main
  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["file://${TOPDIR}/ext.zip"],
  sha256="${sha256}",
)
EOF

  # Prime the repository cache.
  bazel build '@ext//:foo' || fail "expected success"

  # Use `--repository_cache` with no path to explicitly disable repository cache
  bazel build --repository_cache= '@ext//:foo' || fail "expected success"

  # make sure, the empty path is not interpreted relative to `pwd`; i.e., we do
  # not expect any new directories generated in the workspace, in particular
  # none named conent_addressable, which is the directory where the cache puts
  # its artifacts into.
  ls -al | grep content_addressable \
      && fail "Should not interpret empty path as cache directly in the work space" || :

  # Now "go offline" and clean local resources.
  rm -f "${TOPDIR}/ext.zip"
  bazel clean --expunge

  # Do a noop build with the cache enabled to ensure the cache can be disabled
  # after the server starts.
  bazel build

  # The build should fail since we are not using the repository cache, but the
  # original file can no longer be "downloaded".
  bazel build --repository_cache= '@ext//:foo' \
      && fail "Should fail for lack of fetchable archive" || :
}

function test_repository_cache() {
  # Verify that --repository_cache works for query and caches soly
  # based on the predicted hash.
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"
  mkdir ext
  cat > ext/BUILD <<'EOF'
genrule(
  name="foo",
  outs=["foo.txt"],
  cmd="echo Hello World > $@",
)
genrule(
  name="bar",
  outs=["bar.txt"],
  srcs=[":foo"],
  cmd="cp $< $@",
)
EOF
  TOPDIR=`pwd`
  zip ext.zip ext/*
  rm -rf ext
  sha256=$(sha256sum ext.zip | head -c 64)

  rm -rf cache
  mkdir cache

  rm -rf main
  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["file://${TOPDIR}/ext.zip"],
  sha256="${sha256}",
)
EOF
  # Use the external repository once to make sure it is cached.
  bazel build --repository_cache="${TOPDIR}/cache}" '@ext//:bar' \
      || fail "expected success"

  # Now "go offline" and clean local resources.
  rm -f "${TOPDIR}/ext.zip"
  bazel clean --expunge
  bazel query 'deps("@ext//:bar")' && fail "Couldn't clean local cache" || :

  # The value should still be available from the repository cache
  bazel query 'deps("@ext//:bar")' \
        --repository_cache="${TOPDIR}/cache}" > "${TEST_log}" \
      || fail "Expected success"
  expect_log '@ext//:foo'

  # Clean again.
  bazel clean --expunge
  # Even with a different source URL, the cache should be consulted.

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["http://doesnotexist.example.com/invalidpath/othername.zip"],
  sha256="${sha256}",
)
EOF
  bazel query 'deps("@ext//:bar")' \
        --repository_cache="${TOPDIR}/cache}" > "${TEST_log}" \
      || fail "Expected success"
  expect_log '@ext//:foo'
}

function test_cache_probe() {
  # Verify that repository rules are able to probe for cache hits.
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"

  mkdir ext
  cat > ext/BUILD <<'EOF'
genrule(
  name = "ext",
  outs = ["ext.txt"],
  cmd = "echo True external file > $@",
  visibility = ["//visibility:public"],
)
EOF
  zip ext.zip ext/*
  rm -rf ext
  sha256=$(sha256sum ext.zip | head -c 64)

  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("//:rule.bzl", "probe")

http_archive(
  name = "ext",
  url = "file://${WRKDIR}/ext.zip",
  strip_prefix="ext",
)

probe(
  name = "probe",
  sha256 = "${sha256}",
)
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "it",
  outs = ["it.txt"],
  srcs = ["@probe//:ext"],
  cmd = "cp $< $@",
)
EOF
  cat > rule.bzl <<'EOF'
def _rule_impl(ctx):
  result = ctx.download_and_extract(
    url = [],
    type = "zip",
    stripPrefix="ext",
    sha256 = ctx.attr.sha256,
    allow_fail = True,
  )
  if not result.success:
    # provide default implementation; a real probe
    # should ask for credentials here and then download
    # the actual (restricted) file.
    ctx.file(
      "BUILD",
      "genrule(name='ext', outs = ['ext.txt'], cmd = 'echo no cache hit > $@', "
      + "visibility = ['//visibility:public'],)" ,
    )

probe = repository_rule(
  implementation = _rule_impl,
  attrs = {"sha256" : attr.string()},
  environ = ["ATTEMPT"],
)
EOF

  echo; echo initial build; echo
  # initially, no cache hit, should show default
  env ATTEMPT=1 bazel build //:it
  grep -q 'no cache hit' `bazel info bazel-genfiles`/it.txt \
      || fail "didn't find the default implementation"

  echo; echo build of ext; echo
  # ensure the cache is filled
  bazel build @ext//...

  echo; echo build with cache hit; echo
  # now we should get the real external dependency
  env ATTEMPT=2 bazel build //:it
  grep -q 'True external file' `bazel info bazel-genfiles`/it.txt \
      || fail "didn't find the default implementation"
}

function test_cache_hit_reported() {
  # Verify that information about a cache hit is reported
  # if an error happened in that repository. This information
  # is useful as users sometimes change the URL but do not
  # update the hash.
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"
  mkdir ext-1.1
  cat > ext-1.1/BUILD <<'EOF'
genrule(
  name="foo",
  outs=["foo.txt"],
  cmd="echo Hello World > $@",
  visibility = ["//visibility:public"],
)
EOF
  zip ext-1.1.zip ext-1.1/*
  rm -rf ext-1.1
  sha256=$(sha256sum ext-1.1.zip | head -c 64)

  rm -rf main
  mkdir main
  cd main
  cat > WORKSPACE <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext-1.1",
  urls=["file://${WRKDIR}/ext-1.1.zip"],
  sha256="${sha256}",
)
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "it",
  srcs = ["@ext//:foo"],
  outs = ["it.txt"],
  cmd = "cp $< $@"
)
EOF

  echo "Build #1"

  # build to fill the cache
  bazel build //:it || fail "Expected success"

  # go offline and clean everything
  bazel clean --expunge
  rm "${WRKDIR}/ext-1.1.zip"

  echo "Build #2"

  # We still should be able to build, as the file is in cache
  bazel build //:it > "${TEST_log}" 2>&1 || fail "Expected success"
  # As a cache hit is a perfectly normal thing, we don't expect it to be
  # reported.
  expect_not_log 'cache hit'
  expect_not_log "${sha256}"
  expect_not_log 'file:.*/ext-1.1.zip'

  # Now update ext-1.1 to ext-1.2, while forgetting to update the checksum
  ed WORKSPACE <<EOI
%s/ext-1\.1/ext-1\.2/g
w
q
EOI

  echo "Build #3"

  # The build should fail, as the prefix is not found. The available prefix
  # should be reported as well as the information that the file was taken
  # from cache.
  bazel build //:it > "${TEST_log}" 2>&1 && fail "Should not succeed" || :
  expect_log 'ext-1.2.*not found'
  expect_log 'prefixes.*ext-1.1'
  expect_log 'cache hit'
  expect_log "${sha256}"
  expect_log 'file:.*/ext-1.2.zip'

  # Now consider the case where no prefix is specified (and hence, the
  # download_and_extract call will succeed), but a patch command has
  # an assumption on a wrong path. As the fetching of the external
  # repository will fail, we still expect being hinted at the
  # cache hit.
  ed WORKSPACE <<'EOI'
/strip_prefix
d
a
patch_cmds = ["cp ext-1.2/foo.txt ext-1.2/BUILD ."],
.
w
q
EOI

  echo "Build #4"

  bazel build //:it > "${TEST_log}" 2>&1 && fail "Should not succeed" || :
  expect_not_log 'prefix'
  expect_log 'cp ext-1.2/foo.txt ext-1.2/BUILD'
  expect_log 'cache hit'
  expect_log "${sha256}"
  expect_log 'file:.*/ext-1.2.zip'
}

function test_distdir() {
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"
  mkdir ext
  cat > ext/BUILD <<'EOF'
genrule(
  name="foo",
  outs=["foo.txt"],
  cmd="echo Hello World > $@",
  visibility = ["//visibility:public"],
)
EOF
  zip ext.zip ext/*
  rm -rf ext
  sha256=$(sha256sum ext.zip | head -c 64)

  mkdir distfiles
  mv ext.zip distfiles

  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["http://doesnotexist.example.com/outdatedpath/ext.zip"],
  sha256="${sha256}",
)
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "local",
  srcs = ["@ext//:foo"],
  outs = ["local.txt"],
  cmd = "cp $< $@",
)
EOF

  bazel clean --expunge
  bazel build --distdir="${WRKDIR}/distfiles" //:local \
    || fail "expected success"
}


function test_distdir_outputname() {
  # Verify that distdir searches at least for the local part of the URL,
  # even if the output is renamed.
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"
  date +%s > actual_file_name.txt
  sha256=$(sha256sum actual_file_name.txt | head -c 64)

  mkdir distfiles
  mv actual_file_name.txt distfiles

  mkdir main
  cd main
  cat > ext_file.bzl <<'EOF'
def _impl(ctx):
  ctx.download(
    url = ctx.attr.urls,
    output = 'foo',
    sha256 = ctx.attr.sha256,
  )
  ctx.file(
    "BUILD",
    "exports_files(['foo'], visibility=['//visibility:public'])",
  )

ext_file = repository_rule(
  implementation = _impl,
  attrs = { "urls" : attr.string_list(), "sha256" : attr.string() },
)
EOF
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("//:ext_file.bzl", "ext_file")
ext_file(
  name="ext",
  urls=["http://doesnotexist.example.com/outdatedpath/actual_file_name.txt"],
  sha256="${sha256}",
)
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "local",
  srcs = ["@ext//:foo"],
  outs = ["local.txt"],
  cmd = "cp $< $@",
)
EOF

  bazel clean --expunge
  bazel build --distdir="${WRKDIR}/distfiles" //:local \
    || fail "expected success"
}

function test_distdir_relative_path() {
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"
  mkdir ext
  cat > ext/BUILD <<'EOF'
genrule(
  name="foo",
  outs=["foo.txt"],
  cmd="echo Hello World > $@",
  visibility = ["//visibility:public"],
)
EOF
  zip ext.zip ext/*
  rm -rf ext
  sha256=$(sha256sum ext.zip | head -c 64)

  mkdir distfiles
  mv ext.zip distfiles

  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["http://doesnotexist.example.com/outdatedpath/ext.zip"],
  sha256="${sha256}",
)
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "local",
  srcs = ["@ext//:foo"],
  outs = ["local.txt"],
  cmd = "cp $< $@",
)
EOF

  bazel clean --expunge
  bazel build --distdir="../distfiles" //:local \
    || fail "expected success"
}

function test_distdir_non_existent() {
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"
  mkdir ext
  cat > ext/BUILD <<'EOF'
genrule(
  name="foo",
  outs=["foo.txt"],
  cmd="echo Hello World > $@",
  visibility = ["//visibility:public"],
)
EOF
  zip ext.zip ext/*
  rm -rf ext
  sha256=$(sha256sum ext.zip | head -c 64)

  touch thisisafile
  mkdir thisisempty

  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["file://${WRKDIR}/ext.zip"],
  sha256="${sha256}",
)
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "local",
  srcs = ["@ext//:foo"],
  outs = ["local.txt"],
  cmd = "cp $< $@",
)
EOF

  bazel clean --expunge
  # The local distdirs all do no provide the file; still, it should work by fetching
  # the file from upstream.
  bazel build --distdir=does/not/exist --distdir=/global/does/not/exist --distdir=../thisisafile --distdir=../thisisempty //:local \
    || fail "expected success"
}

function test_good_symlinks() {
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"

  mkdir -p ext/subdir
  echo foo > ext/file.txt
  ln -s ../file.txt ext/subdir/symlink.txt
  ls -alR ext
  zip -r --symlinks ext.zip ext

  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["file://${WRKDIR}/ext.zip"],
  build_file="@//:ext.BUILD"
)
EOF
  cat > ext.BUILD <<'EOF'
exports_files(["subdir/symlink.txt"])
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "local",
  srcs = ["@ext//:subdir/symlink.txt"],
  outs = ["local.txt"],
  cmd = "cp $< $@",
)
EOF

  bazel build //:local || fail "Expected success"
}

function test_distdir_option_not_sticky() {
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"
  mkdir ext
  cat > ext/BUILD <<'EOF'
genrule(
  name="foo",
  outs=["foo.txt"],
  cmd="echo Hello World > $@",
  visibility = ["//visibility:public"],
)
EOF
  zip ext.zip ext/*
  rm -rf ext
  sha256=$(sha256sum ext.zip | head -c 64)

  mkdir distfiles
  mv ext.zip distfiles

  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["http://doesnotexist.example.com/outdatedpath/ext.zip"],
  sha256="${sha256}",
)
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "local",
  srcs = ["@ext//:foo"],
  outs = ["local.txt"],
  cmd = "cp $< $@",
)

genrule(
  name = "unrelated",
  outs = ["unrelated.txt"],
  cmd = "echo Something > $@",
)
EOF

  bazel clean --expunge
  bazel build --distdir="../distfiles" //:unrelated \
    || fail "expected success"
  # As no --distdir option is given and upstream not available,
  # we expect the build to fail
  bazel build //:local && fail "Expected failure" || :
}

function test_bad_symlinks() {
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"

  mkdir -p ext/subdir
  echo foo > ext/file.txt
  ln -s ../file.txt ext/symlink.txt
  ls -alR ext
  zip -r --symlinks ext.zip ext

  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["file://${WRKDIR}/ext.zip"],
  build_file="@//:ext.BUILD"
)
EOF
  cat > ext.BUILD <<'EOF'
exports_files(["file.txt"])
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "local",
  srcs = ["@ext//:file.txt"],
  outs = ["local.txt"],
  cmd = "cp $< $@",
)
EOF

  bazel build //:local \
    && fail "Expected failure due to unsupported symlink" || :
}

function test_progress_reporting() {
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"

  cat > rule.bzl <<'EOF'
def _rule_impl(ctx):
  ctx.report_progress("First action")
  ctx.execute(["/bin/sh", "-c", "sleep 5"])
  ctx.report_progress("Second action")
  ctx.execute(["/bin/sh", "-c", "sleep 5"])
  ctx.report_progress("Actual files")
  ctx.file("data", "Hello world")
  ctx.file("BUILD", "exports_files(['data'])")

with_progress = repository_rule(
  implementation = _rule_impl,
  attrs = {},
)
EOF

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<'EOF'
load("//:rule.bzl", "with_progress")
with_progress(name="foo")
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "local",
  srcs = ["@foo//:data"],
  outs = ["local.txt"],
  cmd = "cp $< $@",
)
EOF
  bazel build --curses=yes //:local > "${TEST_log}" 2>&1 \
      || fail "expected success"
  expect_log "foo.*First action"
  expect_log "foo.*Second action"
}

function test_progress_reporting() {
  # Isse 7353 requested that even in the case of a syntactically invalid
  # checksum, the file still should be fetched and its checksum computed.
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"

  touch ext.tar

  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name = "ext",
  urls = ["file://${WRKDIR}/ext.tar"],
  sha256 = "badargument",
)
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "it",
  srcs = ["@ext//:in.txt"],
  outs = ["out.txt"],
  cmd = "cp $< $@",
)
EOF

  bazel build //:it > "${TEST_log}" 2>&1 && fail "Expected failure" || :

  expect_log '@ext.*badargument'
}

function test_prefix_suggestions() {
  # Verify that useful suggestions are made if an expected prefix
  # is not found in an archive.
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"

  mkdir -p ext-1.1/foo
  touch ext-1.1/foo.txt

  tar cvf ext.tar ext-1.1
  rm -rf ext-1

  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext-1.0",
  urls=["file://${WRKDIR}/ext.tar"],
)
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "it",
  srcs = ["@ext//:foo.txt"],
  outs = ["it.txt"],
  cmd = "cp $< $@",
)
EOF
  bazel build //:it > "${TEST_log}" 2>&1 && fail "expected failure" || :
  expect_log "ext-1.0.*not found"
  expect_log "prefixes.*ext-1.1"
}

function test_suggest_nostripprefix() {
  # Verify that a suggestion is made about dropping an unnecessary
  # `strip_prefix` argument.
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"

  mkdir ext
  touch ext/foo.txt
  (cd ext && tar cvf "${WRKDIR}/ext.tar" foo.txt)

  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext-1.0",
  urls=["file://${WRKDIR}/ext.tar"],
)
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "it",
  srcs = ["@ext//:foo.txt"],
  outs = ["it.txt"],
  cmd = "cp $< $@",
)
EOF
  bazel build //:it > "${TEST_log}" 2>&1 && fail "expected failure" || :
  expect_log "ext-1.0.*not found"
  expect_log "not.*any directory"
  expect_log 'no need for `strip_prefix`'
}

function test_loaded_file_reported() {
  # Verify that upon a load in the WORKSPACE file with
  # the repository not (yet) defined, the name of the
  # file is reported in the error message.
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"

  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<'EOF'
load("@nonexistent//path/to/package:file/to/import.bzl", "foo")
foo()
EOF
  touch BUILD
  bazel build //... > "${TEST_log}" 2>&1 && fail "Expected failure"

  expect_log '@nonexistent//path/to/package:file/to/import.bzl'
  expect_log 'nonexistent.*repository.*WORKSPACE'
}

function test_report_files_searched() {
  # Verify that upon  a missing package, the places where a BUILD file was
  # searched for are reported.
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"

  mkdir ext
  echo 'data' > ext/foo.txt
  tar cvf ext.tar ext
  rm -rf ext

  mkdir -p path/to/workspace
  cd path/to/workspace
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  urls=["file://${WRKDIR}/ext.tar"],
  build_file = "@//path/to:ext.BUILD",
)
EOF
  mkdir -p path/to
  echo 'exports_files(["foo.txt"])' > path/to/ext.BUILD

  bazel build @ext//... > "${TEST_log}" 2>&1 \
      && fail "Expected failure" || :

  expect_log 'BUILD file not found'
  expect_log 'path/to/workspace/path/to'
}

function test_report_package_external() {
  # Verify that a useful error message is shown for a BUILD
  # file not found at the expected location in an external repository.
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"

  mkdir -p ext/path/too/deep
  echo 'data' > ext/path/too/deep/foo.txt
  echo 'exports_files(["deep/foo.txt"])' > ext/path/too/BUILD
  tar cvf ext.tar ext
  rm -rf ext

  mkdir -p path/to/workspace
  cd path/to/workspace
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  urls=["file://${WRKDIR}/ext.tar"],
)
EOF
  cat > BUILD <<EOF
genrule(
  name = "it",
  outs = ["it.txt"],
  srcs = ["@ext//path/too/deep:foo.txt"],
  cmd = "cp $< $@",
)
EOF

  bazel build //:it > "${TEST_log}" 2>&1 \
      && fail "Expected failure" || :

  expect_log 'BUILD file not found.*path/too/deep'
}

function test_location_reported() {
  # Verify that some useful information is provided about where
  # a failing repository definition occurred.
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"
  mkdir empty
  tar cvf x.tar empty
  rm -rf empty

  mkdir -p path/to/main
  cd path/to/main
  touch BUILD
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<'EOF'
load("//:repos.bzl", "repos")
repos()
EOF
  cat > repos.bzl <<"EOF"
load("//:foo.bzl", "foo_repos")

def repos():
  # ..forgot to add the repository bar
  foo_repos()
EOF
  cat > foo.bzl <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

def foo_repos():
    http_archive(
        name = "foo",
        url = "file://${WRKDIR}/x.tar",
        build_file = "@bar//:foo.build",
    )
EOF

  bazel build @foo//... > "${TEST_log}" 2>&1 && fail "expected failure"
  inplace-sed -e "s?$WRKDIR/?WRKDIR/?g" -e "s?$TEST_TMPDIR/?TEST_TMPDIR/?g" "${TEST_log}"

  expect_log 'error.*repository.*foo'
  expect_log '@bar//:foo.build'
  expect_log "Repository foo instantiated at:"
  expect_log "  WRKDIR/path/to/main/WORKSPACE:"
  expect_log "  WRKDIR/path/to/main/repos.bzl:5"
  expect_log "  WRKDIR/path/to/main/foo.bzl:4"
  expect_log "Repository rule http_archive defined at:"
  expect_log "  TEST_TMPDIR/.*/external/bazel_tools/tools/build_defs/repo/http.bzl:"
}

function test_circular_definition_reported() {
  # Verify that bazel reports a useful error message upon
  # detecting a circular definition of a repository.
  # Also verify that the call stack of the definition is shown.

  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"

  mkdir ext
  touch ext/BUILD
  cat > ext/a.BUILD <<'EOF'
genrule(
  name = "a",
  outs = ["a.txt"],
  cmd = "echo Hello World > $@",
)
EOF
  cat > ext/b.BUILD <<'EOF'
genrule(
  name = "b",
  outs = ["b.txt"],
  cmd = "echo Hello World > $@",
)
EOF
  cat > ext/notabuildfile.bzl <<'EOF'
x = 42
EOF
  tar cvf ext.tar ext
  rm -rf ext

  mkdir main
  cd main
  cat > foo.bzl <<'EOF'
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

def foo():
  http_archive(
    name = "a",
    url = "file://${WRKDIR}/ext.tar",
    build_file = "@b//:a.BUILD",
  )
EOF
  cat > bar.bzl <<'EOF'
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

def bar():
  http_archive(
    name = "b",
    url = "file://${WRKDIR}/ext.tar",
    build_file = "@a//:b.BUILD",
  )
EOF
  cat > WORKSPACE <<EOF
load("//:foo.bzl", "foo")
load("//:bar.bzl", "bar")

foo()
bar()

load("@a//:notabuildfile.bzl", "x")
EOF
  touch BUILD

  bazel build //... > "${TEST_log}" 2>&1 && fail "expected failure" || :
  inplace-sed -e 's?$(pwd)/?PWD/?g' "${TEST_log}"

  expect_not_log '[iI]nternal [eE]rror'
  expect_not_log 'IllegalStateException'
  expect_log '[Cc]ircular definition.*repositor'
  expect_log '@a'
  expect_log '@b'

  # We expect to find the call stack for the definition of the repositories
  # a and b
  expect_log "WORKSPACE:4:4"
  expect_log "foo.bzl:4:15"

  expect_log "WORKSPACE:5:4"
  expect_log "bar.bzl:4:15"
}

function test_missing_repo_reported() {
  # Verify that, if a WORKSPACE cycle is reported due to
  # a missing repository definition, the name of the actually
  # missing repository is reported.
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"

  mkdir main
  cd main

  cat > withimplicit.bzl <<'EOF'
def _impl(ctx):
  ctx.file("data.txt", ctx.attr.value)
  ctx.file("data.bzl", "value = %s" % (ctx.attr.value,))
  ctx.symlink(ctx.attr._generic_build_file, "BUILD")

data_repo = repository_rule(
  implementation = _impl,
  attrs = { "value" : attr.string(),
            "_generic_build_file" : attr.label(
                default = Label("@this_repo_is_missing//:generic.BUILD")) },
)
EOF
  touch BUILD
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<'EOF'
load("//:withimplicit.bzl", "data_repo")

data_repo(
  name = "data",
  value = "42")

load("@data//:value.bzl", "value")
EOF

  bazel build //... > "${TEST_log}" 2>&1 && fail "expected failure" || :
  inplace-sed -e 's?$(pwd)/?PWD/?g' "${TEST_log}"

  expect_log "you have to add.*this_repo_is_missing.*WORKSPACE"
  # Also verify that the repository class and its definition is reported, to
  # help finding out where the implicit dependency comes from.
  expect_log "Repository data instantiated at:"
  expect_log ".../WORKSPACE:[0-9]*"
  expect_log "Repository rule data_repo defined at:"
  expect_log ".../withimplicit.bzl:6"
}

function test_overwrite_existing_workspace_build() {
  # Verify that the WORKSPACE and BUILD files provided by
  # the invocation of an http_archive rule correctly
  # overwritel any such file packed in the archive.
  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
  cd "${WRKDIR}"

  for bad_file in \
      'echo BAD content > $1' \
      'ln -s /does/not/exist/BAD $1'

  do
    rm -rf ext ext.tar
    mkdir ext
    sh -c "${bad_file}" -- ext/WORKSPACE
    sh -c "${bad_file}" -- ext/BUILD.bazel
    echo hello world > ext/data
    tar cvf ext.tar ext

    for BUILD_FILE in \
      'build_file_content = '\''exports_files(["data", "WORKSPACE"])'\' \
      'build_file = "@//:external_build_file"'
    do
      rm -rf main
      mkdir main
      cd main

      cat > WORKSPACE <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
   name = "ext",
   strip_prefix = "ext",
   url = 'file://${WRKDIR}/ext.tar',
   ${BUILD_FILE},
)
EOF
      echo
      ls -al ${WRKDIR}/ext
      echo
      cat WORKSPACE
      echo

      cat > external_build_file <<'EOF'
exports_files(["data", "WORKSPACE"])
EOF

      cat > BUILD <<'EOF'
genrule(
  name = "it",
  cmd = "cp $< $@",
  srcs = ["@ext//:data"],
  outs = ["it.txt"],
)

genrule(
  name = "ws",
  cmd = "cp $< $@",
  srcs = ["@ext//:WORKSPACE"],
  outs = ["ws.txt"],
)
EOF

      bazel build //:it || fail "Expected success"
      grep 'world' `bazel info bazel-genfiles`/it.txt \
          || fail "Wrong content of data file"
      bazel build //:ws || fail "Expected success"
      grep 'BAD' `bazel info bazel-genfiles`/ws.txt \
          && fail "WORKSPACE file not overwritten" || :

      cd ..
    done
  done
}

function test_external_java_target_depends_on_external_resources() {
  local test_repo1=$TEST_TMPDIR/repo1
  local test_repo2=$TEST_TMPDIR/repo2

  mkdir -p $test_repo1/a
  mkdir -p $test_repo2

  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
local_repository(name = 'repo1', path='$test_repo1')
local_repository(name = 'repo2', path='$test_repo2')
EOF
  cat > BUILD <<'EOF'
java_binary(
    name = "a_bin",
    runtime_deps = ["@repo1//a:a"],
    main_class = "a.A",
)
EOF

  touch $test_repo1/WORKSPACE
  cat > $test_repo1/a/BUILD <<'EOF'
package(default_visibility = ["//visibility:public"])

java_library(
    name = "a",
    srcs = ["A.java"],
    resources = ["@repo2//:resource_files"],
)
EOF
  cat > $test_repo1/a/A.java <<EOF
package a;

public class A {
    public static void main(String args[]) {
    }
}
EOF

  touch $test_repo2/WORKSPACE
  cat > $test_repo2/BUILD <<'EOF'
package(default_visibility = ["//visibility:public"])

filegroup(
    name = "resource_files",
    srcs = ["resource.txt"]
)
EOF

  cat > $test_repo2/resource.txt <<EOF
RESOURCE
EOF

  bazel build a_bin >& $TEST_log  || fail "Expected build/run to succeed"
}

function test_query_external_packages() {
  setup_skylib_support

  mkdir -p external/nested
  mkdir -p not-external

  cat > external/BUILD <<EOF
filegroup(
    name = "a1",
    srcs = [],
)
EOF
  cat > external/nested/BUILD <<EOF
filegroup(
    name = "a2",
    srcs = [],
)
EOF

  cat > not-external/BUILD <<EOF
filegroup(
    name = "b",
    srcs = [],
)
EOF

  bazel query //... >& $TEST_log || fail "Expected build/run to succeed"
  expect_log "//not-external:b"
  expect_not_log "//external:a1"
  expect_not_log "//external/nested:a2"

  bazel query --experimental_sibling_repository_layout //... >& $TEST_log \ ||
    fail "Expected build/run to succeed"
  expect_log "//not-external:b"
  # Targets in //external aren't supported yet.
  expect_not_log "//external:a1"
  expect_log "//external/nested:a2"
}

function test_query_external_all_targets() {
  setup_skylib_support

  mkdir -p external/nested
  mkdir -p not-external

  cat > external/nested/BUILD <<EOF
filegroup(
    name = "a",
    srcs = glob(["**"]),
)
EOF
  touch external/nested/A

  cat > not-external/BUILD <<EOF
filegroup(
    name = "b",
    srcs = glob(["**"]),
)
EOF
  touch not-external/B

  bazel query //...:all-targets >& $TEST_log \
    || fail "Expected build/run to succeed"
  expect_log "//not-external:b"
  expect_log "//not-external:B"
  expect_not_log "//external/nested:a"
  expect_not_log "//external/nested:A"

  bazel query --experimental_sibling_repository_layout //...:all-targets \
    >& $TEST_log || fail "Expected build/run to succeed"
  expect_log "//not-external:b"
  expect_log "//not-external:B"
  expect_log "//external/nested:a"
  expect_log "//external/nested:A"
}

function test_external_deps_skymeld() {
  # A minimal build to make sure bazel in Skymeld mode can build with external
  # dependencies.
  mkdir ext
  echo content > ext/ext
  EXTREPODIR=`pwd`
  zip ext.zip ext/*
  rm -rf ext

  rm -rf main
  mkdir main
  cd main
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  url="file://${EXTREPODIR}/ext.zip",
  build_file_content="exports_files([\"ext\"])",
)
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "foo",
  srcs = ["@ext//:ext"],
  outs = ["foo"],
  cmd = "cp $< $@",
)
EOF
  execroot="$(bazel info execution_root)"

  bazel build --experimental_merged_skyframe_analysis_execution //:foo \
    || fail 'Expected build to succeed with Skymeld'

  test -h "$execroot/external/ext" || fail "Expected symlink to external repo."
}

function test_environ_incrementally() {
  # Set up workspace with a repository rule to examine env vars.  Assert that undeclared
  # env vars don't trigger reevaluations.
  cat > repo.bzl <<EOF
def _impl(rctx):
  rctx.symlink(rctx.attr.build_file, 'BUILD')
  print('UNDECLARED_KEY=%s' % rctx.os.environ.get('UNDECLARED_KEY'))
  print('PREDECLARED_KEY=%s' % rctx.os.environ.get('PREDECLARED_KEY'))
  print('LAZYEVAL_KEY=%s' % rctx.getenv('LAZYEVAL_KEY'))

dummy_repository = repository_rule(
  implementation = _impl,
  attrs = {'build_file': attr.label()},
  environ = ['PREDECLARED_KEY'],  # sic
)
EOF
  cat > BUILD.dummy <<EOF
filegroup(name='dummy', srcs=['BUILD'])
EOF
  touch BUILD
  cat > WORKSPACE <<EOF
load('//:repo.bzl', 'dummy_repository')
dummy_repository(name = 'foo', build_file = '@@//:BUILD.dummy')
EOF

  # Baseline: DEBUG: UNDECLARED_KEY is logged to stderr.
  UNDECLARED_KEY=val1 bazel query @foo//:BUILD 2>$TEST_log || fail 'Expected no-op build to succeed'
  expect_log "UNDECLARED_KEY=val1"

  # UNDECLARED_KEY is, well, undeclared.  This will be a no-op.
  UNDECLARED_KEY=val2 bazel query @foo//:BUILD 2>$TEST_log || fail 'Expected no-op build to succeed'
  expect_not_log "UNDECLARED_KEY"

  #---

  # Predeclared key.
  PREDECLARED_KEY=wal1 bazel query @foo//:BUILD 2>$TEST_log || fail 'Expected no-op build to succeed'
  expect_log "PREDECLARED_KEY=wal1"

  # Predeclared key, no-op build.
  PREDECLARED_KEY=wal1 bazel query @foo//:BUILD 2>$TEST_log || fail 'Expected no-op build to succeed'
  expect_not_log "PREDECLARED_KEY"

  # Predeclared key, new value -> refetch.
  PREDECLARED_KEY=wal2 bazel query @foo//:BUILD 2>$TEST_log || fail 'Expected no-op build to succeed'
  expect_log "PREDECLARED_KEY=wal2"

  #---

  # Side-effect key.
  LAZYEVAL_KEY=xal1 bazel query @foo//:BUILD 2>$TEST_log || fail 'Expected no-op build to succeed'
  expect_log "PREDECLARED_KEY=None"
  expect_log "LAZYEVAL_KEY=xal1"

  # Side-effect key, no-op build.
  LAZYEVAL_KEY=xal1 bazel query @foo//:BUILD 2>$TEST_log || fail 'Expected no-op build to succeed'
  expect_not_log "LAZYEVAL_KEY"

  # Side-effect key, new value -> refetch.
  LAZYEVAL_KEY=xal2 bazel query @foo//:BUILD 2>$TEST_log || fail 'Expected no-op build to succeed'
  expect_log "LAZYEVAL_KEY=xal2"

  # Ditto, but with --repo_env overriding environment.
  LAZYEVAL_KEY=xal2 bazel query --repo_env=LAZYEVAL_KEY=xal3 @foo//:BUILD 2>$TEST_log || fail 'Expected no-op build to succeed'
  expect_log "LAZYEVAL_KEY=xal3"
}

run_suite "external tests"
