mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 05:13:10 +00:00
Make "time" a job prefix
In particular, this allows `true && time true`, or `true; and time true`, and both `time not true` as well as `not time true` (like bash). time is valid only as job _prefix_, so `true | time true` could call `/bin/time` (same in bash) See discussion in #6442
This commit is contained in:
parent
c1140bc436
commit
3de95038b0
13 changed files with 101 additions and 42 deletions
|
@ -44,6 +44,7 @@
|
||||||
#include "reader.h"
|
#include "reader.h"
|
||||||
#include "redirection.h"
|
#include "redirection.h"
|
||||||
#include "signal.h"
|
#include "signal.h"
|
||||||
|
#include "timer.h"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
#include "wutil.h" // IWYU pragma: keep
|
#include "wutil.h" // IWYU pragma: keep
|
||||||
|
|
||||||
|
@ -1049,6 +1050,7 @@ bool exec_job(parser_t &parser, const shared_ptr<job_t> &j, const job_lineage_t
|
||||||
parser.set_last_statuses(statuses_t::just(status));
|
parser.set_last_statuses(statuses_t::just(status));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
cleanup_t timer = push_timer(j->flags().has_time_prefix);
|
||||||
|
|
||||||
// Get the deferred process, if any. We will have to remember its pipes.
|
// Get the deferred process, if any. We will have to remember its pipes.
|
||||||
autoclose_pipes_t deferred_pipes;
|
autoclose_pipes_t deferred_pipes;
|
||||||
|
|
|
@ -1211,6 +1211,10 @@ highlighter_t::color_array_t highlighter_t::highlight() {
|
||||||
this->color_node(node, highlight_role_t::statement_terminator);
|
this->color_node(node, highlight_role_t::statement_terminator);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case symbol_optional_time: {
|
||||||
|
this->color_node(node, highlight_role_t::operat);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case symbol_plain_statement: {
|
case symbol_plain_statement: {
|
||||||
tnode_t<g::plain_statement> stmt(&parse_tree, &node);
|
tnode_t<g::plain_statement> stmt(&parse_tree, &node);
|
||||||
// Get the decoration from the parent.
|
// Get the decoration from the parent.
|
||||||
|
|
|
@ -52,6 +52,7 @@ enum parse_token_type_t : uint8_t {
|
||||||
symbol_redirection,
|
symbol_redirection,
|
||||||
symbol_optional_background,
|
symbol_optional_background,
|
||||||
symbol_optional_newlines,
|
symbol_optional_newlines,
|
||||||
|
symbol_optional_time,
|
||||||
symbol_end_command,
|
symbol_end_command,
|
||||||
// Terminal types.
|
// Terminal types.
|
||||||
parse_token_type_string,
|
parse_token_type_string,
|
||||||
|
@ -157,12 +158,14 @@ enum parse_job_decoration_t {
|
||||||
parse_job_decoration_none,
|
parse_job_decoration_none,
|
||||||
parse_job_decoration_and,
|
parse_job_decoration_and,
|
||||||
parse_job_decoration_or,
|
parse_job_decoration_or,
|
||||||
parse_job_decoration_time,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Whether a statement is backgrounded.
|
// Whether a statement is backgrounded.
|
||||||
enum parse_optional_background_t { parse_no_background, parse_background };
|
enum parse_optional_background_t { parse_no_background, parse_background };
|
||||||
|
|
||||||
|
// Whether a job is prefixed with "time".
|
||||||
|
enum parse_optional_time_t { parse_optional_time_no_time, parse_optional_time_time };
|
||||||
|
|
||||||
// Parse error code list.
|
// Parse error code list.
|
||||||
enum parse_error_code_t {
|
enum parse_error_code_t {
|
||||||
parse_error_none,
|
parse_error_none,
|
||||||
|
|
|
@ -122,13 +122,14 @@ tnode_t<g::plain_statement> parse_execution_context_t::infinite_recursive_statem
|
||||||
// Here's the statement node we find that's infinite recursive.
|
// Here's the statement node we find that's infinite recursive.
|
||||||
tnode_t<grammar::plain_statement> infinite_recursive_statement;
|
tnode_t<grammar::plain_statement> infinite_recursive_statement;
|
||||||
|
|
||||||
// Get the list of plain statements.
|
// Ignore the jobs variable assigment and "time" prefixes.
|
||||||
// Ignore statements with decorations like 'builtin' or 'command', since those
|
tnode_t<g::statement> statement = first_job.child<2>();
|
||||||
// are not infinite recursion. In particular that is what enables 'wrapper functions'.
|
tnode_t<g::job_continuation> continuation = first_job.child<3>();
|
||||||
tnode_t<g::statement> statement = first_job.child<1>();
|
|
||||||
tnode_t<g::job_continuation> continuation = first_job.child<2>();
|
|
||||||
const null_environment_t nullenv{};
|
const null_environment_t nullenv{};
|
||||||
while (statement) {
|
while (statement) {
|
||||||
|
// Get the list of plain statements.
|
||||||
|
// Ignore statements with decorations like 'builtin' or 'command', since those
|
||||||
|
// are not infinite recursion. In particular that is what enables 'wrapper functions'.
|
||||||
tnode_t<g::plain_statement> plain_statement =
|
tnode_t<g::plain_statement> plain_statement =
|
||||||
statement.try_get_child<g::decorated_statement, 0>()
|
statement.try_get_child<g::decorated_statement, 0>()
|
||||||
.try_get_child<g::plain_statement, 0>();
|
.try_get_child<g::plain_statement, 0>();
|
||||||
|
@ -203,10 +204,10 @@ maybe_t<eval_result_t> parse_execution_context_t::check_end_execution() const {
|
||||||
|
|
||||||
/// Return whether the job contains a single statement, of block type, with no redirections.
|
/// Return whether the job contains a single statement, of block type, with no redirections.
|
||||||
bool parse_execution_context_t::job_is_simple_block(tnode_t<g::job> job_node) const {
|
bool parse_execution_context_t::job_is_simple_block(tnode_t<g::job> job_node) const {
|
||||||
tnode_t<g::statement> statement = job_node.child<1>();
|
tnode_t<g::statement> statement = job_node.child<2>();
|
||||||
|
|
||||||
// Must be no pipes.
|
// Must be no pipes.
|
||||||
if (job_node.child<2>().try_get_child<g::tok_pipe, 0>()) {
|
if (job_node.child<3>().try_get_child<g::tok_pipe, 0>()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -975,8 +976,10 @@ eval_result_t parse_execution_context_t::populate_not_process(
|
||||||
job_t *job, process_t *proc, tnode_t<g::not_statement> not_statement) {
|
job_t *job, process_t *proc, tnode_t<g::not_statement> not_statement) {
|
||||||
auto &flags = job->mut_flags();
|
auto &flags = job->mut_flags();
|
||||||
flags.negate = !flags.negate;
|
flags.negate = !flags.negate;
|
||||||
|
auto optional_time = not_statement.require_get_child<g::optional_time, 2>();
|
||||||
|
if (optional_time.tag() == parse_optional_time_time) flags.has_time_prefix = true;
|
||||||
return this->populate_job_process(
|
return this->populate_job_process(
|
||||||
job, proc, not_statement.require_get_child<g::statement, 2>(),
|
job, proc, not_statement.require_get_child<g::statement, 3>(),
|
||||||
not_statement.require_get_child<g::variable_assignments, 1>());
|
not_statement.require_get_child<g::variable_assignments, 1>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1111,18 +1114,20 @@ eval_result_t parse_execution_context_t::populate_job_from_job_node(
|
||||||
|
|
||||||
// We are going to construct process_t structures for every statement in the job. Get the first
|
// We are going to construct process_t structures for every statement in the job. Get the first
|
||||||
// statement.
|
// statement.
|
||||||
tnode_t<g::statement> statement = job_node.child<1>();
|
tnode_t<g::optional_time> optional_time = job_node.child<0>();
|
||||||
tnode_t<g::variable_assignments> variable_assignments = job_node.child<0>();
|
tnode_t<g::variable_assignments> variable_assignments = job_node.child<1>();
|
||||||
|
tnode_t<g::statement> statement = job_node.child<2>();
|
||||||
|
|
||||||
// Create processes. Each one may fail.
|
// Create processes. Each one may fail.
|
||||||
process_list_t processes;
|
process_list_t processes;
|
||||||
processes.emplace_back(new process_t());
|
processes.emplace_back(new process_t());
|
||||||
|
if (optional_time.tag() == parse_optional_time_time) j->mut_flags().has_time_prefix = true;
|
||||||
eval_result_t result =
|
eval_result_t result =
|
||||||
this->populate_job_process(j, processes.back().get(), statement, variable_assignments);
|
this->populate_job_process(j, processes.back().get(), statement, variable_assignments);
|
||||||
|
|
||||||
// Construct process_ts for job continuations (pipelines), by walking the list until we hit the
|
// Construct process_ts for job continuations (pipelines), by walking the list until we hit the
|
||||||
// terminal (empty) job continuation.
|
// terminal (empty) job continuation.
|
||||||
tnode_t<g::job_continuation> job_cont = job_node.child<2>();
|
tnode_t<g::job_continuation> job_cont = job_node.child<3>();
|
||||||
assert(job_cont);
|
assert(job_cont);
|
||||||
while (auto pipe = job_cont.try_get_child<g::tok_pipe, 0>()) {
|
while (auto pipe = job_cont.try_get_child<g::tok_pipe, 0>()) {
|
||||||
if (result != eval_result_t::ok) {
|
if (result != eval_result_t::ok) {
|
||||||
|
@ -1212,7 +1217,9 @@ eval_result_t parse_execution_context_t::run_1_job(tnode_t<g::job> job_node,
|
||||||
// However, if there are no redirections, then we can just jump into the block directly, which
|
// However, if there are no redirections, then we can just jump into the block directly, which
|
||||||
// is significantly faster.
|
// is significantly faster.
|
||||||
if (job_is_simple_block(job_node)) {
|
if (job_is_simple_block(job_node)) {
|
||||||
tnode_t<g::variable_assignments> variable_assignments = job_node.child<0>();
|
tnode_t<g::optional_time> optional_time = job_node.child<0>();
|
||||||
|
cleanup_t timer = push_timer(optional_time.tag() == parse_optional_time_time);
|
||||||
|
tnode_t<g::variable_assignments> variable_assignments = job_node.child<1>();
|
||||||
const block_t *block = nullptr;
|
const block_t *block = nullptr;
|
||||||
eval_result_t result =
|
eval_result_t result =
|
||||||
this->apply_variable_assignments(nullptr, variable_assignments, &block);
|
this->apply_variable_assignments(nullptr, variable_assignments, &block);
|
||||||
|
@ -1220,7 +1227,7 @@ eval_result_t parse_execution_context_t::run_1_job(tnode_t<g::job> job_node,
|
||||||
if (block) parser->pop_block(block);
|
if (block) parser->pop_block(block);
|
||||||
});
|
});
|
||||||
|
|
||||||
tnode_t<g::statement> statement = job_node.child<1>();
|
tnode_t<g::statement> statement = job_node.child<2>();
|
||||||
const parse_node_t &specific_statement = statement.get_child_node<0>();
|
const parse_node_t &specific_statement = statement.get_child_node<0>();
|
||||||
assert(specific_statement_type_is_redirectable_block(specific_statement));
|
assert(specific_statement_type_is_redirectable_block(specific_statement));
|
||||||
if (result == eval_result_t::ok) {
|
if (result == eval_result_t::ok) {
|
||||||
|
@ -1389,7 +1396,6 @@ eval_result_t parse_execution_context_t::run_job_list(tnode_t<Type> job_list,
|
||||||
static_assert(Type::token == symbol_job_list || Type::token == symbol_andor_job_list,
|
static_assert(Type::token == symbol_job_list || Type::token == symbol_andor_job_list,
|
||||||
"Not a job list");
|
"Not a job list");
|
||||||
|
|
||||||
static std::vector<timer_snapshot_t> active_timers;
|
|
||||||
eval_result_t result = eval_result_t::ok;
|
eval_result_t result = eval_result_t::ok;
|
||||||
while (auto job_conj = job_list.template next_in_list<g::job_conjunction>()) {
|
while (auto job_conj = job_list.template next_in_list<g::job_conjunction>()) {
|
||||||
if (auto reason = check_end_execution()) {
|
if (auto reason = check_end_execution()) {
|
||||||
|
@ -1399,26 +1405,11 @@ eval_result_t parse_execution_context_t::run_job_list(tnode_t<Type> job_list,
|
||||||
|
|
||||||
// Maybe skip the job if it has a leading and/or.
|
// Maybe skip the job if it has a leading and/or.
|
||||||
// Skipping is treated as success.
|
// Skipping is treated as success.
|
||||||
bool timer_started = false;
|
|
||||||
if (get_decorator(job_conj) == parse_job_decoration_time) {
|
|
||||||
active_timers.emplace_back(timer_snapshot_t::take());
|
|
||||||
timer_started = true;
|
|
||||||
}
|
|
||||||
if (should_skip(get_decorator(job_conj))) {
|
if (should_skip(get_decorator(job_conj))) {
|
||||||
result = eval_result_t::ok;
|
result = eval_result_t::ok;
|
||||||
} else {
|
} else {
|
||||||
result = this->run_job_conjunction(job_conj, associated_block);
|
result = this->run_job_conjunction(job_conj, associated_block);
|
||||||
}
|
}
|
||||||
if (timer_started) {
|
|
||||||
auto t1 = active_timers.back();
|
|
||||||
active_timers.pop_back();
|
|
||||||
auto t2 = timer_snapshot_t::take();
|
|
||||||
|
|
||||||
// Well, this is awkward. By defining `time` as a decorator and not a built-in, there's
|
|
||||||
// no associated stream for its output!
|
|
||||||
auto output = timer_snapshot_t::print_delta(t1, t2, true);
|
|
||||||
std::fwprintf(stderr, L"%S\n", output.c_str());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the result of the last job executed or skipped.
|
// Returns the result of the last job executed or skipped.
|
||||||
|
|
|
@ -210,7 +210,6 @@ DEF_ALT(job_list) {
|
||||||
DEF_ALT(job_decorator) {
|
DEF_ALT(job_decorator) {
|
||||||
using ands = single<keyword<parse_keyword_and>>;
|
using ands = single<keyword<parse_keyword_and>>;
|
||||||
using ors = single<keyword<parse_keyword_or>>;
|
using ors = single<keyword<parse_keyword_or>>;
|
||||||
using times = single<keyword<parse_keyword_time>>;
|
|
||||||
using empty = grammar::empty;
|
using empty = grammar::empty;
|
||||||
ALT_BODY(job_decorator, ands, ors, empty);
|
ALT_BODY(job_decorator, ands, ors, empty);
|
||||||
};
|
};
|
||||||
|
@ -225,13 +224,20 @@ DEF_ALT(job_conjunction_continuation) {
|
||||||
ALT_BODY(job_conjunction_continuation, andands, orors, empty);
|
ALT_BODY(job_conjunction_continuation, andands, orors, empty);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The time builtin.
|
||||||
|
DEF_ALT(optional_time) {
|
||||||
|
using empty = grammar::empty;
|
||||||
|
using time = single<keyword<parse_keyword_time>>;
|
||||||
|
ALT_BODY(optional_time, empty, time);
|
||||||
|
};
|
||||||
|
|
||||||
// A job is a non-empty list of statements, separated by pipes. (Non-empty is useful for cases
|
// A job is a non-empty list of statements, separated by pipes. (Non-empty is useful for cases
|
||||||
// like if statements, where we require a command). To represent "non-empty", we require a
|
// like if statements, where we require a command). To represent "non-empty", we require a
|
||||||
// statement, followed by a possibly empty job_continuation, and then optionally a background
|
// statement, followed by a possibly empty job_continuation, and then optionally a background
|
||||||
// specifier '&'
|
// specifier '&'
|
||||||
DEF(job)
|
DEF(job)
|
||||||
produces_sequence<variable_assignments, statement, job_continuation, optional_background>{
|
produces_sequence<optional_time, variable_assignments, statement, job_continuation,
|
||||||
BODY(job)};
|
optional_background>{BODY(job)};
|
||||||
|
|
||||||
DEF_ALT(job_continuation) {
|
DEF_ALT(job_continuation) {
|
||||||
using piped =
|
using piped =
|
||||||
|
@ -322,8 +328,9 @@ produces_sequence<keyword<parse_keyword_function>, argument, argument_list, tok_
|
||||||
BODY(function_header)};
|
BODY(function_header)};
|
||||||
|
|
||||||
DEF_ALT(not_statement) {
|
DEF_ALT(not_statement) {
|
||||||
using nots = seq<keyword<parse_keyword_not>, variable_assignments, statement>;
|
using nots = seq<keyword<parse_keyword_not>, variable_assignments, optional_time, statement>;
|
||||||
using exclams = seq<keyword<parse_keyword_exclam>, variable_assignments, statement>;
|
using exclams =
|
||||||
|
seq<keyword<parse_keyword_exclam>, variable_assignments, optional_time, statement>;
|
||||||
ALT_BODY(not_statement, nots, exclams);
|
ALT_BODY(not_statement, nots, exclams);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ ELEM(argument)
|
||||||
ELEM(redirection)
|
ELEM(redirection)
|
||||||
ELEM(optional_background)
|
ELEM(optional_background)
|
||||||
ELEM(optional_newlines)
|
ELEM(optional_newlines)
|
||||||
|
ELEM(optional_time)
|
||||||
ELEM(end_command)
|
ELEM(end_command)
|
||||||
ELEM(freestanding_argument_list)
|
ELEM(freestanding_argument_list)
|
||||||
#undef ELEM
|
#undef ELEM
|
||||||
|
|
|
@ -82,10 +82,6 @@ RESOLVE(job_decorator) {
|
||||||
*out_tag = parse_job_decoration_or;
|
*out_tag = parse_job_decoration_or;
|
||||||
return production_for<ors>();
|
return production_for<ors>();
|
||||||
}
|
}
|
||||||
case parse_keyword_time: {
|
|
||||||
*out_tag = parse_job_decoration_time;
|
|
||||||
return production_for<times>();
|
|
||||||
}
|
|
||||||
default: {
|
default: {
|
||||||
*out_tag = parse_job_decoration_none;
|
*out_tag = parse_job_decoration_none;
|
||||||
return production_for<empty>();
|
return production_for<empty>();
|
||||||
|
@ -400,6 +396,19 @@ RESOLVE(optional_background) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RESOLVE(optional_time) {
|
||||||
|
UNUSED(token2);
|
||||||
|
|
||||||
|
switch (token1.keyword) {
|
||||||
|
case parse_keyword_time:
|
||||||
|
*out_tag = parse_optional_time_time;
|
||||||
|
return production_for<time>();
|
||||||
|
default:
|
||||||
|
*out_tag = parse_optional_time_no_time;
|
||||||
|
return production_for<empty>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const production_element_t *parse_productions::production_for_token(parse_token_type_t node_type,
|
const production_element_t *parse_productions::production_for_token(parse_token_type_t node_type,
|
||||||
const parse_token_t &input1,
|
const parse_token_t &input1,
|
||||||
const parse_token_t &input2,
|
const parse_token_t &input2,
|
||||||
|
|
|
@ -955,7 +955,7 @@ void parse_ll_t::accept_tokens(parse_token_t token1, parse_token_t token2) {
|
||||||
case symbol_job:
|
case symbol_job:
|
||||||
variable_assignments =
|
variable_assignments =
|
||||||
tnode_t<grammar::job>(&nodes, parent)
|
tnode_t<grammar::job>(&nodes, parent)
|
||||||
.try_get_child<grammar::variable_assignments, 0>();
|
.try_get_child<grammar::variable_assignments, 1>();
|
||||||
break;
|
break;
|
||||||
case symbol_job_continuation:
|
case symbol_job_continuation:
|
||||||
variable_assignments =
|
variable_assignments =
|
||||||
|
|
|
@ -421,6 +421,9 @@ class job_t {
|
||||||
|
|
||||||
/// This job is disowned, and should be removed from the active jobs list.
|
/// This job is disowned, and should be removed from the active jobs list.
|
||||||
bool disown_requested{false};
|
bool disown_requested{false};
|
||||||
|
|
||||||
|
/// Whether to print timing for this job.
|
||||||
|
bool has_time_prefix{false};
|
||||||
} job_flags{};
|
} job_flags{};
|
||||||
|
|
||||||
/// Access the job flags.
|
/// Access the job flags.
|
||||||
|
|
|
@ -195,3 +195,22 @@ wcstring timer_snapshot_t::print_delta(timer_snapshot_t t1, timer_snapshot_t t2,
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static std::vector<timer_snapshot_t> active_timers;
|
||||||
|
|
||||||
|
static void pop_timer() {
|
||||||
|
auto t1 = std::move(active_timers.back());
|
||||||
|
active_timers.pop_back();
|
||||||
|
auto t2 = timer_snapshot_t::take();
|
||||||
|
|
||||||
|
// Well, this is awkward. By defining `time` as a decorator and not a built-in, there's
|
||||||
|
// no associated stream for its output!
|
||||||
|
auto output = timer_snapshot_t::print_delta(std::move(t1), std::move(t2), true);
|
||||||
|
std::fwprintf(stderr, L"%S\n", output.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_t push_timer(bool enabled) {
|
||||||
|
if (!enabled) return {[]() {}};
|
||||||
|
active_timers.emplace_back(timer_snapshot_t::take());
|
||||||
|
return {[]() { pop_timer(); }};
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
class parser_t;
|
class parser_t;
|
||||||
struct io_streams_t;
|
struct io_streams_t;
|
||||||
|
|
||||||
|
cleanup_t push_timer(bool enabled);
|
||||||
|
|
||||||
struct timer_snapshot_t {
|
struct timer_snapshot_t {
|
||||||
public:
|
public:
|
||||||
struct rusage cpu_fish;
|
struct rusage cpu_fish;
|
||||||
|
|
|
@ -114,7 +114,7 @@ arguments_node_list_t get_argument_nodes(tnode_t<grammar::arguments_or_redirecti
|
||||||
}
|
}
|
||||||
|
|
||||||
bool job_node_is_background(tnode_t<grammar::job> job) {
|
bool job_node_is_background(tnode_t<grammar::job> job) {
|
||||||
tnode_t<grammar::optional_background> bg = job.child<3>();
|
tnode_t<grammar::optional_background> bg = job.child<4>();
|
||||||
return bg.tag() == parse_background;
|
return bg.tag() == parse_background;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ pipeline_position_t get_pipeline_position(tnode_t<grammar::statement> st) {
|
||||||
|
|
||||||
// Check if we're the beginning of a job, and if so, whether that job
|
// Check if we're the beginning of a job, and if so, whether that job
|
||||||
// has a non-empty continuation.
|
// has a non-empty continuation.
|
||||||
tnode_t<job_continuation> jc = st.try_get_parent<job>().child<2>();
|
tnode_t<job_continuation> jc = st.try_get_parent<job>().child<3>();
|
||||||
if (jc.try_get_child<statement, 3>()) {
|
if (jc.try_get_child<statement, 3>()) {
|
||||||
return pipeline_position_t::first;
|
return pipeline_position_t::first;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,3 +25,21 @@ time echo 'foo -s bar'
|
||||||
#CHECKERR: Executed in {{[\d,.\s]*}} {{millis|micros|secs}} {{\s*}}fish {{\s*}}external
|
#CHECKERR: Executed in {{[\d,.\s]*}} {{millis|micros|secs}} {{\s*}}fish {{\s*}}external
|
||||||
#CHECKERR: usr time {{[\d,.\s]*}} {{millis|micros|secs}} {{[\d,.\s]*}} {{millis|micros|secs}} {{[\d,.\s]*}} {{millis|micros|secs}}
|
#CHECKERR: usr time {{[\d,.\s]*}} {{millis|micros|secs}} {{[\d,.\s]*}} {{millis|micros|secs}} {{[\d,.\s]*}} {{millis|micros|secs}}
|
||||||
#CHECKERR: sys time {{[\d,.\s]*}} {{millis|micros|secs}} {{[\d,.\s]*}} {{millis|micros|secs}} {{[\d,.\s]*}} {{millis|micros|secs}}
|
#CHECKERR: sys time {{[\d,.\s]*}} {{millis|micros|secs}} {{[\d,.\s]*}} {{millis|micros|secs}} {{[\d,.\s]*}} {{millis|micros|secs}}
|
||||||
|
|
||||||
|
true && time a=b not builtin true | true
|
||||||
|
#CHECKERR: ___{{.*}}
|
||||||
|
#CHECKERR: {{.*}}
|
||||||
|
#CHECKERR: {{.*}}
|
||||||
|
#CHECKERR: {{.*}}
|
||||||
|
|
||||||
|
PATH= time true
|
||||||
|
#CHECKERR: fish: Unknown command: time
|
||||||
|
#CHECKERR: {{.*}}
|
||||||
|
#CHECKERR: PATH= time true
|
||||||
|
#CHECKERR: ^
|
||||||
|
|
||||||
|
not time true
|
||||||
|
#CHECKERR: ___{{.*}}
|
||||||
|
#CHECKERR: {{.*}}
|
||||||
|
#CHECKERR: {{.*}}
|
||||||
|
#CHECKERR: {{.*}}
|
||||||
|
|
Loading…
Reference in a new issue