Skip to content

Commit 54cc2c9

Browse files
committed
Initial version of Coverable6
1 parent 1fd1b7d commit 54cc2c9

File tree

1 file changed

+200
-0
lines changed

1 file changed

+200
-0
lines changed

Coverable.p6

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
#!/usr/bin/env perl6
2+
# Copyright © 2016-2017
3+
# Daniel Green <ddgreen@gmail.com>
4+
# Aleks-Daniel Jakimenko-Aleksejev <alex.jakimenko@gmail.com>
5+
#
6+
# This program is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU Affero General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU Affero General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU Affero General Public License
17+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
19+
use lib .;
20+
use Misc;
21+
use Whateverable;
22+
23+
use IRC::Client;
24+
25+
unit class Coverable does Whateverable;
26+
27+
constant TOTAL-TIME = 60 × 3;
28+
29+
sub condense(@list) {
30+
Seq.new( class :: does Iterator {
31+
has $.iterator;
32+
has $!first;
33+
has $!last;
34+
method pull-one() {
35+
if $!iterator {
36+
if (my $pulled := $!iterator.pull-one) =:= IterationEnd {
37+
$!iterator = Nil;
38+
$!first.defined
39+
?? $!first == $!last
40+
?? $!last
41+
!! Range.new($!first,$!last)
42+
!! IterationEnd
43+
}
44+
elsif $pulled ~~ Int:D {
45+
if $!first.defined {
46+
if $pulled == $!last + 1 {
47+
++$!last;
48+
++$!last
49+
while !(($pulled := $!iterator.pull-one) =:= IterationEnd)
50+
&& $pulled ~~ Int:D
51+
&& $pulled == $!last + 1;
52+
}
53+
if $pulled =:= IterationEnd || $pulled ~~ Int:D {
54+
my $value = $!first == $!last
55+
?? $!last
56+
!! Range.new($!first,$!last);
57+
$pulled =:= IterationEnd
58+
?? ($!iterator = Nil)
59+
!! ($!first = $!last = $pulled);
60+
$value
61+
}
62+
else {
63+
die "Cannot handle $pulled.perl()"
64+
}
65+
}
66+
else {
67+
$!first = $!last = $pulled;
68+
self.pull-one
69+
}
70+
}
71+
else {
72+
die "Cannot handle $pulled.perl()"
73+
}
74+
}
75+
else {
76+
IterationEnd
77+
}
78+
}
79+
method is-lazy() { $!iterator.is-lazy }
80+
}.new(iterator => @list.iterator))
81+
}
82+
83+
method help($msg) {
84+
Like this: {$msg.server.current-nick}: f583f22 grep=SETTING say ‘hello’; say ‘world’
85+
}
86+
87+
multi method irc-to-me($msg where { .text ~~ /^ \s* $<config>=\S+ \s+ ['grep=' $<grep>=\S+ \s+]? $<code>=.+ / }) {
88+
my ($value, %additional-files) = self.process: $msg, ~$<config>, ~($<grep> // 'SETTING::'), ~$<code>;
89+
return without $value;
90+
return ($value but Reply($msg)) but FileStore(%additional-files)
91+
}
92+
93+
method process($msg, $config is copy, $grep is copy, $code is copy) {
94+
my $old-dir = $*CWD; # TODO not needed because we don't chdir anywhere?
95+
my $start-time = now;
96+
97+
if $config ~~ /^ [say|sub] $/ {
98+
$msg.reply: Seems like you forgot to specify a revision (will use “HEAD” instead of “$config”);
99+
$code = $config $code;
100+
$config = HEAD
101+
}
102+
103+
my ($commits-status, @commits) = self.get-commits: $config;
104+
return $commits-status unless @commits;
105+
return "Coverable only works with one commit" if +@commits > 1;
106+
107+
my ($succeeded, $code-response) = self.process-code: $code, $msg;
108+
return $code-response unless $succeeded;
109+
$code = $code-response;
110+
111+
my $filename = self.write-code: $code;
112+
113+
my $result;
114+
my %lookup;
115+
my $output = ;
116+
my $commit = @commits[0];
117+
118+
# convert to real ids so we can look up the builds
119+
my $full-commit = self.to-full-commit: $commit;
120+
if not defined $full-commit {
121+
$output = Cannot find this revision;
122+
my @options = <HEAD>;
123+
$output ~= (did you mean “{self.get-short-commit: self.get-similar: $commit, @options}”?)
124+
} elsif not self.build-exists: $full-commit {
125+
$output = No build for this commit
126+
} else { # actually run the code
127+
my $log = "coverage_{now.to-posix[0]}.log";
128+
LEAVE { unlink $log }
129+
130+
%*ENV<MVM_COVERAGE_LOG> = $log;
131+
$result = self.run-snippet: $full-commit, $filename;
132+
%*ENV<MVM_COVERAGE_LOG>:delete;
133+
134+
# TODO shell injection in $grep
135+
my $g = run 'grep', '-P', $grep, $log, :out;
136+
my $s = run 'sort', '--key=2,2', '--key=3n', '-u', :in($g.out), :out;
137+
my $colrm = run 'colrm', 1, 5, :in($s.out), :out;
138+
$result<coverage> = $colrm.out.slurp-rest.chomp;
139+
$colrm.out.close;
140+
$output = $result<output>;
141+
if $result<signal> < 0 { # numbers less than zero indicate other weird failures
142+
$output = Cannot test this commit ($output)
143+
} else {
144+
$output ~= «exit code = $result<exit-code>» if $result<exit-code> ≠ 0;
145+
$output ~= «exit signal = {Signal($result<signal>)} ($result<signal>)» if $result<signal> ≠ 0
146+
}
147+
}
148+
my $short-commit = self.get-short-commit: $commit;
149+
$short-commit ~= ({self.get-short-commit: $full-commit}) if $commit eq HEAD;
150+
151+
if now - $start-time > TOTAL-TIME {
152+
return «hit the total time limit of {TOTAL-TIME} seconds»
153+
}
154+
155+
my $short-str = ¦$short-commit: «$output»; # TODO no need for short string (we gist it anyway)
156+
my $long-str = ¦$full-commit: «$output»; # TODO simpler output perhaps?
157+
158+
my %coverage;
159+
for $result<coverage>.split("\n") -> $line {
160+
my ($filename, $lineno) = $line.split(/\s+/);
161+
%coverage{$filename}.push: +$lineno;
162+
}
163+
164+
my $cover-report = | File | Code |\n|--|--|\n;
165+
my $url = "https://github.com/rakudo/rakudo/blob/$full-commit";
166+
# ↓ TODO So we are using RAKUDO, but RAKUDO may not know about some commits *yet*, while
167+
# they may be accessible if you give a hash directly.
168+
my @git = git, --git-dir, {RAKUDO}/.git, --work-tree, RAKUDO;
169+
for %coverage.keys.sort -> $fn {
170+
for condense(%coverage{$fn}) -> $l {
171+
my $ln = 'L' ~ ($l ~~ Int ?? $l !! "$l.min()-L$l.max()");
172+
if $fn.starts-with('SETTING::') or $fn ~~ m|'/Perl6/'| {
173+
my $fname = $fn;
174+
$fname .= substr(9) if $fn.starts-with('SETTING::');
175+
$cover-report ~= "| [$fname#$ln]($url/$fname#$ln) |";
176+
my $sed-range = "{$l.min},{$l.max}p";
177+
# ⚠ TODO don't do this ↓ for every line, do it for every *file*. It will be much faster.
178+
my $proc = run :out, |@git, show, $full-commit:$fname;
179+
my $code = run(:out, :in($proc.out), sed, -n, $sed-range).out.slurp-rest.trim; # TODO trim? or just chomp?
180+
$proc.out.close;
181+
$code .= subst(:g, \n, ```<br>```); # TODO multiline code blocks using github markdown?
182+
$code .= subst(:g, |, \|); # TODO really?
183+
$cover-report ~= ```$code``` |\n; # TODO close properly (see how many ``` are there already)
184+
} else {
185+
$cover-report ~= "| $fn#$ln | |\n"; # TODO write “N/A” instead of having an empty cell?
186+
}
187+
}
188+
}
189+
190+
return $short-str but ProperStr($long-str), %('result.md' => $cover-report); # TODO no need for $short-str as mentioned earlier
191+
192+
LEAVE { # TODO not needed same as $old-dir?
193+
chdir $old-dir;
194+
unlink $filename if defined $filename and $filename.chars > 0
195+
}
196+
}
197+
198+
Coverable.new.selfrun: coverable6, [ /cover6?/, fuzzy-nick(coverable6, 3) ];
199+
200+
# vim: expandtab shiftwidth=4 ft=perl6

0 commit comments

Comments
 (0)