static struct curl_slist * _get_curl_headers(HTTPDestinationDriver *self, LogMessage *msg) { GList *header = NULL; struct curl_slist *curl_headers = NULL; gchar header_host[128] = {0}; gchar header_program[32] = {0}; gchar header_facility[32] = {0}; gchar header_level[32] = {0}; g_snprintf(header_host, sizeof(header_host), "X-Syslog-Host: %s", log_msg_get_value(msg, LM_V_HOST, NULL)); curl_headers = curl_slist_append(curl_headers, header_host); g_snprintf(header_program, sizeof(header_program), "X-Syslog-Program: %s", log_msg_get_value(msg, LM_V_PROGRAM, NULL)); curl_headers = curl_slist_append(curl_headers, header_program); g_snprintf(header_facility, sizeof(header_facility), "X-Syslog-Facility: %s", syslog_name_lookup_name_by_value(msg->pri & LOG_FACMASK, sl_facilities)); curl_headers = curl_slist_append(curl_headers, header_facility); g_snprintf(header_level, sizeof(header_level), "X-Syslog-Level: %s", syslog_name_lookup_name_by_value(msg->pri & LOG_PRIMASK, sl_levels)); curl_headers = curl_slist_append(curl_headers, header_level); header = self->headers; while (header != NULL) { curl_headers = curl_slist_append(curl_headers, (gchar *)header->data); header = g_list_next(header); } return curl_headers; }
void check_val_in_hash_clone(gchar *msg, LogMessage *self,LogMessage *msg_clone , const gchar* pair[2]) { const gchar * a1 = log_msg_get_value(self, log_msg_get_value_handle(pair[0]), NULL); const gchar * a2 = log_msg_get_value(msg_clone, log_msg_get_value_handle(pair[0]), NULL); TEST_ASSERT(strcmp( a1, pair[1]) == 0 && strcmp(a2, pair[1] ) == 0, "%s", a1, a2); }
gboolean assert_grabbed_messages_contain_non_fatal(const gchar *pattern, const gchar *error_message, ...) { GList *l; va_list args; for (l = internal_messages; l; l = l->next) { LogMessage *msg = (LogMessage *) l->data; const gchar *msg_text = log_msg_get_value(msg, LM_V_MESSAGE, NULL); if (strstr(msg_text, pattern)) { return TRUE; } } va_start(args, error_message); print_failure(error_message, args, "no grabbed message contains the pattern=%s", pattern); va_end(args); fprintf(stderr, " # Grabbed internal messages follow:\n"); for (l = internal_messages; l; l = l->next) { LogMessage *msg = (LogMessage *) l->data; const gchar *msg_text = log_msg_get_value(msg, LM_V_MESSAGE, NULL); fprintf(stderr, " #\t%s\n", msg_text); } return FALSE; }
/* * NOTE: suppress_lock must be held. */ static void log_writer_last_msg_flush(LogWriter *self) { LogMessage *m; LogPathOptions path_options = LOG_PATH_OPTIONS_INIT; gchar buf[1024]; gssize len; const gchar *p; msg_debug("Suppress timer elapsed, emitting suppression summary", NULL); m = log_msg_new_empty(); m->timestamps[LM_TS_STAMP] = m->timestamps[LM_TS_RECVD]; m->pri = self->last_msg->pri; m->flags = LF_INTERNAL | LF_LOCAL; p = log_msg_get_value(self->last_msg, LM_V_HOST, &len); log_msg_set_value(m, LM_V_HOST, p, len); p = log_msg_get_value(self->last_msg, LM_V_PROGRAM, &len); log_msg_set_value(m, LM_V_PROGRAM, p, len); len = g_snprintf(buf, sizeof(buf), "Last message '%.20s' repeated %d times, suppressed by syslog-ng on %s", log_msg_get_value(self->last_msg, LM_V_MESSAGE, NULL), self->last_msg_count, get_local_hostname(NULL)); log_msg_set_value(m, LM_V_MESSAGE, buf, len); path_options.ack_needed = FALSE; log_queue_push_tail(self->queue, m, &path_options); log_writer_last_msg_release(self); }
void assert_log_msg_clear_clears_all_properties(LogMessage *msg) { log_msg_clear(msg); assert_string("", log_msg_get_value(msg, nv_handle, NULL), "Message still contains value after log_msg_clear"); assert_string("", log_msg_get_value(msg, sd_handle, NULL), "Message still contains sdata value after log_msg_clear"); assert_true(msg->saddr == NULL, "Message still contains an saddr after log_msg_clear"); assert_false(log_msg_is_tag_by_name(msg, tag_name), "Message still contains a valid tag after log_msg_clear"); }
void assert_log_message_values_equal(LogMessage *log_message_a, LogMessage *log_message_b, NVHandle handle) { gssize key_name_length; const gchar *key_name = log_msg_get_value_name(handle, &key_name_length); const gchar *value_a = log_msg_get_value(log_message_a, handle, NULL); const gchar *value_b = log_msg_get_value(log_message_b, handle, NULL); assert_string(value_a, value_b, "Value is not expected for key %s", key_name); }
int testcase_replace(const gchar *log, const gchar *re, gchar *replacement, const gchar *expected_result, const gint matcher_flags, LogMatcher *m) { LogMessage *msg; LogTemplate *r; gchar *result; gssize length; gchar buf[1024]; gssize msglen; NVHandle nonasciiz = log_msg_get_value_handle("NON-ASCIIZ"); const gchar *value; GSockAddr *sa; sa = g_sockaddr_inet_new("10.10.10.10", 1010); msg = log_msg_new(log, strlen(log), sa, &parse_options); g_sockaddr_unref(sa); /* NOTE: we test how our matchers cope with non-zero terminated values. We don't change message_len, only the value */ g_snprintf(buf, sizeof(buf), "%sAAAAAAAAAAAA", log_msg_get_value(msg, LM_V_MESSAGE, &msglen)); log_msg_set_value(msg, log_msg_get_value_handle("MESSAGE2"), buf, -1); /* add a non-zero terminated indirect value which contains the whole message */ log_msg_set_value_indirect(msg, nonasciiz, log_msg_get_value_handle("MESSAGE2"), 0, 0, msglen); log_matcher_set_flags(m, matcher_flags); log_matcher_compile(m, re); r = log_template_new(configuration, NULL); log_template_compile(r, replacement, NULL); NVTable *nv_table = nv_table_ref(msg->payload); value = log_msg_get_value(msg, nonasciiz, &msglen); result = log_matcher_replace(m, msg, nonasciiz, value, msglen, r, &length); value = log_msg_get_value(msg, nonasciiz, &msglen); nv_table_unref(nv_table); if (strncmp(result ? result : value, expected_result, result ? length : msglen) != 0) { fprintf(stderr, "Testcase failure. pattern=%s, result=%.*s, expected=%s\n", re, (gint) length, result ? result : value, expected_result); exit(1); } g_free(result); log_template_unref(r); log_matcher_unref(m); log_msg_unref(msg); return 0; }
/** * log_writer_is_msg_suppressed: * * This function is called to suppress duplicate messages from a given host. * * Locking notes: * * There's a strict ordering requirement between suppress_lock and * interacting with the main loop (which ml_batched_timer beind * suppress_timer is doing). * * The reason is that the main thread (running * the main loop) sometimes acquires suppress_lock (at suppress timer * expiration) and while blocking on suppress_lock it cannot service * main_loop_calls() * * This function makes it sure that ml_batched_timer_update/cancel calls are * only done with the suppress lock released. * * If we do this, we might have a few unfortunate side effects due to races * that we also try to handle: * * Two messages race, one of these matches the recorded last message, * the other doesn't. In this case, moving the update on the suppress_timer * outside of the lock region might cause two different races: * * 1) matching message comes first, then non-matching * * This case the suppress_lock protected region decides that the suppress * timer needs to fire (#1) and then the other decides that it needs to * be cancelled. (#2) * * If these are processed in order, then we are the same as if the two was * also protected by the mutex (which is ok) * * If they are reversed, e.g. we first cancels the timer and the second arms it, * then we might have a timer wakeup which will find no suppressed messages * to report (as the non-matching message will set last_msg_count to zero). This * spurious wakeup should be handled by the expiration callback. * * 1) non-matching message comes first, then matching * * This is simply a message reordering case, e.g. we don't * want any suppressions to be emitted. * * In this case the locked regions finds that neither messages matched * the recorded one, thus both times they decide to cancel the timer, which * is ok. Timer cancellation can be reordered as they will have the same * effect anyway. * * Returns TRUE to indicate that the message is to be suppressed. **/ static gboolean log_writer_is_msg_suppressed(LogWriter *self, LogMessage *lm) { gboolean need_to_arm_suppress_timer; gboolean need_to_cancel_suppress_timer = FALSE; if (self->options->suppress <= 0) return FALSE; g_static_mutex_lock(&self->suppress_lock); if (self->last_msg) { if (_is_time_within_the_suppress_timeout(self, lm) && _is_message_a_repetition(lm, self->last_msg) && !_is_message_a_mark(lm)) { stats_counter_inc(self->suppressed_messages); self->last_msg_count++; /* we only create the timer if this is the first suppressed message, otherwise it is already running. */ need_to_arm_suppress_timer = self->last_msg_count == 1; g_static_mutex_unlock(&self->suppress_lock); /* this has to be outside of suppress_lock */ if (need_to_arm_suppress_timer) log_writer_arm_suppress_timer(self); msg_debug("Suppressing duplicate message", evt_tag_str("host", log_msg_get_value(lm, LM_V_HOST, NULL)), evt_tag_str("msg", log_msg_get_value(lm, LM_V_MESSAGE, NULL))); return TRUE; } else { if (self->last_msg_count) log_writer_emit_suppress_summary(self); else log_writer_release_last_message(self); need_to_cancel_suppress_timer = TRUE; } } log_writer_record_last_message(self, lm); g_static_mutex_unlock(&self->suppress_lock); if (need_to_cancel_suppress_timer) ml_batched_timer_cancel(&self->suppress_timer); return FALSE; }
static gboolean _is_message_a_repetition(LogMessage *msg, LogMessage *last) { return strcmp(log_msg_get_value(last, LM_V_MESSAGE, NULL), log_msg_get_value(msg, LM_V_MESSAGE, NULL)) == 0 && strcmp(log_msg_get_value(last, LM_V_HOST, NULL), log_msg_get_value(msg, LM_V_HOST, NULL)) == 0 && strcmp(log_msg_get_value(last, LM_V_PROGRAM, NULL), log_msg_get_value(msg, LM_V_PROGRAM, NULL)) == 0 && strcmp(log_msg_get_value(last, LM_V_PID, NULL), log_msg_get_value(msg, LM_V_PID, NULL)) == 0; }
static void _set_curl_opt(HTTPDestinationDriver *self, LogMessage *msg, struct curl_slist *curl_headers, GString *body_rendered) { curl_easy_reset(self->curl); curl_easy_setopt(self->curl, CURLOPT_WRITEFUNCTION, _http_write_cb); curl_easy_setopt(self->curl, CURLOPT_URL, self->url); if (self->user) curl_easy_setopt(self->curl, CURLOPT_USERNAME, self->user); if (self->password) curl_easy_setopt(self->curl, CURLOPT_PASSWORD, self->password); if (self->user_agent) curl_easy_setopt(self->curl, CURLOPT_USERAGENT, self->user_agent); curl_easy_setopt(self->curl, CURLOPT_HTTPHEADER, curl_headers); const gchar *body = body_rendered ? body_rendered->str : log_msg_get_value(msg, LM_V_MESSAGE, NULL); curl_easy_setopt(self->curl, CURLOPT_POSTFIELDS, body); if (self->method_type == METHOD_TYPE_PUT) curl_easy_setopt(self->curl, CURLOPT_CUSTOMREQUEST, "PUT"); }
static void log_rewrite_queue(LogPipe *s, LogMessage *msg, const LogPathOptions *path_options, gpointer user_data) { LogRewrite *self = (LogRewrite *) s; gchar buf[128]; gssize length; const gchar *value; if (self->condition && !filter_expr_eval_root(self->condition, &msg, path_options)) { msg_debug("Rewrite condition unmatched, skipping rewrite", evt_tag_str("value", log_msg_get_value_name(self->value_handle, NULL)), NULL); } else { self->process(self, &msg, path_options); } if (G_UNLIKELY(debug_flag)) { value = log_msg_get_value(msg, self->value_handle, &length); msg_debug("Rewrite expression evaluation result", evt_tag_str("value", log_msg_get_value_name(self->value_handle, NULL)), evt_tag_printf("new_value", "%.*s", (gint) length, value), evt_tag_str("rule", self->name), evt_tag_str("location", log_expr_node_format_location(s->expr_node, buf, sizeof(buf))), NULL); } log_pipe_forward_msg(s, msg, path_options); }
//FIXME use log msg value lookup void check_val_in_hash(gchar *msg, LogMessage *self, const gchar* pair[2]) { const gchar *a = log_msg_get_value(self, log_msg_get_value_handle(pair[0]), NULL); TEST_ASSERT(strcmp(a, pair[1]) == 0, "%s", a, pair[1]); }
static void log_rewrite_rule_call_item(gpointer item, gpointer user_data) { LogRewrite *r = (LogRewrite *) item; LogMessage *msg = (LogMessage *) user_data; gssize length; const gchar *value; if (r->condition && !filter_expr_eval(r->condition, msg)) { msg_debug("Rewrite condition unmatched, skipping rewrite", evt_tag_str("value", log_msg_get_value_name(r->value_handle, NULL)), NULL); return; } r->process(r, msg); if (G_UNLIKELY(debug_flag)) { value = log_msg_get_value(msg, r->value_handle, &length); msg_debug("Rewrite expression evaluation result", evt_tag_str("value", log_msg_get_value_name(r->value_handle, NULL)), evt_tag_printf("new_value", "%.*s", (gint) length, value), NULL); } }
void test_rule_value_without_clean(const gchar *program, const gchar *pattern, const gchar *name, const gchar *value) { gboolean result; LogMessage *msg = log_msg_new_empty(); gboolean found = FALSE; const gchar *val; gssize len; PDBInput input; log_msg_set_value(msg, LM_V_MESSAGE, pattern, strlen(pattern)); log_msg_set_value(msg, LM_V_PROGRAM, program, 5); log_msg_set_value(msg, LM_V_HOST, MYHOST, strlen(MYHOST)); log_msg_set_value(msg, LM_V_PID, MYPID, strlen(MYPID)); result = pattern_db_process(patterndb, PDB_INPUT_WRAP_MESSAGE(&input, msg)); val = log_msg_get_value(msg, log_msg_get_value_handle(name), &len); if (value) found = strcmp(val, value) == 0; if (!!value ^ (len > 0)) test_fail("Value '%s' is %smatching for pattern '%s' (%d)\n", name, found ? "" : "not ", pattern, !!result); log_msg_unref(msg); }
static void __handle_data(gchar *key, gchar *value, gpointer user_data) { gpointer *args = user_data; LogMessage *msg = args[0]; JournalReaderOptions *options = args[1]; gssize value_len = MIN(strlen(value), options->max_field_size); if (strcmp(key, "MESSAGE") == 0) { log_msg_set_value(msg, LM_V_MESSAGE, value, value_len); msg_debug("Incoming log entry from journal", evt_tag_printf("message", "%.*s", (int)value_len, value), NULL); } else if (strcmp(key, "_HOSTNAME") == 0) { log_msg_set_value(msg, LM_V_HOST, value, value_len); } else if (strcmp(key, "_PID") == 0) { log_msg_set_value(msg, LM_V_PID, value, value_len); } else if (strcmp(key, "_COMM") == 0) { log_msg_set_value(msg, LM_V_PROGRAM, value, value_len); } else if (strcmp(key, "SYSLOG_IDENTIFIER") == 0) { gssize program_length; (void)log_msg_get_value(msg, LM_V_PROGRAM, &program_length); if (program_length == 0) { log_msg_set_value(msg, LM_V_PROGRAM, value, value_len); } } else if (strcmp(key, "SYSLOG_FACILITY") == 0) { msg->pri = (msg->pri & 7) | atoi(value) << 3; } else if (strcmp(key, "PRIORITY") == 0) { msg->pri = (msg->pri & ~7) | atoi(value); } else { if (!options->prefix) { log_msg_set_value_by_name(msg, key, value, value_len); } else { gchar *prefixed_key = g_strdup_printf("%s%s", options->prefix, key); log_msg_set_value_by_name(msg, prefixed_key, value, value_len); g_free(prefixed_key); } } }
static void assert_log_kmsg_value(LogMessage *message, const gchar *key, const gchar *expected_value) { const gchar *actual_value = log_msg_get_value(message, log_msg_get_value_handle(key), NULL); assert_string(actual_value, expected_value, NULL); }
void _test_program_field_test(TestCase *self, TestSource *src, LogMessage *msg) { Journald *journal = self->user_data; gchar *cursor; journald_get_cursor(journal, &cursor); if (strcmp(cursor, "no SYSLOG_IDENTIFIER") != 0) { cr_assert_str_eq(log_msg_get_value(msg, LM_V_PROGRAM, NULL), "syslog_program", "%s", "Bad program name"); g_free(cursor); } else { cr_assert_str_eq(log_msg_get_value(msg, LM_V_PROGRAM, NULL), "comm_program", "%s", "Bad program name"); g_free(cursor); test_source_finish_tc(src); } }
static void result_append_value(GString *result, LogMessage *lm, NVHandle handle, gboolean escape) { const gchar *str; gssize len = 0; str = log_msg_get_value(lm, handle, &len); result_append(result, str, len, escape); }
void assert_log_message_value(LogMessage *self, NVHandle handle, const gchar *expected_value) { gssize key_name_length; const gchar *key_name = log_msg_get_value_name(handle, &key_name_length); const gchar *actual_value = log_msg_get_value(self, handle, NULL); assert_string(actual_value, expected_value, "Value is not expected for key %s", key_name); }
void assert_log_message_sdata_pairs(LogMessage *message, const gchar *expected_sd_pairs[][2]) { gint i; for (i = 0; expected_sd_pairs && expected_sd_pairs[i][0] != NULL;i++) { const gchar *actual_value = log_msg_get_value(message, log_msg_get_value_handle(expected_sd_pairs[i][0]), NULL); assert_string(actual_value, expected_sd_pairs[i][1], NULL); } }
static void log_writer_last_msg_flush(LogWriter *self) { LogMessage *m; LogPathOptions path_options = LOG_PATH_OPTIONS_INIT; gchar hostname[256]; gchar buf[1024]; gssize len; const gchar *p; msg_debug("Suppress timer elapsed, emitting suppression summary", NULL); getlonghostname(hostname, sizeof(hostname)); m = log_msg_new_empty(); m->timestamps[LM_TS_STAMP] = m->timestamps[LM_TS_RECVD]; m->pri = self->last_msg->pri; m->flags = LF_INTERNAL | LF_LOCAL; p = log_msg_get_value(self->last_msg, LM_V_HOST, &len); log_msg_set_value(m, LM_V_HOST, p, len); p = log_msg_get_value(self->last_msg, LM_V_PROGRAM, &len); log_msg_set_value(m, LM_V_PROGRAM, p, len); len = g_snprintf(buf, sizeof(buf), "Last message '%.20s' repeated %d times, supressed by syslog-ng on %s", log_msg_get_value(self->last_msg, LM_V_MESSAGE, NULL), self->last_msg_count, hostname); log_msg_set_value(m, LM_V_MESSAGE, buf, len); path_options.flow_control = FALSE; if (!log_queue_push_tail(self->queue, m, &path_options)) { stats_counter_inc(self->dropped_messages); msg_debug("Destination queue full, dropping suppressed message", evt_tag_int("queue_len", log_queue_get_length(self->queue)), evt_tag_int("mem_fifo_size", self->options->mem_fifo_size), NULL); log_msg_drop(m, &path_options); } log_writer_last_msg_release(self); }
void __test_other_has_prefix(TestCase *self, LogMessage *msg) { gchar *requested_name = g_strdup_printf("%s%s", (gchar *) self->user_data, "_CMDLINE"); NVHandle handle = log_msg_get_value_handle(requested_name); gssize value_len; const gchar *value = log_msg_get_value(msg, handle, &value_len); assert_string(value, "sshd: foo_user [priv]", ASSERTION_ERROR("Bad value for prefixed key")); g_free(requested_name); }
void __test_message_has_no_prefix(TestCase *self, LogMessage *msg) { gchar *requested_name = g_strdup_printf("%s%s", (gchar *)self->user_data, "MESSAGE"); NVHandle handle = log_msg_get_value_handle(requested_name); gssize value_len; log_msg_get_value(msg, handle, &value_len); assert_gint(value_len, 0, ASSERTION_ERROR("MESSAGE has prefix")); g_free(requested_name); }
static void test_log_msg_set_value_indirect_with_self_referencing_handle_results_in_a_nonindirect_value(void) { LogMessage *msg; gssize value_len; msg = construct_log_message_with_all_bells_and_whistles(); log_msg_set_value_indirect(msg, nv_handle, nv_handle, 0, 0, 5); assert_string(log_msg_get_value(msg, nv_handle, &value_len), "value", "indirect self-reference value doesn't match"); log_msg_unref(msg); }
void _test_prefix_test(TestCase *self, TestSource *src, LogMessage *msg) { const gchar *message = log_msg_get_value(msg, LM_V_MESSAGE, NULL); assert_string(message, "pam_unix(sshd:session): session opened for user foo_user by (uid=0)", ASSERTION_ERROR("Bad message")); __test_message_has_no_prefix(self, msg); __test_other_has_prefix(self, msg); test_source_finish_tc(src); }
static void log_db_parser_emit(LogMessage *msg, gboolean synthetic, gpointer user_data) { if (synthetic) { msg_post_message(msg); msg_debug("db-parser: emitting synthetic message", evt_tag_str("msg", log_msg_get_value(msg, LM_V_MESSAGE, NULL)), NULL); } }
gboolean test_clusters_find(gpointer key, gpointer value, gpointer user_data) { int i; gchar *line; clusterfindData *data; clusterfind2Data *find_data; gboolean found; guint lines_in_cluster, lines_found = 0; gssize msglen; data = ((clusterfindData *) user_data); found = TRUE; for (i = 0; i < data->num_of_lines; ++i) { line = g_strdup((gchar *) log_msg_get_value((LogMessage *) g_ptr_array_index(data->logs, data->lines[i]), LM_V_MESSAGE, &msglen)); find_data = g_new(clusterfind2Data, 1); find_data->search_line = line; find_data->found = FALSE; /* we count the number of lines in the cluster several times this way, * but why on earth should we optimize a unit test?... :) */ find_data->lines_in_cluster = 0; g_ptr_array_foreach(((Cluster *) value)->loglines, test_clusters_loglines_find, find_data); if (find_data->found) ++lines_found; else found = FALSE; lines_in_cluster = find_data->lines_in_cluster; g_free(find_data); g_free(line); /* at least one line is missing, this cannot be a match */ if (!found) return FALSE; } /* if we got to this point, this means we have found all required rows, so we * only have to check for completeness */ if (data->num_of_lines == lines_in_cluster) return TRUE; else return FALSE; }
static void dummy_dd_queue(LogPipe *s, LogMessage *msg, const LogPathOptions *path_options, gpointer user_data) { DummyDestDriver *self = (DummyDestDriver *) s; msg_notice("Dummy plugin received a message", evt_tag_str("msg", log_msg_get_value(msg, LM_V_MESSAGE, NULL)), evt_tag_int("opt", self->opt), NULL); log_msg_ack(msg, path_options); log_msg_unref(msg); }
Test(date, test_date_with_additional_text_at_the_end) { const gchar *msg = "2015-01-26T16:14:49+0300 Disappointing log file"; LogParser *parser = _construct_parser(NULL, NULL, LM_TS_STAMP); LogMessage *logmsg = _construct_logmsg(msg); gboolean success = log_parser_process(parser, &logmsg, NULL, log_msg_get_value(logmsg, LM_V_MESSAGE, NULL), -1); cr_assert_not(success, "successfully parsed but expected failure, msg=%s", msg); log_pipe_unref(&parser->super); log_msg_unref(logmsg); }
void _test_default_working_test(TestCase *self, TestSource *src, LogMessage *msg) { const gchar *message = log_msg_get_value(msg, LM_V_MESSAGE, NULL); JournalReaderOptions *options = self->user_data; assert_string(message, "Dummy message", ASSERTION_ERROR("Bad message")); assert_gint(msg->pri, options->default_pri, ASSERTION_ERROR("Bad default prio")); assert_gint(options->fetch_limit, 10, ASSERTION_ERROR("Bad default fetch_limit")); assert_gint(options->max_field_size, 64 * 1024, ASSERTION_ERROR("Bad max field size")); assert_gpointer(options->prefix, NULL, ASSERTION_ERROR("Bad default prefix value")); assert_string(options->recv_time_zone, configuration->recv_time_zone, ASSERTION_ERROR("Bad default timezone")); test_source_finish_tc(src); }