require 'set'
require 'debci/status'
require 'debci/package'
module Debci
class Repository
attr_reader :path
def initialize(path=nil) path ||= Debci.config.data_basedir
@path = path
@data_dirs = Dir.glob(File.join(path, 'packages', '*', '*')).reject { |d| d =~ /\.old$/ }
end
def suites
@suites ||= Dir.glob(File.join(@path, 'packages', '*')).reject { |d| d =~ /\.old$/ }.map { |d| File.basename(d) }.sort
end
def architectures
@architectures ||= @data_dirs.map { |d| File.basename(d) }.uniq.sort
end
def packages
@packages ||= (@data_dirs.map { |d| Dir.glob(File.join(d, '*/*')) }.flatten.map { |d| File.basename(d) } + Debci.blacklist.packages).to_set.sort
end
def non_blacklisted_packages
@non_blacklisted_packages ||= packages - Debci.blacklist.packages
end
def prefixes
@prefixes ||= @data_dirs.map { |d| Dir.glob(File.join(d, '*/')) }.flatten.map { |d| File.basename(d) }.uniq.sort
end
def tmpfail_packages
tmpfail_packages = Set.new
all_non_blacklisted_statuses.each do |status|
if status.status == :tmpfail
tmpfail_packages << status.package
end
end
tmpfail_packages.sort.map { |p| Debci::Package.new(p, self) }
end
def failing_packages
failing_packages = Set.new
all_non_blacklisted_statuses.each do |status|
if status.status == :fail
failing_packages << status.package
end
end
failing_packages.sort.map { |p| Debci::Package.new(p, self) }
end
def slow_statuses
all_non_blacklisted_statuses.select do |status|
status.duration_seconds && status.duration_seconds > 60 * 60 end
end
def suites_for(package)
package = String(package)
data_dirs_for(package).map { |d| File.basename(File.dirname(d)) }.uniq
end
def architectures_for(package)
package = String(package)
data_dirs_for(package).map { |d| File.basename(d) }.uniq
end
class PackageNotFound < Exception
end
def find_package(name)
if !packages.include?(name)
raise PackageNotFound.new(name)
end
Debci::Package.new(name, self)
end
def each_package
packages.sort.each do |pkgname|
pkg = Debci::Package.new(pkgname, self)
yield pkg
end
end
def search(query)
match = packages.select { |p| p == query }
if match.empty?
re = Regexp.new(query)
match = packages.select { |p| p =~ re }
end
match.sort.map { |p| Debci::Package.new(p, self) }
end
def all_status_for(package)
architectures.map do |arch|
suites.map do |suite|
status_file = File.join(data_dir(suite, arch, package), 'latest.json')
load_status(status_file, suite, arch)
end
end
end
def status_for(package)
all_status_for(package).map { |r| r.reject(&:blacklisted?) }
end
def blacklisted_status_for(package)
all_status_for(package).map { |r| r.select(&:blacklisted?) }
end
def platform_specific_issues
result = {}
non_blacklisted_packages.each do |package|
statuses = status_for(package).flatten
if statuses.map(&:status).reject { |s| [:no_test_data, :tmpfail].include?(s) }.uniq.size > 1
result[package] = statuses
end
end
result
end
def history_for(package, suite, architecture)
file = File.join(data_dir(suite, architecture, package), 'history.json')
return [] unless File.exists?(file)
entries = JSON.load(File.read(file))
entries
.map { |test| Debci::Status.from_data(test, suite, architecture) }
.sort_by(&:date)
.reverse
end
def news_for(package, n=10)
history = []
suites.each do |suite|
architectures.each do |arch|
history += history_for(package, suite, arch)
end
end
history.sort_by! { |e| -e.run_id.to_i }
news = []
while !history.empty?
status = history.shift
if status.newsworthy?
news << status
end
if news.size >= n
break
end
end
news
end
def global_news(limit = 10)
latest = Dir[File.join(self.path, '*/*/*/*/*/latest.json')].sort_by do |f|
File::Stat.new(f).mtime
end
news = []
while !latest.empty?
file = latest.pop
dir = File.expand_path(File.dirname(file) + '/../..')
suite = File.basename(File.dirname(dir))
architecture = File.basename(dir)
status = Debci::Status.from_file(file, suite, architecture)
if status.newsworthy?
news << status
end
if news.size >= limit
break
end
end
news
end
def status_history(suite, architecture)
return unless File.exists?(file = File.join(status_dir(suite, architecture), 'history.json'))
data = nil
begin
File.open(file, 'r') do |f|
data = JSON.load(f)
end
rescue JSON::ParserError
true
end
data
end
private
def data_dir(suite, arch, package)
File.join(@path, 'packages', "#{suite}", "#{arch}", prefix(package), package)
end
def status_dir(suite, arch)
File.join(@path, 'status', "#{suite}", "#{arch}")
end
def prefix(package)
String(package).sub(/^((lib)?.).*/, '\1')
end
def load_status(status_file, suite, architecture)
Debci::Status.from_file(status_file, suite, architecture)
end
def data_dirs_for(package)
@data_dirs.select { |d| File.exist?(File.join(d, prefix(package), package)) }
end
def all_statuses
@all_statuses ||=
begin
r = []
packages.each do |package|
suites.each do |suite|
architectures.each do |arch|
status = load_status(File.join(data_dir(suite, arch, package), "latest.json"), suite, arch)
r << status
end
end
end
r
end
end
def all_non_blacklisted_statuses
@all_non_blacklisted_statuses ||= all_statuses.reject(&:blacklisted?)
end
end
end