Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions builder/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,23 @@ refresh_test_fixtures:

rm -rf /tmp/byo-redis
git clone https://github.com/codecrafters-io/build-your-own-redis /tmp/byo-redis
cd /tmp/byo-redis && git checkout 5ce64229f59108851de28c4f083f3459d58eff34
cd /tmp/byo-redis && git checkout 5fccb61555a659223eff1d3b96c437ce82704d48

rm -rf /tmp/byo-sqlite
git clone https://github.com/codecrafters-io/build-your-own-sqlite /tmp/byo-sqlite
cd /tmp/byo-sqlite && git checkout 8f419979fb7b3eb95cd2d0547f084032cf9edf39

rm -rf ./tests/fixtures/redis-ruby-pass-stage-1
cp -R /tmp/byo-redis/solutions/ruby/01-jm1/code ./tests/fixtures/redis-ruby-pass-stage-1
cp /tmp/byo-redis/dockerfiles/ruby-3.3.Dockerfile ./tests/fixtures/dockerfiles/redis-ruby-3.3.Dockerfile
cp /tmp/byo-redis/dockerfiles/ruby-3.4.Dockerfile ./tests/fixtures/dockerfiles/redis-ruby-3.4.Dockerfile

rm -rf ./tests/fixtures/redis-rust-pass-stage-1
cp -R /tmp/byo-redis/solutions/rust/01-jm1/code ./tests/fixtures/redis-rust-pass-stage-1
cp /tmp/byo-redis/dockerfiles/rust-1.88.Dockerfile ./tests/fixtures/dockerfiles/redis-rust-1.88.Dockerfile
cp /tmp/byo-redis/dockerfiles/rust-1.91.Dockerfile ./tests/fixtures/dockerfiles/redis-rust-1.91.Dockerfile

rm -rf ./tests/fixtures/redis-go-pass-stage-1
cp -R /tmp/byo-redis/solutions/go/01-jm1/code ./tests/fixtures/redis-go-pass-stage-1
cp /tmp/byo-redis/dockerfiles/go-1.24.Dockerfile ./tests/fixtures/dockerfiles/redis-go-1.24.Dockerfile
cp /tmp/byo-redis/dockerfiles/go-1.25.Dockerfile ./tests/fixtures/dockerfiles/redis-go-1.25.Dockerfile

rm -rf ./tests/fixtures/sqlite-python-pass-stage-1
cp -R /tmp/byo-sqlite/solutions/python/01-dr6/code ./tests/fixtures/sqlite-python-pass-stage-1
Expand Down
12 changes: 12 additions & 0 deletions builder/internal/commands/build_image_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,18 @@ func BuildImageCommand() int {
return handleErrorDuringBuild(build, "", fmt.Errorf("tester dir (%s) does not exist", testerDir))
}

testShPath := filepath.Join(testerDir, "test.sh")
fileInfo, err := os.Stat(testShPath)
if os.IsNotExist(err) {
return handleErrorDuringBuild(build, "", fmt.Errorf("test.sh does not exist in tester dir (%s)", testerDir))
}
if err != nil {
return handleErrorDuringBuild(build, "", fmt.Errorf("failed to stat test.sh in tester dir (%s): %w", testerDir, err))
}
if fileInfo.Mode()&0111 == 0 {
return handleErrorDuringBuild(build, "", fmt.Errorf("test.sh is not executable in tester dir (%s)", testerDir))
}

if _, err := os.Stat(testRunnerDir); os.IsNotExist(err) {
return handleErrorDuringBuild(build, "", fmt.Errorf("test runner dir (%s) does not exist", testRunnerDir))
}
Expand Down
65 changes: 61 additions & 4 deletions builder/tests/build_image_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,72 @@ def test_removes_dockerignore_file
create_build!
run_script!
stream_output = Timeout.timeout(5) { `logstream -url #{@build.local_logstream_url} follow` }

assert_match(/Build successful/, stream_output)
assert_equal "success", @build.reload.status

ls_output = `docker run --rm registry.fly.io/test-depot-push:test-runner-tests ls -la /app`
refute_match(/.dockerignore/, ls_output, ".dockerignore should not exist in the built image")
assert_match(/control.txt/, ls_output, "control.txt should still exist in the built image")
end

def test_handles_missing_test_sh
create_and_push_git_repository!("redis-ruby")

# Create a tester directory without test.sh
tester_dir = Dir.mktmpdir
# Create a dummy tester binary (validation only checks for test.sh)
File.write(File.join(tester_dir, "tester"), "#!/bin/sh\necho 'dummy tester'\n")
File.chmod(0o755, File.join(tester_dir, "tester"))

create_build!

begin
run_script!(tester_dir: tester_dir)
flunk "Expected build to fail with missing test.sh"
rescue
# Expected to fail
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Bare rescue catches flunk exception masking test failures

The bare rescue catches all StandardError subclasses, including Minitest::Assertion raised by flunk. If run_script! unexpectedly succeeds, flunk is called but immediately caught by the rescue block, causing the test to continue rather than fail with the intended message. The test would eventually fail at subsequent assertions but with a confusing error message, making debugging harder. The rescue block needs to specify the expected exception type (like RuntimeError) to avoid swallowing the flunk assertion.

Additional Locations (1)

Fix in Cursor Fix in Web


assert_equal "error", @build.reload.status

output = Timeout.timeout(5) { `logstream -url #{@build.local_logstream_url} follow` }
assert_match(/CodeCrafters internal error/i, output)
assert_match(/test\.sh does not exist in tester dir/, output)
assert_match(/CodeCrafters internal error/i, @build.reload.parsed_logs)
assert_match(/test\.sh does not exist in tester dir/, @build.reload.parsed_logs)
end

