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)
'ltlcross "spin -f %s>%N" ...'. This feature is much
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
(http://adl.github.io/hoaf/) available as a
spot::hoa_stream_parser object or spot::hoa_parse() function.
......
......@@ -32,6 +32,8 @@ libcommon_a_SOURCES = \
common_conv.cc \
common_cout.hh \
common_cout.cc \
common_file.cc \
common_file.hh \
common_finput.cc \
common_finput.hh \
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 @@
#include <string>
#include <iostream>
#include <sstream>
#include <fstream>
#include <cstdlib>
#include <cstdio>
#include <argp.h>
......@@ -36,6 +35,7 @@
#include "common_cout.hh"
#include "common_conv.hh"
#include "common_trans.hh"
#include "common_file.hh"
#include "common_finput.hh"
#include "dstarparse/public.hh"
#include "hoaparse/public.hh"
......@@ -123,25 +123,29 @@ static const argp_option options[] =
"averaged unless the number is prefixed with '+'", 0 },
/**************************************************/
{ 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 },
{ "csv", OPT_CSV, "FILENAME", OPTION_ARG_OPTIONAL,
"output statistics as CSV in FILENAME or on standard output", 0 },
{ "csv", OPT_CSV, "[>>]FILENAME", OPTION_ARG_OPTIONAL,
"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,
"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,
"colorize output; WHEN can be 'never', 'always' (the default if "
"--color is used without argument), or "
"'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 " \
"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 },
{ "verbose", OPT_VERBOSE, 0, 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 }
};
......@@ -149,7 +153,7 @@ const struct argp_child children[] =
{
{ &finput_argp, 0, 0, 1 },
{ &trans_argp, 0, 0, 0 },
{ &misc_argp, 0, 0, -1 },
{ &misc_argp, 0, 0, -2 },
{ 0, 0, 0, 0 }
};
......@@ -190,8 +194,8 @@ static bool products_avg = true;
static bool opt_omit = false;
static bool has_sr = false; // Has Streett or Rabin automata to process.
static const char* bogus_output_filename = 0;
static std::ofstream* bogus_output = 0;
static std::ofstream* grind_output = 0;
static output_file* bogus_output = 0;
static output_file* grind_output = 0;
static bool verbose = false;
static bool ignore_exec_fail = false;
static unsigned ignored_exec_fail = 0;
......@@ -398,9 +402,7 @@ parse_opt(int key, char* arg, struct argp_state*)
break;
case OPT_BOGUS:
{
bogus_output = new std::ofstream(arg);
if (!*bogus_output)
error(2, errno, "cannot open '%s'", arg);
bogus_output = new output_file(arg);
bogus_output_filename = arg;
break;
}
......@@ -423,9 +425,7 @@ parse_opt(int key, char* arg, struct argp_state*)
allow_dups = true;
break;
case OPT_GRIND:
grind_output = new std::ofstream(arg);
if (!*grind_output)
error(2, errno, "cannot open '%s'", arg);
grind_output = new output_file(arg);
break;
case OPT_IGNORE_EXEC_FAIL:
ignore_exec_fail = true;
......@@ -894,7 +894,7 @@ namespace
int res = process_formula(f, filename, linenum);
if (res && bogus_output)
*bogus_output << input << std::endl;
bogus_output->ostream() << input << std::endl;
if (res && grind_output)
{
std::string bogus = input;
......@@ -935,7 +935,7 @@ namespace
else
bogus = spot::ltl::to_string(f);
if (bogus_output)
*bogus_output << bogus << std::endl;
bogus_output->ostream() << bogus << std::endl;
}
}
std::cerr << "Smallest bogus mutation found for ";
......@@ -951,7 +951,7 @@ namespace
if (color_opt)
std::cerr << reset_color;
std::cerr << ".\n\n";
*grind_output << bogus << std::endl;
grind_output->ostream() << bogus << std::endl;
}
f->destroy();
......@@ -1272,39 +1272,33 @@ print_stats_csv(const char* filename)
if (verbose)
std::cerr << "info: writing CSV to " << filename << '\n';
std::ofstream* outfile = 0;
std::ostream* out;
if (!strncmp(filename, "-", 2))
{
out = &std::cout;
}
else
{
out = outfile = new std::ofstream(filename);
if (!*outfile)
error(2, errno, "cannot open '%s'", filename);
}
output_file outf(filename);
std::ostream& out = outf.ostream();
unsigned ntrans = translators.size();
unsigned rounds = vstats.size();
assert(rounds == formulas.size());
*out << "\"formula\",\"tool\",";
statistics::fields(*out, !opt_omit, has_sr);
*out << '\n';
if (!outf.append())
{
// 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 t = 0; t < ntrans; ++t)
if (!opt_omit || vstats[r][t].ok)
{
*out << '"';
spot::escape_rfc4180(*out, formulas[r]);
*out << "\",\"";
spot::escape_rfc4180(*out, translators[t].name);
*out << "\",";
vstats[r][t].to_csv(*out, !opt_omit, has_sr);
*out << '\n';
out << '"';
spot::escape_rfc4180(out, formulas[r]);
out << "\",\"";
spot::escape_rfc4180(out, translators[t].name);
out << "\",";
vstats[r][t].to_csv(out, !opt_omit, has_sr);
out << '\n';
}
delete outfile;
}
static void
......@@ -1313,56 +1307,44 @@ print_stats_json(const char* filename)
if (verbose)
std::cerr << "info: writing JSON to " << filename << '\n';
std::ofstream* outfile = 0;
std::ostream* out;
if (!strncmp(filename, "-", 2))
{
out = &std::cout;
}
else
{
out = outfile = new std::ofstream(filename);
if (!*outfile)
error(2, errno, "cannot open '%s'", filename);
}
output_file outf(filename);
std::ostream& out = outf.ostream();
unsigned ntrans = translators.size();
unsigned rounds = vstats.size();
assert(rounds == formulas.size());
*out << "{\n \"tool\": [\n \"";
spot::escape_str(*out, translators[0].name);
out << "{\n \"tool\": [\n \"";
spot::escape_str(out, translators[0].name);
for (unsigned t = 1; t < ntrans; ++t)
{
*out << "\",\n \"";
spot::escape_str(*out, translators[t].name);
out << "\",\n \"";
spot::escape_str(out, translators[t].name);
}
*out << "\"\n ],\n \"formula\": [\n \"";
spot::escape_str(*out, formulas[0]);
out << "\"\n ],\n \"formula\": [\n \"";
spot::escape_str(out, formulas[0]);
for (unsigned r = 1; r < rounds; ++r)
{
*out << "\",\n \"";
spot::escape_str(*out, formulas[r]);
out << "\",\n \"";
spot::escape_str(out, formulas[r]);
}
*out << ("\"\n ],\n \"fields\": [\n \"formula\",\"tool\",");
statistics::fields(*out, !opt_omit, has_sr);
*out << "\n ],\n \"inputs\": [ 0, 1 ],";
*out << "\n \"results\": [";
out << ("\"\n ],\n \"fields\": [\n \"formula\",\"tool\",");
statistics::fields(out, !opt_omit, has_sr);
out << "\n ],\n \"inputs\": [ 0, 1 ],";
out << "\n \"results\": [";
bool notfirst = false;
for (unsigned r = 0; r < rounds; ++r)
for (unsigned t = 0; t < ntrans; ++t)
if (!opt_omit || vstats[r][t].ok)
{
if (notfirst)
*out << ',';
out << ',';
notfirst = true;
*out << "\n [ " << r << ',' << t << ',';
vstats[r][t].to_csv(*out, !opt_omit, has_sr, "null");
*out << " ]";
out << "\n [ " << r << ',' << t << ',';
vstats[r][t].to_csv(out, !opt_omit, has_sr, "null");
out << " ]";
}
*out << "\n ]\n}\n";
delete outfile;
out << "\n ]\n}\n";
}
int
......@@ -1451,6 +1433,7 @@ main(int argc, char** argv)
}
delete bogus_output;
delete grind_output;
if (json_output)
print_stats_json(json_output);
......
......@@ -123,17 +123,19 @@ test $q -eq `expr $p + 12`
# 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' \
-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`
test $q -eq `expr $p + 6`
grep '"exit_status"' out.csv
grep '"exit_code"' out.csv
test `grep 'error:.*returned exit code 1' stderr | wc -l` -eq 2
test `grep '"exit code",1' out.csv | wc -l` -eq 2
check_csv out.csv
grep 'X a' bug.txt
test $q -eq `expr $p + 6`
test "`head -n 1 bug.txt`" = "$first"
# Support for --ABORT-- in HOA.
......@@ -143,6 +145,17 @@ 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 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
......
Markdown is supported
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