#!/usr/bin/env ruby
# frozen_string_literal: true

# Repo-level test runner for the mixed minitest/pytest suites under plugins/.
#
#   bin/test              # run every plugin suite
#   bin/test os-backlog   # run one plugin's suites (repeatable)
#
# Per plugin it runs `ruby tests/all.rb` when present (minitest) and
# `pytest tests/` when any tests/*_test.py or tests/test_*.py exists.
# pytest runs once per plugin so same-named test files (hook_test.py in
# several plugins) never collide on module import.
# os-vault's tests/ is an eval harness (harness.sh), not a unit suite,
# and is intentionally not run here.

require "open3"

class PluginSuite
  RUNNERS = {
    minitest: ->(dir) { ["ruby", "tests/all.rb"] },
    pytest: ->(dir) { ["pytest", "-q", "tests"] }
  }.freeze

  attr_reader :name

  def initialize(dir)
    @dir = dir
    @name = File.basename(dir)
  end

  def kinds
    kinds = []
    kinds << :minitest if File.exist?(File.join(@dir, "tests", "all.rb"))
    kinds << :pytest if pytest_files?
    kinds
  end

  def run(kind)
    command = RUNNERS.fetch(kind).call(@dir)
    stdout, status = Open3.capture2e(*command, chdir: @dir)
    [status.success?, stdout]
  end

  private

  def pytest_files?
    Dir.glob(File.join(@dir, "tests", "{test_*,*_test}.py")).any?
  end
end

repo_root = File.expand_path("..", __dir__)
requested = ARGV
suites = Dir.glob(File.join(repo_root, "plugins", "*"))
  .select { |d| File.directory?(d) }
  .map { |d| PluginSuite.new(d) }
  .select { |s| requested.empty? || requested.include?(s.name) }
  .sort_by(&:name)

unknown = requested - suites.map(&:name)
abort "bin/test: no such plugin(s): #{unknown.join(", ")}" unless unknown.empty?

failures = []
ran = 0
suites.each do |suite|
  suite.kinds.each do |kind|
    ran += 1
    ok, output = suite.run(kind)
    puts "#{ok ? "PASS" : "FAIL"}  #{suite.name} (#{kind})"
    unless ok
      failures << "#{suite.name} (#{kind})"
      puts output.lines.map { |l| "      #{l}" }.join
    end
  end
end

if ran.zero?
  puts "bin/test: no test suites found"
elsif failures.empty?
  puts "#{ran} suites passed"
else
  abort "#{failures.size}/#{ran} suites failed: #{failures.join(", ")}"
end
