require "tap" require "option_parser" require "colorize" class Storage def initialize(@root : String = "storage") end def store(project, environment, revision, content) date = Time.utc.to_unix file_path = "#{@root}/#{project}/#{environment}/#{date}##{revision}.tap" Dir.mkdir_p File.dirname file_path File.write file_path, content end def projects Dir.children @root end def environments(project) Dir.children "#{@root}/#{project}" end def results(project, environment) dir_name = "#{@root}/#{project}/#{environment}" Dir.children(dir_name).compact_map do |file_name| unless file_name.match /.tap$/ next end tap = Tap.parse File.read "#{dir_name}/#{file_name}" next unless tap md = file_name.match /^([^#]*)#(.*).tap$/ unless md next end revision = md[2] timestamp = md[1] {revision, timestamp, tap} end end end Colorize.on_tty_only! args = Array(String).new show_projects = [] of String show_environments = [] of String show_revisions = [] of String show_as_summary = true storage_directory = "storage" OptionParser.parse! do |parser| parser.banner = "usage: tap-aggregator [options]" parser.on("-h", "--help", "Show this help") do puts parser exit 0 end parser.on("-p id", "--project id", "Filter results that do not match this project.") do |id| show_projects << id end parser.on("-e id", "--environment id", "Filter results that do not match this environment.") do |id| show_environments << id end parser.on("-r id", "--revision id", "Filter results that do not match this revision id.") do |id| show_revisions << id end parser.on("-s storage", "--storage storage", "Default: storage") do |storage| storage_directory = storage end parser.on("-v", "--verbose", "Prints all data instead of just summaries.") do show_as_summary = false end parser.invalid_option do |flag| STDERR.puts "ERROR: #{flag} is not a valid option." STDERR.puts parser exit 1 end parser.unknown_args do |x| args = x if args.size < 1 puts parser exit 1 end end end command = args[0] storage = Storage.new storage_directory enum Tap::Entry::Status def to_s if self == NotOk "not ok" elsif self == Ok "ok" end end end case command when "add" if args.size != 4 STDERR.puts "usage: tap-aggregator add [options]" exit 1 end project = args[1] environment = args[2] revision = args[3] storage.store project, environment, revision, STDIN.gets_to_end when "show" projects = storage.projects.select do |project| show_projects.size == 0 || show_projects.any? &.==(project) end projects.each do |project| environments = storage.environments(project).select do |environment| show_environments.size == 0 || show_environments.any? &.==(environment) end environments.each do |environment| # FIXME: This currently parses everything, including entries # that are not going to be displayed. results = storage.results(project, environment).select do |revision, timestamp, suite| show_revisions.size == 0 || show_revisions.any? &.==(revision) end results.each do |revision, timestamp, suite| # FIXME: Only extract most recent revision if show_as_summary. summary = suite.summary number_ok = summary.tests_passed.size number_failed = summary.tests_failed.size # This prints the header, or summary line of a test suite. STDOUT << ("%-15s" % project).colorize(:white).bright STDOUT << " (#{environment}) r=#{revision}, " STDOUT << "#{number_ok} ok".colorize(:green).bright STDOUT << ", " STDOUT << "#{number_failed} not ok".colorize(:red).bright STDOUT << "\n" if !show_as_summary suite.each do |test| # This prints a single TAP test entry. STDOUT << " -> " STDOUT << ("%3s" % test.id).colorize(:cyan) if test.status.ok? STDOUT << (" %6s " % test.status.to_s).colorize(:green) else STDOUT << (" %6s " % test.status.to_s).colorize(:red) end STDOUT << test.title.colorize(:white) STDOUT << "\n" STDOUT.flush end end end end end end