Commit d17d7469 authored by Alexandre Duret-Lutz's avatar Alexandre Duret-Lutz
Browse files

ltlcross: allow appending to files via >>FILENAME

Suggested by Joachim Klein.  Fixes #57.

* src/bin/common_file.hh, src/bin/common_file.cc: New file.
* src/bin/Makefile.am: Build them.
* src/bin/ltlcross.cc: Use the output_file class.
* src/tgbatest/ltlcross3.test: Test >>.
* NEWS: mention it.
parent 7daea8d3
...@@ -47,6 +47,13 @@ New in spot 1.99a (not yet released) ...@@ -47,6 +47,13 @@ New in spot 1.99a (not yet released)
'ltlcross "spin -f %s>%N" ...'. This feature is much 'ltlcross "spin -f %s>%N" ...'. This feature is much
more useful for ltldo. more useful for ltldo.
- For options that take an output filename (i.e., ltlcross's
--save-bogus, --grind, --csv, --json) you can force the file
to be open in append mode (instead of being truncated) by
by prefix the filename with ">>". For instance:
--save-bogus=">>bugs.ltl"
will append to the end of the file.
- There is a parser for the HOA format - There is a parser for the HOA format
(http://adl.github.io/hoaf/) available as a (http://adl.github.io/hoaf/) available as a
spot::hoa_stream_parser object or spot::hoa_parse() function. spot::hoa_stream_parser object or spot::hoa_parse() function.
......
...@@ -32,6 +32,8 @@ libcommon_a_SOURCES = \ ...@@ -32,6 +32,8 @@ libcommon_a_SOURCES = \
common_conv.cc \ common_conv.cc \
common_cout.hh \ common_cout.hh \
common_cout.cc \ common_cout.cc \
common_file.cc \
common_file.hh \
common_finput.cc \ common_finput.cc \
common_finput.hh \ common_finput.hh \
common_output.cc \ common_output.cc \
......
// -*- coding: utf-8 -*-
// Copyright (C) 2015 Laboratoire de Recherche et Développement de
// l'Epita (LRDE).
//
// This file is part of Spot, a model checking library.
//
// Spot is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// Spot is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "common_file.hh"
#include <error.h>
#include <iostream>
output_file::output_file(const char* name)
{
std::ios_base::openmode mode = std::ios_base::trunc;
if (name[0] == '>' && name[1] == '>')
{
mode = std::ios_base::app;
append_ = true;
name += 2;
}
if (name[0] == '-' && name[1] == 0)
{
os_ = &std::cout;
return;
}
of_ = new std::ofstream(name, mode);
if (!*of_)
error(2, errno, "cannot open '%s'", name);
os_ = of_;
}
// -*- coding: utf-8 -*-
// Copyright (C) 2015 Laboratoire de Recherche et Développement de
// l'Epita (LRDE).
//
// This file is part of Spot, a model checking library.
//
// Spot is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// Spot is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef SPOT_BIN_COMMON_FILE_HH
#define SPOT_BIN_COMMON_FILE_HH
#include "common_sys.hh"
#include <iosfwd>
#include <fstream>
class output_file
{
std::ostream* os_;
std::ofstream* of_ = nullptr;
bool append_ = false;
public:
// Open a file for output. "-" is interpreted as stdout.
// Names that start with ">>" are opened for append.
// The function calls error() on... error.
output_file(const char* name);
~output_file()
{
delete of_;
}
bool append() const
{
return append_;
}
std::ostream& ostream()
{
return *os_;
}
};
#endif // SPOT_BIN_COMMON_FILE_HH
...@@ -23,7 +23,6 @@ ...@@ -23,7 +23,6 @@
#include <string> #include <string>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <fstream>
#include <cstdlib> #include <cstdlib>
#include <cstdio> #include <cstdio>
#include <argp.h> #include <argp.h>
...@@ -36,6 +35,7 @@ ...@@ -36,6 +35,7 @@
#include "common_cout.hh" #include "common_cout.hh"
#include "common_conv.hh" #include "common_conv.hh"
#include "common_trans.hh" #include "common_trans.hh"
#include "common_file.hh"
#include "common_finput.hh" #include "common_finput.hh"
#include "dstarparse/public.hh" #include "dstarparse/public.hh"
#include "hoaparse/public.hh" #include "hoaparse/public.hh"
...@@ -123,25 +123,29 @@ static const argp_option options[] = ...@@ -123,25 +123,29 @@ static const argp_option options[] =
"averaged unless the number is prefixed with '+'", 0 }, "averaged unless the number is prefixed with '+'", 0 },
/**************************************************/ /**************************************************/
{ 0, 0, 0, 0, "Statistics output:", 7 }, { 0, 0, 0, 0, "Statistics output:", 7 },
{ "json", OPT_JSON, "FILENAME", OPTION_ARG_OPTIONAL, { "json", OPT_JSON, "[>>]FILENAME", OPTION_ARG_OPTIONAL,
"output statistics as JSON in FILENAME or on standard output", 0 }, "output statistics as JSON in FILENAME or on standard output", 0 },
{ "csv", OPT_CSV, "FILENAME", OPTION_ARG_OPTIONAL, { "csv", OPT_CSV, "[>>]FILENAME", OPTION_ARG_OPTIONAL,
"output statistics as CSV in FILENAME or on standard output", 0 }, "output statistics as CSV in FILENAME or on standard output "
"(if '>>' is used to request append mode, the header line is "
"not output)", 0 },
{ "omit-missing", OPT_OMIT, 0, 0, { "omit-missing", OPT_OMIT, 0, 0,
"do not output statistics for timeouts or failed translations", 0 }, "do not output statistics for timeouts or failed translations", 0 },
/**************************************************/ /**************************************************/
{ 0, 0, 0, 0, "Miscellaneous options:", -1 }, { 0, 0, 0, 0, "Miscellaneous options:", -2 },
{ "color", OPT_COLOR, "WHEN", OPTION_ARG_OPTIONAL, { "color", OPT_COLOR, "WHEN", OPTION_ARG_OPTIONAL,
"colorize output; WHEN can be 'never', 'always' (the default if " "colorize output; WHEN can be 'never', 'always' (the default if "
"--color is used without argument), or " "--color is used without argument), or "
"'auto' (the default if --color is not used)", 0 }, "'auto' (the default if --color is not used)", 0 },
{ "grind", OPT_GRIND, "FILENAME", 0, { "grind", OPT_GRIND, "[>>]FILENAME", 0,
"for each formula for which a problem was detected, write a simpler " \ "for each formula for which a problem was detected, write a simpler " \
"formula that fails on the same test in FILENAME", 0 }, "formula that fails on the same test in FILENAME", 0 },
{ "save-bogus", OPT_BOGUS, "FILENAME", 0, { "save-bogus", OPT_BOGUS, "[>>]FILENAME", 0,
"save formulas for which problems were detected in FILENAME", 0 }, "save formulas for which problems were detected in FILENAME", 0 },
{ "verbose", OPT_VERBOSE, 0, 0, { "verbose", OPT_VERBOSE, 0, 0,
"print what is being done, for debugging", 0 }, "print what is being done, for debugging", 0 },
{ 0, 0, 0, 0, "If an output FILENAME is prefixed with '>>', is it open "
"in append mode instead of being truncated.", -1 },
{ 0, 0, 0, 0, 0, 0 } { 0, 0, 0, 0, 0, 0 }
}; };
...@@ -149,7 +153,7 @@ const struct argp_child children[] = ...@@ -149,7 +153,7 @@ const struct argp_child children[] =
{ {
{ &finput_argp, 0, 0, 1 }, { &finput_argp, 0, 0, 1 },
{ &trans_argp, 0, 0, 0 }, { &trans_argp, 0, 0, 0 },
{ &misc_argp, 0, 0, -1 }, { &misc_argp, 0, 0, -2 },
{ 0, 0, 0, 0 } { 0, 0, 0, 0 }
}; };
...@@ -190,8 +194,8 @@ static bool products_avg = true; ...@@ -190,8 +194,8 @@ static bool products_avg = true;
static bool opt_omit = false; static bool opt_omit = false;
static bool has_sr = false; // Has Streett or Rabin automata to process. static bool has_sr = false; // Has Streett or Rabin automata to process.
static const char* bogus_output_filename = 0; static const char* bogus_output_filename = 0;
static std::ofstream* bogus_output = 0; static output_file* bogus_output = 0;
static std::ofstream* grind_output = 0; static output_file* grind_output = 0;
static bool verbose = false; static bool verbose = false;
static bool ignore_exec_fail = false; static bool ignore_exec_fail = false;
static unsigned ignored_exec_fail = 0; static unsigned ignored_exec_fail = 0;
...@@ -398,9 +402,7 @@ parse_opt(int key, char* arg, struct argp_state*) ...@@ -398,9 +402,7 @@ parse_opt(int key, char* arg, struct argp_state*)
break; break;
case OPT_BOGUS: case OPT_BOGUS:
{ {
bogus_output = new std::ofstream(arg); bogus_output = new output_file(arg);
if (!*bogus_output)
error(2, errno, "cannot open '%s'", arg);
bogus_output_filename = arg; bogus_output_filename = arg;
break; break;
} }
...@@ -423,9 +425,7 @@ parse_opt(int key, char* arg, struct argp_state*) ...@@ -423,9 +425,7 @@ parse_opt(int key, char* arg, struct argp_state*)
allow_dups = true; allow_dups = true;
break; break;
case OPT_GRIND: case OPT_GRIND:
grind_output = new std::ofstream(arg); grind_output = new output_file(arg);
if (!*grind_output)
error(2, errno, "cannot open '%s'", arg);
break; break;
case OPT_IGNORE_EXEC_FAIL: case OPT_IGNORE_EXEC_FAIL:
ignore_exec_fail = true; ignore_exec_fail = true;
...@@ -894,7 +894,7 @@ namespace ...@@ -894,7 +894,7 @@ namespace
int res = process_formula(f, filename, linenum); int res = process_formula(f, filename, linenum);
if (res && bogus_output) if (res && bogus_output)
*bogus_output << input << std::endl; bogus_output->ostream() << input << std::endl;
if (res && grind_output) if (res && grind_output)
{ {
std::string bogus = input; std::string bogus = input;
...@@ -935,7 +935,7 @@ namespace ...@@ -935,7 +935,7 @@ namespace
else else
bogus = spot::ltl::to_string(f); bogus = spot::ltl::to_string(f);
if (bogus_output) if (bogus_output)
*bogus_output << bogus << std::endl; bogus_output->ostream() << bogus << std::endl;
} }
} }
std::cerr << "Smallest bogus mutation found for "; std::cerr << "Smallest bogus mutation found for ";
...@@ -951,7 +951,7 @@ namespace ...@@ -951,7 +951,7 @@ namespace
if (color_opt) if (color_opt)
std::cerr << reset_color; std::cerr << reset_color;
std::cerr << ".\n\n"; std::cerr << ".\n\n";
*grind_output << bogus << std::endl; grind_output->ostream() << bogus << std::endl;
} }
f->destroy(); f->destroy();
...@@ -1272,39 +1272,33 @@ print_stats_csv(const char* filename) ...@@ -1272,39 +1272,33 @@ print_stats_csv(const char* filename)
if (verbose) if (verbose)
std::cerr << "info: writing CSV to " << filename << '\n'; std::cerr << "info: writing CSV to " << filename << '\n';
std::ofstream* outfile = 0; output_file outf(filename);
std::ostream* out; std::ostream& out = outf.ostream();
if (!strncmp(filename, "-", 2))
{
out = &std::cout;
}
else
{
out = outfile = new std::ofstream(filename);
if (!*outfile)
error(2, errno, "cannot open '%s'", filename);
}
unsigned ntrans = translators.size(); unsigned ntrans = translators.size();
unsigned rounds = vstats.size(); unsigned rounds = vstats.size();
assert(rounds == formulas.size()); assert(rounds == formulas.size());
*out << "\"formula\",\"tool\","; if (!outf.append())
statistics::fields(*out, !opt_omit, has_sr); {
*out << '\n'; // Do not output the header line if we append to a file.
// (Even if that file was empty initially.)
out << "\"formula\",\"tool\",";
statistics::fields(out, !opt_omit, has_sr);
out << '\n';
}
for (unsigned r = 0; r < rounds; ++r) for (unsigned r = 0; r < rounds; ++r)
for (unsigned t = 0; t < ntrans; ++t) for (unsigned t = 0; t < ntrans; ++t)
if (!opt_omit || vstats[r][t].ok) if (!opt_omit || vstats[r][t].ok)
{ {
*out << '"'; out << '"';
spot::escape_rfc4180(*out, formulas[r]); spot::escape_rfc4180(out, formulas[r]);
*out << "\",\""; out << "\",\"";
spot::escape_rfc4180(*out, translators[t].name); spot::escape_rfc4180(out, translators[t].name);
*out << "\","; out << "\",";
vstats[r][t].to_csv(*out, !opt_omit, has_sr); vstats[r][t].to_csv(out, !opt_omit, has_sr);
*out << '\n'; out << '\n';
} }
delete outfile;
} }
static void static void
...@@ -1313,56 +1307,44 @@ print_stats_json(const char* filename) ...@@ -1313,56 +1307,44 @@ print_stats_json(const char* filename)
if (verbose) if (verbose)
std::cerr << "info: writing JSON to " << filename << '\n'; std::cerr << "info: writing JSON to " << filename << '\n';
std::ofstream* outfile = 0; output_file outf(filename);
std::ostream* out; std::ostream& out = outf.ostream();
if (!strncmp(filename, "-", 2))
{
out = &std::cout;
}
else
{
out = outfile = new std::ofstream(filename);
if (!*outfile)
error(2, errno, "cannot open '%s'", filename);
}
unsigned ntrans = translators.size(); unsigned ntrans = translators.size();
unsigned rounds = vstats.size(); unsigned rounds = vstats.size();
assert(rounds == formulas.size()); assert(rounds == formulas.size());
*out << "{\n \"tool\": [\n \""; out << "{\n \"tool\": [\n \"";
spot::escape_str(*out, translators[0].name); spot::escape_str(out, translators[0].name);
for (unsigned t = 1; t < ntrans; ++t) for (unsigned t = 1; t < ntrans; ++t)
{ {
*out << "\",\n \""; out << "\",\n \"";
spot::escape_str(*out, translators[t].name); spot::escape_str(out, translators[t].name);
} }
*out << "\"\n ],\n \"formula\": [\n \""; out << "\"\n ],\n \"formula\": [\n \"";
spot::escape_str(*out, formulas[0]); spot::escape_str(out, formulas[0]);
for (unsigned r = 1; r < rounds; ++r) for (unsigned r = 1; r < rounds; ++r)
{ {
*out << "\",\n \""; out << "\",\n \"";
spot::escape_str(*out, formulas[r]); spot::escape_str(out, formulas[r]);
} }
*out << ("\"\n ],\n \"fields\": [\n \"formula\",\"tool\","); out << ("\"\n ],\n \"fields\": [\n \"formula\",\"tool\",");
statistics::fields(*out, !opt_omit, has_sr); statistics::fields(out, !opt_omit, has_sr);
*out << "\n ],\n \"inputs\": [ 0, 1 ],"; out << "\n ],\n \"inputs\": [ 0, 1 ],";
*out << "\n \"results\": ["; out << "\n \"results\": [";
bool notfirst = false; bool notfirst = false;
for (unsigned r = 0; r < rounds; ++r) for (unsigned r = 0; r < rounds; ++r)
for (unsigned t = 0; t < ntrans; ++t) for (unsigned t = 0; t < ntrans; ++t)
if (!opt_omit || vstats[r][t].ok) if (!opt_omit || vstats[r][t].ok)
{ {
if (notfirst) if (notfirst)
*out << ','; out << ',';
notfirst = true; notfirst = true;
*out << "\n [ " << r << ',' << t << ','; out << "\n [ " << r << ',' << t << ',';
vstats[r][t].to_csv(*out, !opt_omit, has_sr, "null"); vstats[r][t].to_csv(out, !opt_omit, has_sr, "null");
*out << " ]"; out << " ]";
} }
*out << "\n ]\n}\n"; out << "\n ]\n}\n";
delete outfile;
} }
int int
...@@ -1451,6 +1433,7 @@ main(int argc, char** argv) ...@@ -1451,6 +1433,7 @@ main(int argc, char** argv)
} }
delete bogus_output; delete bogus_output;
delete grind_output;
if (json_output) if (json_output)
print_stats_json(json_output); print_stats_json(json_output);
......
...@@ -123,17 +123,19 @@ test $q -eq `expr $p + 12` ...@@ -123,17 +123,19 @@ test $q -eq `expr $p + 12`
# Check with Rabin/Streett output # Check with Rabin/Streett output
first="should not be erased"
echo "$first" > bug.txt
run 1 ../../bin/ltlcross "$ltl2tgba -s %f >%N" 'false %f >%D' \ run 1 ../../bin/ltlcross "$ltl2tgba -s %f >%N" 'false %f >%D' \
-f 'X a' --csv=out.csv --save-bogus=bug.txt 2>stderr -f 'X a' --csv=out.csv --save-bogus='>>bug.txt' 2>stderr
q=`sed 's/[^,]//g;q' out.csv | wc -c` q=`sed 's/[^,]//g;q' out.csv | wc -c`
test $q -eq `expr $p + 6`
grep '"exit_status"' out.csv grep '"exit_status"' out.csv
grep '"exit_code"' out.csv grep '"exit_code"' out.csv
test `grep 'error:.*returned exit code 1' stderr | wc -l` -eq 2 test `grep 'error:.*returned exit code 1' stderr | wc -l` -eq 2
test `grep '"exit code",1' out.csv | wc -l` -eq 2 test `grep '"exit code",1' out.csv | wc -l` -eq 2
check_csv out.csv check_csv out.csv
grep 'X a' bug.txt grep 'X a' bug.txt
test "`head -n 1 bug.txt`" = "$first"
test $q -eq `expr $p + 6`
# Support for --ABORT-- in HOA. # Support for --ABORT-- in HOA.
...@@ -143,6 +145,17 @@ grep '"exit_status"' out.csv ...@@ -143,6 +145,17 @@ grep '"exit_status"' out.csv
grep '"exit_code"' out.csv grep '"exit_code"' out.csv
test `grep 'error:.*aborted' stderr | wc -l` -eq 2 test `grep 'error:.*aborted' stderr | wc -l` -eq 2
test `grep '"aborted",-1' out.csv | wc -l` -eq 2 test `grep '"aborted",-1' out.csv | wc -l` -eq 2
test 3 = `wc -l < out.csv`
check_csv out.csv
# The header of CSV file is not output in append mode
run 1 ../../bin/ltlcross 'echo HOA: --ABORT-- %f > %H' \
-f a --csv='>>out.csv' 2>stderr
grep '"exit_status"' out.csv
grep '"exit_code"' out.csv
test `grep 'error:.*aborted' stderr | wc -l` -eq 2
test `grep '"aborted",-1' out.csv | wc -l` -eq 4
test 5 = `wc -l < out.csv`
check_csv out.csv check_csv out.csv
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment