PR #2 opened by Kacper Michajłow (kasper93)
URL: https://code.ffmpeg.org/FFmpeg/fateserver/pulls/2
Patch URL: https://code.ffmpeg.org/FFmpeg/fateserver/pulls/2.patch


From e2901da35bbf0e61d42df13dc3288593e25407dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Thu, 30 Jul 2026 01:03:02 +0200
Subject: [PATCH 1/6] FATE: accept a "desc" prefix on any sort key
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The prefix shared the 10 character budget of the key it reverses, so the
links the index itself generates for the Comment and Arch columns were
rejected with "Invalid sort value".

Signed-off-by: Kacper Michajłow <[email protected]>
---
 FATE.pm | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/FATE.pm b/FATE.pm
index d74fd00..804dcd2 100644
--- a/FATE.pm
+++ b/FATE.pm
@@ -489,7 +489,9 @@ sub safeparam($$) {
 }
 
 sub safeparam_sort() {
-    return safeparam_opt qr/[a-z]{1,10}(?:\/\/[a-z]{1,10})*/, 'sort';
+    # The "desc" prefix reversing a key must not eat into its length budget.
+    my $key = qr/(?:desc)?[a-z]{1,10}/;
+    return safeparam_opt qr/$key(?:\/\/$key)*/, 'sort';
 }
 
 sub safeparam_slot() {
-- 
2.52.0


From 4af3b82eba59fff9de0950c02ca7a4fe4ecd0d0b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Thu, 30 Jul 2026 01:03:26 +0200
Subject: [PATCH 2/6] index: keep the sort direction per key
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The loop variable aliases the array element, so stripping "desc" edited
@sort in place and left $sdir reversed for every remaining key and every
later comparison.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 index.cgi | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/index.cgi b/index.cgi
index 04b6cbd..4373679 100755
--- a/index.cgi
+++ b/index.cgi
@@ -84,7 +84,6 @@ $allfail = 100 * $allfail / @reps;
 my $warn = 100 - $allpass - $allfail;
 
 my @sort = ('subarch', 'os', 'cc', 'comment', 'slot');
-my $sdir = 1; # default to ascending sorting
 defined $sort and unshift @sort, split /\/\//, $sort;
 $sort ||= $sort[0];
 
@@ -94,15 +93,13 @@ sub nscmp {
 }
 
 sub repcmp {
-    my $r;
-    for my $s (@sort) {
-        if ($s =~ /^desc/) {
-            $s =~ s/^desc//;
-            $sdir = -1;
-        }
-        last if $r = $sdir * nscmp $$a{$s}, $$b{$s};
+    for my $key (@sort) {
+        # $key aliases the array element, so do not edit it in place.
+        (my $s = $key) =~ s/^desc//;
+        my $r = ($s eq $key ? 1 : -1) * nscmp $$a{$s}, $$b{$s};
+        return $r if $r;
     }
-    return $r;
+    return 0;
 };
 
 sub lsort {
-- 
2.52.0


From 4c227e8de941374ac04c2bc2be84b861894a6844 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Thu, 30 Jul 2026 01:04:06 +0200
Subject: [PATCH 3/6] index: let the Arch column sort in both directions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

"arch" is rewritten to "subarch" before lsort compares it against the
current key, so the header never matched and never offered the reverse
order.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 index.cgi | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/index.cgi b/index.cgi
index 4373679..9c9fe6c 100755
--- a/index.cgi
+++ b/index.cgi
@@ -237,7 +237,7 @@ end 'tr';
 start 'tr';
 start 'th'; lsort 'Time',     'descdate';      end 'th';
 start 'th'; lsort 'Rev',      'rev';           end 'th';
-start 'th'; lsort 'Arch',     'arch';          end 'th';
+start 'th'; lsort 'Arch',     'subarch';       end 'th';
 start 'th'; lsort 'OS',       'os';            end 'th';
 start 'th'; lsort 'Compiler', 'cc';            end 'th';
 start 'th'; lsort 'Comment',  'comment';       end 'th';
-- 
2.52.0


From 275ab9a6144fed081e807658d725ccfd901a04df Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Thu, 30 Jul 2026 01:05:53 +0200
Subject: [PATCH 4/6] index: allow "/" and "%" in search criteria
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

CGI already percent-decodes each parameter, so decoding the query again
turned an escaped "/" inside a value back into the "//" separator and tore
the criterion in two.

Pass one criterion per parameter instead: CGI splits on "&" before decoding,
so a value can no longer be mistaken for a separator. Escape values as
bytes, as uri_escape_utf8 double-encodes the bytes CGI and the report files
actually hand out.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 index.cgi | 86 +++++++++++++++++++++++++++----------------------------
 1 file changed, 43 insertions(+), 43 deletions(-)

diff --git a/index.cgi b/index.cgi
index 9c9fe6c..1df8c89 100755
--- a/index.cgi
+++ b/index.cgi
@@ -20,7 +20,7 @@ use warnings;
 
 use lib "/var/www/fateweb";
 
-use CGI qw/param/;
+use CGI qw/param multi_param/;
 use HTML::Entities;
 use FATE;
 use Time::Zone;
@@ -28,12 +28,17 @@ use URI::Escape;
 
 cgi_path_is_trustworthy;
 
-# Format for /?query= : /?query=type:value//type:value// (URI encoded).
-# Trailing // does not matter (i.e. may be added).
-# @queries contains an array of 'type:value' strings.
-# Every member of @queries can be further parsed with another simple
-# split(/:/, $this_query, 2);
-my @queries = split(/\/\//, uri_unescape scalar param 'query') if (param 
'query');
+# Format for /?query= : /?query=type:value, repeated once per criterion, as an
+# array of [type, value] pairs.  CGI splits the parameters before decoding
+# them, so a value may contain any character; decoding again here would let a
+# '/' in a value act as a separator.
+my @queries;
+for my $this_query (multi_param 'query') {
+    my ($type, $text) = split(/:/, $this_query, 2);
+    defined $text or next;
+    ($type) = $type =~ /^([a-z]{1,16})\z/ or next;
+    push @queries, [$type, $text];
+}
 
 my $sort = safeparam_sort;
 $sort = "subarch" if defined($sort) && $sort eq "arch";
@@ -55,8 +60,8 @@ for my $slot (@slots) {
     my $not_matched = 0;
     $$rep{subarch} = $$rep{arch} if not $$rep{subarch};
     for my $this_query (@queries) {
-        my ($type, $text) = split(/:/, $this_query, 2);
-        $not_matched = 1 if ($$rep{$type} ne $text);
+        my ($type, $text) = @$this_query;
+        $not_matched = 1 if not defined $$rep{$type} or $$rep{$type} ne $text;
     }
     next if $not_matched;
 
@@ -102,16 +107,29 @@ sub repcmp {
     return 0;
 };
 
-sub lsort {
-    my $params = '';
+# Every parameter except 'query' and, optionally, $skip, in HTTP format.
+# 'query' may occur several times and is regenerated by query_params().
+sub other_params {
+    my ($skip) = @_;
+    my @params;
     for my $thisparam (param) {
         next if $thisparam =~ /[^a-z0-9_]/;
-        next if $thisparam =~ 'sort';
-        $params .= '&' if $params ne '';
-        $params .= "$thisparam=" . uri_escape(param($thisparam));
+        next if $thisparam eq 'query';
+        next if defined $skip and $thisparam eq $skip;
+        push @params, "$thisparam=" . uri_escape(scalar param($thisparam));
     }
-    $params .= '&' if $params;
+    return @params;
+}
+
+# Serialise [type, value] pairs as repeated 'query' parameters.  Values are
+# bytes, as CGI and the report files give them, so escape them as bytes.
+sub query_params {
+    return map "query=" . uri_escape("$$_[0]:" . ($$_[1] // '')), @_;
+}
+
+sub lsort {
     my ($text, $key) = @_;
+    my @params = (other_params('sort'), query_params(@queries));
 
     my $newkey = '';
     if ($sort eq $key) {                           # $key     = $sort
@@ -130,39 +148,21 @@ sub lsort {
     }
 
     $key = $newkey if $newkey ne '';
-    anchor $text, href => "?${params}sort=$key";
+    push @params, "sort=$key";
+    anchor $text, href => '?' . join '&amp;', @params;
 }
 
 sub category {
     my ($category, $rep) = @_;
-    my $head_printed = 0;
 
-    # $params will contain parameters else than query, if any, in HTTP format.
-    my $params = '';
-    for my $thisparam (param) {
-        next if $thisparam =~ /[^a-z0-9_]/;
-        next if $thisparam eq 'query';
-        $params .= '&' if $params ne '';
-        $params .= "$thisparam=" . uri_escape(param($thisparam));
-    }
-    my $head = ($params ? '&' : '') . 'query=';
-
-    if (@queries) {
-        for my $this_query (@queries) {
-            my ($type, $text) = split(/:/, $this_query, 2);
-            if ($type ne $category) {
-                $params .= $head if (!$head_printed);
-                $params .= $this_query . '//';
-                $head_printed = 1;
-            }
-        }
-    }
-    $params .= $head if (!$head_printed);
-    $params .= "$category:" . uri_escape_utf8 "$$rep{$category}" . '//';
-    $head_printed = 1;                 # for the sake of completeness
+    # Keep every search criterion except the one for $category, which the
+    # value in this cell replaces.
+    my @params = (other_params(),
+                  query_params(grep($$_[0] ne $category, @queries),
+                               [$category, $$rep{$category}]));
 
     start 'td';
-    anchor $$rep{$category}, href => "?$params";
+    anchor $$rep{$category}, href => '?' . join '&amp;', @params;
     end 'td';
 }
 
@@ -197,8 +197,8 @@ if (@queries) {
     start 'p';
     print 'Search patterns: ';
     for my $this_query (@queries) {
-        my ($type, $text) = split(/:/, $this_query, 2);
-        print "$type: $text; ";
+        my ($type, $text) = @$this_query;
+        print encode_entities("$type: $text", '<>&"'), '; ';
     }
     anchor 'clear all.', href => "";
     end 'p';
-- 
2.52.0


From 6ac7749560c28606c26625dcf2319496a938e06c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Thu, 30 Jul 2026 01:06:26 +0200
Subject: [PATCH 5/6] index: round the percentages in the failometer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

They land in a CSS width and a tooltip, where 17 significant digits are of
no use.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 index.cgi | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/index.cgi b/index.cgi
index 1df8c89..25ed088 100755
--- a/index.cgi
+++ b/index.cgi
@@ -84,9 +84,17 @@ for my $slot (@slots) {
                          "<a href=\"\">Clear all search criteria.</a>" :
                          'No data in $fatedir.';
 
-$allpass = 100 * $allpass / @reps;
-$allfail = 100 * $allfail / @reps;
-my $warn = 100 - $allpass - $allfail;
+# Percentage of $n out of $total, rounded to one decimal for display.
+sub pct {
+    my ($n, $total) = @_;
+    return int(1000 * $n / $total + 0.5) / 10;
+}
+
+my $nreps = @reps;
+my $warn = $nreps - $allpass - $allfail;
+$allpass = pct($allpass, $nreps);
+$allfail = pct($allfail, $nreps);
+$warn    = pct($warn,    $nreps);
 
 my @sort = ('subarch', 'os', 'cc', 'comment', 'slot');
 defined $sort and unshift @sort, split /\/\//, $sort;
-- 
2.52.0


From 4b23f6da3b3b1119958b7f2c0011a1ed7028d5cd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Thu, 30 Jul 2026 01:06:59 +0200
Subject: [PATCH 6/6] index, history: show a failure to build tests as a
 failure
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

A run where every test passed but the status is non-zero failed to build
the tests, which report.cgi already flags as a failure.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 history.cgi | 3 ++-
 index.cgi   | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/history.cgi b/history.cgi
index 5e4f007..71ecdbb 100755
--- a/history.cgi
+++ b/history.cgi
@@ -76,7 +76,8 @@ for my $date ((sort { $b cmp $a } @reps)[0..49]) {
     td $$rep{nwarn};
     if ($npass) {
         $rtext  = "$npass / $ntest";
-        $rclass = $$rep{status}==0? 'pass' : $npass? 'warn' : 'fail';
+        $rclass = !$$rep{status}  ? 'pass' :
+                  $npass < $ntest ? 'warn' : 'fail';
     } elsif (!$ntest and !$$rep{status}) {
         $rtext  = "build only";
         $rclass = $$rep{status}? 'fail' : 'pass';
diff --git a/index.cgi b/index.cgi
index 25ed088..d38b999 100755
--- a/index.cgi
+++ b/index.cgi
@@ -293,7 +293,8 @@ for my $rep (sort repcmp @reps) {
     td $$rep{comment}, class => 'comment';
     if ($npass) {
         $rtext  = "$npass / $ntest";
-        $rclass = $$rep{status}==0? 'pass' : $npass? 'warn' : 'fail';
+        $rclass = !$$rep{status}  ? 'pass' :
+                  $npass < $ntest ? 'warn' : 'fail';
     } elsif (!$ntest and !$$rep{status}) {
         $rtext  = "build only";
         $rclass = $$rep{status}? 'fail' : 'pass';
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to