at_coder_friends 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +5 -2
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +4 -1
- data/Gemfile.lock +16 -14
- data/Rakefile +2 -0
- data/at_coder_friends.gemspec +1 -0
- data/lib/at_coder_friends.rb +13 -5
- data/lib/at_coder_friends/cli.rb +1 -2
- data/lib/at_coder_friends/config_loader.rb +2 -2
- data/lib/at_coder_friends/context.rb +3 -3
- data/lib/at_coder_friends/cxx_generator.rb +7 -13
- data/lib/at_coder_friends/emitter.rb +0 -4
- data/lib/at_coder_friends/parser/constraints_parser.rb +26 -0
- data/lib/at_coder_friends/parser/format_parser.rb +154 -0
- data/lib/at_coder_friends/parser/main.rb +16 -0
- data/lib/at_coder_friends/parser/page_parser.rb +119 -0
- data/lib/at_coder_friends/path_util.rb +10 -0
- data/lib/at_coder_friends/problem.rb +11 -14
- data/lib/at_coder_friends/scraping/agent.rb +77 -0
- data/lib/at_coder_friends/scraping/authentication.rb +71 -0
- data/lib/at_coder_friends/scraping/custom_test.rb +53 -0
- data/lib/at_coder_friends/scraping/session.rb +26 -0
- data/lib/at_coder_friends/scraping/submission.rb +31 -0
- data/lib/at_coder_friends/scraping/tasks.rb +39 -0
- data/lib/at_coder_friends/test_runner/base.rb +123 -0
- data/lib/at_coder_friends/test_runner/judge.rb +44 -0
- data/lib/at_coder_friends/test_runner/sample.rb +35 -0
- data/lib/at_coder_friends/verifier.rb +4 -2
- data/lib/at_coder_friends/version.rb +1 -1
- data/tasks/regression.rake +163 -0
- metadata +30 -7
- data/lib/at_coder_friends/format_parser.rb +0 -151
- data/lib/at_coder_friends/judge_test_runner.rb +0 -34
- data/lib/at_coder_friends/sample_test_runner.rb +0 -31
- data/lib/at_coder_friends/scraping_agent.rb +0 -265
- data/lib/at_coder_friends/test_runner.rb +0 -104
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
|
5
|
+
module AtCoderFriends
|
6
|
+
module Scraping
|
7
|
+
# fetch problems from tasks page
|
8
|
+
module Tasks
|
9
|
+
def fetch_all
|
10
|
+
puts "***** fetch_all #{contest} *****"
|
11
|
+
fetch_assignments.map do |q, url|
|
12
|
+
pbm = fetch_problem(q, url)
|
13
|
+
yield pbm if block_given?
|
14
|
+
pbm
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def fetch_assignments
|
19
|
+
url = contest_url('tasks')
|
20
|
+
puts "fetch list from #{url} ..."
|
21
|
+
page = fetch_with_auth(url)
|
22
|
+
page
|
23
|
+
.search('//table[1]//td[1]//a')
|
24
|
+
.each_with_object({}) do |a, h|
|
25
|
+
h[a.text] = a[:href]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def fetch_problem(q, url)
|
30
|
+
puts "fetch problem from #{url} ..."
|
31
|
+
page = fetch_with_auth(url)
|
32
|
+
Problem.new(q) do |pbm|
|
33
|
+
page.search('br').each { |br| br.replace("\n") }
|
34
|
+
pbm.page = page
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'colorize'
|
4
|
+
require 'rbconfig'
|
5
|
+
|
6
|
+
module AtCoderFriends
|
7
|
+
module TestRunner
|
8
|
+
# run tests for the specified program.
|
9
|
+
class Base
|
10
|
+
include PathUtil
|
11
|
+
|
12
|
+
attr_reader :ctx, :path, :dir, :prg, :base, :ext, :q
|
13
|
+
|
14
|
+
def initialize(ctx)
|
15
|
+
@ctx = ctx
|
16
|
+
@path, @dir, @prg, @base, @ext, @q = split_prg_path(ctx.path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def config
|
20
|
+
ctx.config
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_cmd
|
24
|
+
@test_cmd ||= begin
|
25
|
+
cmds = config.dig('ext_settings', ext, 'test_cmd')
|
26
|
+
cmd = cmds && (cmds[which_os.to_s] || cmds['default'])
|
27
|
+
return nil unless cmd
|
28
|
+
|
29
|
+
cmd.gsub('{dir}', dir).gsub('{base}', base)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_loc
|
34
|
+
test_cmd ? 'local' : 'remote'
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_mtd
|
38
|
+
test_cmd ? :local_test : :remote_test
|
39
|
+
end
|
40
|
+
|
41
|
+
def run_test(id, infile, outfile, expfile)
|
42
|
+
return false unless File.exist?(infile) && File.exist?(expfile)
|
43
|
+
|
44
|
+
puts "==== #{id} ===="
|
45
|
+
|
46
|
+
makedirs_unless(File.dirname(outfile))
|
47
|
+
is_success = send(test_mtd, infile, outfile)
|
48
|
+
show_result(
|
49
|
+
is_success,
|
50
|
+
File.read(infile),
|
51
|
+
File.read(outfile),
|
52
|
+
File.read(expfile)
|
53
|
+
)
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
def local_test(infile, outfile)
|
58
|
+
system("#{test_cmd} < #{infile} > #{outfile}")
|
59
|
+
end
|
60
|
+
|
61
|
+
def remote_test(infile, outfile)
|
62
|
+
is_success, result = call_remote_test(infile)
|
63
|
+
File.write(outfile, result)
|
64
|
+
is_success
|
65
|
+
end
|
66
|
+
|
67
|
+
def call_remote_test(infile)
|
68
|
+
res = ctx.scraping_agent.code_test(infile)
|
69
|
+
(res && res['Result']) || (return [false, 'Remote test failed.'])
|
70
|
+
|
71
|
+
puts "Exit code: #{res.dig('Result', 'ExitCode')}"
|
72
|
+
puts "Time: #{res.dig('Result', 'TimeConsumption')}ms"
|
73
|
+
puts "Memory: #{res.dig('Result', 'MemoryConsumption')}KB"
|
74
|
+
|
75
|
+
res.dig('Result', 'ExitCode').zero? || (return [false, res['Stderr']])
|
76
|
+
[true, res['Stdout']]
|
77
|
+
end
|
78
|
+
|
79
|
+
def show_result(is_success, input, result, expected)
|
80
|
+
print detail_str(input, result, expected)
|
81
|
+
puts result_str(is_success, result, expected)
|
82
|
+
end
|
83
|
+
|
84
|
+
def detail_str(input, result, expected)
|
85
|
+
ret = ''
|
86
|
+
ret += "-- input --\n"
|
87
|
+
ret += input
|
88
|
+
ret += "-- expected --\n"
|
89
|
+
ret += expected
|
90
|
+
ret += "-- result --\n"
|
91
|
+
ret += result
|
92
|
+
ret
|
93
|
+
end
|
94
|
+
|
95
|
+
def result_str(is_success, result, expected)
|
96
|
+
if !is_success
|
97
|
+
'!!!!! RE !!!!!'.red
|
98
|
+
elsif result != expected
|
99
|
+
'!!!!! WA !!!!!'.red
|
100
|
+
else
|
101
|
+
'<< OK >>'.green
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def which_os
|
106
|
+
@which_os ||= begin
|
107
|
+
case RbConfig::CONFIG['host_os']
|
108
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
109
|
+
:windows
|
110
|
+
when /darwin|mac os/
|
111
|
+
:macosx
|
112
|
+
when /linux/
|
113
|
+
:linux
|
114
|
+
when /solaris|bsd/
|
115
|
+
:unix
|
116
|
+
else
|
117
|
+
:unknown
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
module TestRunner
|
5
|
+
# run test cases for the specified program with judge input/output.
|
6
|
+
class Judge < Base
|
7
|
+
include PathUtil
|
8
|
+
|
9
|
+
attr_reader :data_dir, :result_dir
|
10
|
+
|
11
|
+
def initialize(ctx)
|
12
|
+
super(ctx)
|
13
|
+
@data_dir = cases_dir(dir)
|
14
|
+
@result_dir = cases_dir(tmp_dir(path))
|
15
|
+
end
|
16
|
+
|
17
|
+
def judge_all
|
18
|
+
puts "***** judge_all #{prg} (#{test_loc}) *****"
|
19
|
+
Dir["#{data_dir}/#{q}/in/*.txt"].sort.each do |infile|
|
20
|
+
id = File.basename(infile, '.txt')
|
21
|
+
judge(id, false)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def judge_one(id)
|
26
|
+
puts "***** judge_one #{prg} (#{test_loc}) *****"
|
27
|
+
judge(id, true)
|
28
|
+
end
|
29
|
+
|
30
|
+
def judge(id, detail = true)
|
31
|
+
@detail = detail
|
32
|
+
infile = "#{data_dir}/#{q}/in/#{id}.txt"
|
33
|
+
outfile = "#{result_dir}/#{q}/result/#{id}.txt"
|
34
|
+
expfile = "#{data_dir}/#{q}/out/#{id}.txt"
|
35
|
+
run_test(id, infile, outfile, expfile)
|
36
|
+
end
|
37
|
+
|
38
|
+
def show_result(is_success, input, result, expected)
|
39
|
+
print detail_str(input, result, expected) if @detail
|
40
|
+
puts result_str(is_success, result, expected)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
module TestRunner
|
5
|
+
# run test cases for the specified program with sample input/output.
|
6
|
+
class Sample < Base
|
7
|
+
include PathUtil
|
8
|
+
|
9
|
+
attr_reader :data_dir
|
10
|
+
|
11
|
+
def initialize(ctx)
|
12
|
+
super(ctx)
|
13
|
+
@data_dir = smp_dir(dir)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_all
|
17
|
+
puts "***** test_all #{prg} (#{test_loc}) *****"
|
18
|
+
1.upto(999) do |i|
|
19
|
+
break unless test(i)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_one(n)
|
24
|
+
puts "***** test_one #{prg} (#{test_loc}) *****"
|
25
|
+
test(n)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test(n)
|
29
|
+
id = format('%<q>s_%<n>03d', q: q, n: n)
|
30
|
+
files = %w[in out exp].map { |ext| "#{data_dir}/#{id}.#{ext}" }
|
31
|
+
run_test(id, *files)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -5,19 +5,21 @@ require 'fileutils'
|
|
5
5
|
module AtCoderFriends
|
6
6
|
# marks and checks if the source has been verified.
|
7
7
|
class Verifier
|
8
|
+
include PathUtil
|
9
|
+
|
8
10
|
attr_reader :path, :file, :vdir, :vpath
|
9
11
|
|
10
12
|
def initialize(ctx)
|
11
13
|
@path = ctx.path
|
12
14
|
@file = File.basename(path)
|
13
|
-
@vdir =
|
15
|
+
@vdir = tmp_dir(path)
|
14
16
|
@vpath = File.join(vdir, "#{file}.verified")
|
15
17
|
end
|
16
18
|
|
17
19
|
def verify
|
18
20
|
return unless File.exist?(path)
|
19
21
|
|
20
|
-
|
22
|
+
makedirs_unless(vdir)
|
21
23
|
FileUtils.touch(vpath)
|
22
24
|
end
|
23
25
|
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
require 'json'
|
6
|
+
require 'mechanize'
|
7
|
+
require 'at_coder_friends'
|
8
|
+
|
9
|
+
module AtCoderFriends
|
10
|
+
# tasks for regression test
|
11
|
+
module Regression
|
12
|
+
module_function
|
13
|
+
|
14
|
+
CONTEST_LIST_URL = 'https://kenkoooo.com/atcoder/resources/contests.json'
|
15
|
+
ACF_HOME = File.realpath(File.join(__dir__, '..'))
|
16
|
+
REGRESSION_HOME = File.join(ACF_HOME, 'regression')
|
17
|
+
PAGES_DIR = File.join(REGRESSION_HOME, 'pages')
|
18
|
+
EMIT_ORG_DIR = File.join(REGRESSION_HOME, 'emit_org')
|
19
|
+
EMIT_DIR_FMT = File.join(REGRESSION_HOME, 'emit_%<now>s')
|
20
|
+
|
21
|
+
def setup
|
22
|
+
rmdir_force(PAGES_DIR)
|
23
|
+
rmdir_force(EMIT_ORG_DIR)
|
24
|
+
contest_id_list.each do |contest|
|
25
|
+
begin
|
26
|
+
ctx = context(EMIT_ORG_DIR, contest)
|
27
|
+
ctx.scraping_agent.fetch_all do |pbm|
|
28
|
+
begin
|
29
|
+
html_path = File.join(PAGES_DIR, contest, "#{pbm.q}.html")
|
30
|
+
save_file(html_path, pbm.page.body)
|
31
|
+
pipeline(ctx, pbm)
|
32
|
+
rescue StandardError => e
|
33
|
+
p e
|
34
|
+
end
|
35
|
+
end
|
36
|
+
rescue StandardError => e
|
37
|
+
p e
|
38
|
+
end
|
39
|
+
sleep 3
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def check_diff
|
44
|
+
emit_dir = format(EMIT_DIR_FMT, now: Time.now.strftime('%Y%m%d%H%M%S'))
|
45
|
+
rmdir_force(emit_dir)
|
46
|
+
|
47
|
+
local_pbm_list.each do |contest, q, url|
|
48
|
+
ctx = context(emit_dir, contest)
|
49
|
+
pbm = ctx.scraping_agent.fetch_problem(q, url)
|
50
|
+
pipeline(ctx, pbm)
|
51
|
+
end
|
52
|
+
|
53
|
+
system("diff -r #{EMIT_ORG_DIR} #{emit_dir}")
|
54
|
+
end
|
55
|
+
|
56
|
+
def section_list
|
57
|
+
agent = Mechanize.new
|
58
|
+
list = local_pbm_list.flat_map do |contest, q, url|
|
59
|
+
page = agent.get(url)
|
60
|
+
%w[h2 h3].flat_map do
|
61
|
+
page.search('h3').map do |h3|
|
62
|
+
{ contest: contest, q: q, text: normalize(h3.content) }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
list.group_by { |sec| sec[:text] }.each do |k, vs|
|
67
|
+
puts [k, vs.size, vs[0][:contest], vs[0][:q]].join("\t")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def check_parse
|
72
|
+
parse_list
|
73
|
+
.select { |_, _, no_fmt, no_smp| no_fmt || no_smp }
|
74
|
+
.map { |c, q, no_fmt, no_smp| [c, q, f_to_s(no_fmt), f_to_s(no_smp)] }
|
75
|
+
.sort
|
76
|
+
.each { |args| puts args.join("\t") }
|
77
|
+
end
|
78
|
+
|
79
|
+
def parse_list
|
80
|
+
local_pbm_list.map do |contest, q, url|
|
81
|
+
ctx = context('', contest)
|
82
|
+
pbm = ctx.scraping_agent.fetch_problem(q, url)
|
83
|
+
Parser::Main.process(pbm)
|
84
|
+
no_fmt = pbm.fmt.empty?
|
85
|
+
no_smp = pbm.smps.all? { |smp| smp.txt.empty? }
|
86
|
+
[contest, q, no_fmt, no_smp]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def f_to_s(f)
|
91
|
+
f ? '◯' : '-'
|
92
|
+
end
|
93
|
+
|
94
|
+
def contest_id_list
|
95
|
+
uri = URI.parse(CONTEST_LIST_URL)
|
96
|
+
json = Net::HTTP.get(uri)
|
97
|
+
contests = JSON.parse(json)
|
98
|
+
puts "Total #{contests.size} contests"
|
99
|
+
contests.map { |h| h['id'] }
|
100
|
+
end
|
101
|
+
|
102
|
+
def local_pbm_list
|
103
|
+
Dir.glob(PAGES_DIR + '/**/*.html').map do |pbm_path|
|
104
|
+
contest = File.basename(File.dirname(pbm_path))
|
105
|
+
q = File.basename(pbm_path, '.html')
|
106
|
+
url = "file://#{pbm_path}"
|
107
|
+
[contest, q, url]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def context(root, contest)
|
112
|
+
Context.new({}, File.join(root, contest))
|
113
|
+
end
|
114
|
+
|
115
|
+
def rmdir_force(dir)
|
116
|
+
FileUtils.rm_r(dir) if Dir.exist?(dir)
|
117
|
+
end
|
118
|
+
|
119
|
+
def save_file(path, content)
|
120
|
+
dir = File.dirname(path)
|
121
|
+
FileUtils.makedirs(dir) unless Dir.exist?(dir)
|
122
|
+
File.binwrite(path, content)
|
123
|
+
end
|
124
|
+
|
125
|
+
def pipeline(ctx, pbm)
|
126
|
+
@rb_gen ||= RubyGenerator.new
|
127
|
+
@cxx_gen ||= CxxGenerator.new
|
128
|
+
Parser::Main.process(pbm)
|
129
|
+
@rb_gen.process(pbm)
|
130
|
+
@cxx_gen.process(pbm)
|
131
|
+
ctx.emitter.emit(pbm)
|
132
|
+
end
|
133
|
+
|
134
|
+
def normalize(s)
|
135
|
+
s
|
136
|
+
.tr(' 0-9A-Za-z', ' 0-9A-Za-z')
|
137
|
+
.gsub(/[^一-龠_ぁ-ん_ァ-ヶーa-zA-Z0-9 ]/, '')
|
138
|
+
.strip
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
namespace :regression do
|
144
|
+
desc 'setup regression environment'
|
145
|
+
task :setup do
|
146
|
+
AtCoderFriends::Regression.setup
|
147
|
+
end
|
148
|
+
|
149
|
+
desc 'run regression check'
|
150
|
+
task :check_diff do
|
151
|
+
AtCoderFriends::Regression.check_diff
|
152
|
+
end
|
153
|
+
|
154
|
+
desc 'list all section titles'
|
155
|
+
task :section_list do
|
156
|
+
AtCoderFriends::Regression.section_list
|
157
|
+
end
|
158
|
+
|
159
|
+
desc 'checks page parse result'
|
160
|
+
task :check_parse do
|
161
|
+
AtCoderFriends::Regression.check_parse
|
162
|
+
end
|
163
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: at_coder_friends
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- nejiko96
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-10-
|
11
|
+
date: 2019-10-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colorize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.8.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.8.1
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: launchy
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -145,16 +159,25 @@ files:
|
|
145
159
|
- lib/at_coder_friends/cxx_generator.rb
|
146
160
|
- lib/at_coder_friends/emitter.rb
|
147
161
|
- lib/at_coder_friends/errors.rb
|
148
|
-
- lib/at_coder_friends/
|
149
|
-
- lib/at_coder_friends/
|
162
|
+
- lib/at_coder_friends/parser/constraints_parser.rb
|
163
|
+
- lib/at_coder_friends/parser/format_parser.rb
|
164
|
+
- lib/at_coder_friends/parser/main.rb
|
165
|
+
- lib/at_coder_friends/parser/page_parser.rb
|
150
166
|
- lib/at_coder_friends/path_util.rb
|
151
167
|
- lib/at_coder_friends/problem.rb
|
152
168
|
- lib/at_coder_friends/ruby_generator.rb
|
153
|
-
- lib/at_coder_friends/
|
154
|
-
- lib/at_coder_friends/
|
155
|
-
- lib/at_coder_friends/
|
169
|
+
- lib/at_coder_friends/scraping/agent.rb
|
170
|
+
- lib/at_coder_friends/scraping/authentication.rb
|
171
|
+
- lib/at_coder_friends/scraping/custom_test.rb
|
172
|
+
- lib/at_coder_friends/scraping/session.rb
|
173
|
+
- lib/at_coder_friends/scraping/submission.rb
|
174
|
+
- lib/at_coder_friends/scraping/tasks.rb
|
175
|
+
- lib/at_coder_friends/test_runner/base.rb
|
176
|
+
- lib/at_coder_friends/test_runner/judge.rb
|
177
|
+
- lib/at_coder_friends/test_runner/sample.rb
|
156
178
|
- lib/at_coder_friends/verifier.rb
|
157
179
|
- lib/at_coder_friends/version.rb
|
180
|
+
- tasks/regression.rake
|
158
181
|
homepage: https://github.com/nejiko96/at_coder_friends
|
159
182
|
licenses:
|
160
183
|
- MIT
|