def test_handles_non_executable_test_sh
create_and_push_git_repository!("redis-ruby")

# Create a tester directory with non-executable test.sh
tester_dir = Dir.mktmpdir
# Create a dummy tester binary (validation only checks for test.sh)
File.write(File.join(tester_dir, "tester"), "#!/bin/sh\necho 'dummy tester'\n")
File.chmod(0o755, File.join(tester_dir, "tester"))
test_sh_path = File.join(tester_dir, "test.sh")
File.write(test_sh_path, "#!/bin/sh\nexec \"${TESTER_DIR}/tester\"\n")
File.chmod(0o644, test_sh_path) # Not executable

create_build!

begin
run_script!(tester_dir: tester_dir)
flunk "Expected build to fail with non-executable test.sh"
rescue
# Expected to fail
end

assert_equal "error", @build.reload.status

output = Timeout.timeout(5) { `logstream -url #{@build.local_logstream_url} follow` }
assert_match(/CodeCrafters internal error/i, output)
assert_match(/test\.sh is not executable in tester dir/, output)
assert_match(/CodeCrafters internal error/i, @build.reload.parsed_logs)
assert_match(/test\.sh is not executable in tester dir/, @build.reload.parsed_logs)
end

def create_build!(commit_sha: nil)
commit_sha ||= @git_repository.head_commit_sha
@build = TestRunnerBuild.create!(test_runner: @repository.test_runners.first, id: SecureRandom.uuid, commit_sha: commit_sha)
Expand All @@ -160,9 +217,9 @@ def create_and_push_git_repository!(code_fixture_key)
@git_repository.push_to_git_daemon!
end

def run_script!(test_run: nil)
def run_script!(test_run: nil, tester_dir: nil)
BuildImageCommandRunner
.new(git_repository: @git_repository, repository: @repository)
.run(build: @build, test_run: test_run)
.run(build: @build, test_run: test_run, tester_dir: tester_dir)
end
end
4 changes: 2 additions & 2 deletions builder/tests/lib/build_image_command_runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ def initialize(git_repository:, repository:)
self.repository = repository
end

def run(build:, test_run: nil)
def run(build:, test_run: nil, tester_dir: nil)
tmp_dir = Dir.mktmpdir
tester_dir = TesterDownloader.new(course: OpenStruct.new(slug: repository.course_slug), testers_root_dir: TESTERS_DIR).download_if_needed
tester_dir ||= TesterDownloader.new(course: OpenStruct.new(slug: repository.course_slug), testers_root_dir: TESTERS_DIR).download_if_needed

FileUtils.rm_rf(tmp_dir)
FileUtils.cp_r(git_repository.tmp_dir, tmp_dir)
Expand Down
4 changes: 2 additions & 2 deletions builder/tests/lib/buildpack_dockerfile_processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ def self.build_prelude_lines(from_line)
case from_line
when /alpine/
"RUN apk add --update-cache --upgrade git curl"
when /buster/, /slim/, /focal/, /bullseye/, /bookworm/
when /debian/, /buster/, /slim/, /focal/, /noble/, /bullseye/, /bookworm/, /trixie/
"RUN apt-get update && apt-get install -y git curl"
when /dart/
"RUN apt-get update && apt-get install -y git curl" # Dart doesn't have OS type in its FROM line
else
raise "Unknown from_line: #{from_line}"
raise "Expected FROM line (#{from_line}) to contain a distribution identifier like alpine, debian, buster, etc."
end,

"CMD [\"/var/opt/test-runner\", \"run_tests\"]",
Expand Down
12 changes: 6 additions & 6 deletions builder/tests/lib/code_fixtures.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ module CodeFixtures

DB = {
"redis-ruby" => {
"dockerfile_contents" => File.read(File.join(repository_root, "tests/fixtures/dockerfiles/redis-ruby-3.3.Dockerfile")),
"buildpack_slug" => "ruby-3.3",
"dockerfile_contents" => File.read(File.join(repository_root, "tests/fixtures/dockerfiles/redis-ruby-3.4.Dockerfile")),
"buildpack_slug" => "ruby-3.4",
"code_dir" => File.join(repository_root, "tests/fixtures/redis-ruby-pass-stage-1"),
"course_slug" => "redis",
"language_slug" => "ruby"
},
"redis-rust" => {
"dockerfile_contents" => File.read(File.join(repository_root, "tests/fixtures/dockerfiles/redis-rust-1.88.Dockerfile")),
"buildpack_slug" => "rust-1.88",
"dockerfile_contents" => File.read(File.join(repository_root, "tests/fixtures/dockerfiles/redis-rust-1.91.Dockerfile")),
"buildpack_slug" => "rust-1.91",
"code_dir" => File.join(repository_root, "tests/fixtures/redis-rust-pass-stage-1"),
"course_slug" => "redis",
"language_slug" => "rust"
},
"redis-go" => {
"dockerfile_contents" => File.read(File.join(repository_root, "tests/fixtures/dockerfiles/redis-go-1.24.Dockerfile")),
"buildpack_slug" => "go-1.24",
"dockerfile_contents" => File.read(File.join(repository_root, "tests/fixtures/dockerfiles/redis-go-1.25.Dockerfile")),
"buildpack_slug" => "go-1.25",
"code_dir" => File.join(repository_root, "tests/fixtures/redis-go-pass-stage-1"),
"course_slug" => "redis",
"language_slug" => "go"
Expand Down
Loading