/*
 * This file is part of libgpiod.
 *
 * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 */

/* Test cases for the gpiomon program. */

#include "gpiod-test.h"

#include <signal.h>
#include <unistd.h>

static void gpiomon_single_rising_edge_event(void)
{
	test_tool_run("gpiomon", "--rising-edge", "--num-events=1",
		      test_chip_name(1), "4", (char *)NULL);
	test_set_event(1, 4, TEST_EVENT_RISING, 200);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_RET_OK(test_tool_exit_status());
	TEST_ASSERT_NOT_NULL(test_tool_stdout());
	TEST_ASSERT_NULL(test_tool_stderr());
	TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
				"event\\:\\s+RISING\\s+EDGE\\s+offset\\:\\s+4\\s+timestamp:\\s+\\[[0-9]+\\.[0-9]+\\]");
}
TEST_DEFINE(gpiomon_single_rising_edge_event,
	    "tools: gpiomon - single rising edge event",
	    0, { 8, 8 });

static void gpiomon_single_rising_edge_event_active_low(void)
{
	test_tool_run("gpiomon", "--rising-edge", "--num-events=1",
		      "--active-low", test_chip_name(1), "4", (char *)NULL);
	test_set_event(1, 4, TEST_EVENT_RISING, 200);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_RET_OK(test_tool_exit_status());
	TEST_ASSERT_NOT_NULL(test_tool_stdout());
	TEST_ASSERT_NULL(test_tool_stderr());
	TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
				"event\\:\\s+RISING\\s+EDGE\\s+offset\\:\\s+4\\s+timestamp:\\s+\\[[0-9]+\\.[0-9]+\\]");
}
TEST_DEFINE(gpiomon_single_rising_edge_event_active_low,
	    "tools: gpiomon - single rising edge event (active-low)",
	    0, { 8, 8 });

static void gpiomon_single_rising_edge_event_silent(void)
{
	test_tool_run("gpiomon", "--rising-edge", "--num-events=1",
		      "--silent", test_chip_name(1), "4", (char *)NULL);
	test_set_event(1, 4, TEST_EVENT_RISING, 200);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_RET_OK(test_tool_exit_status());
	TEST_ASSERT_NULL(test_tool_stdout());
	TEST_ASSERT_NULL(test_tool_stderr());
}
TEST_DEFINE(gpiomon_single_rising_edge_event_silent,
	    "tools: gpiomon - single rising edge event (silent mode)",
	    0, { 8, 8 });

static void gpiomon_four_alternating_events(void)
{
	test_tool_run("gpiomon", "--num-events=4",
		      test_chip_name(1), "4", (char *)NULL);
	test_set_event(1, 4, TEST_EVENT_ALTERNATING, 100);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_RET_OK(test_tool_exit_status());
	TEST_ASSERT_NOT_NULL(test_tool_stdout());
	TEST_ASSERT_NULL(test_tool_stderr());
	TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
				"event\\:\\s+FALLING\\s+EDGE\\s+offset\\:\\s+4\\s+timestamp:\\s+\\[[0-9]+\\.[0-9]+\\]");
	TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
				"event\\:\\s+RISING\\s+EDGE\\s+offset\\:\\s+4\\s+timestamp:\\s+\\[[0-9]+\\.[0-9]+\\]");
}
TEST_DEFINE(gpiomon_four_alternating_events,
	    "tools: gpiomon - four alternating events",
	    0, { 8, 8 });

static void gpiomon_falling_edge_events_sigint(void)
{
	test_tool_run("gpiomon", "--falling-edge",
		      test_chip_name(0), "4", (char *)NULL);
	test_set_event(0, 4, TEST_EVENT_FALLING, 100);
	usleep(200000);
	test_tool_signal(SIGINT);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_RET_OK(test_tool_exit_status());
	TEST_ASSERT_NOT_NULL(test_tool_stdout());
	TEST_ASSERT_NULL(test_tool_stderr());
	TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
				"event\\:\\s+FALLING\\s+EDGE\\s+offset\\:\\s+4\\s+timestamp:\\s+\\[[0-9]+\\.[0-9]+\\]");
}
TEST_DEFINE(gpiomon_falling_edge_events_sigint,
	    "tools: gpiomon - receive falling edge events and kill with SIGINT",
	    0, { 8, 8 });

static void gpiomon_both_events_sigterm(void)
{
	test_tool_run("gpiomon", "--falling-edge", "--rising-edge",
		      test_chip_name(0), "4", (char *)NULL);
	test_set_event(0, 4, TEST_EVENT_ALTERNATING, 100);
	usleep(300000);
	test_tool_signal(SIGTERM);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_RET_OK(test_tool_exit_status());
	TEST_ASSERT_NOT_NULL(test_tool_stdout());
	TEST_ASSERT_NULL(test_tool_stderr());
	TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
				"event\\:\\s+FALLING\\s+EDGE\\s+offset\\:\\s+4\\s+timestamp:\\s+\\[[0-9]+\\.[0-9]+\\]");
	TEST_ASSERT_REGEX_MATCH(test_tool_stdout(),
				"event\\:\\s+RISING\\s+EDGE\\s+offset\\:\\s+4\\s+timestamp:\\s+\\[[0-9]+\\.[0-9]+\\]");
}
TEST_DEFINE(gpiomon_both_events_sigterm,
	    "tools: gpiomon - receive both types of events and kill with SIGTERM",
	    0, { 8, 8 });

static void gpiomon_ignore_falling_edge(void)
{
	test_tool_run("gpiomon", "--rising-edge",
		      test_chip_name(0), "4", (char *)NULL);
	test_set_event(0, 4, TEST_EVENT_FALLING, 100);
	usleep(300000);
	test_tool_signal(SIGTERM);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_RET_OK(test_tool_exit_status());
	TEST_ASSERT_NULL(test_tool_stdout());
	TEST_ASSERT_NULL(test_tool_stderr());
}
TEST_DEFINE(gpiomon_ignore_falling_edge,
	    "tools: gpiomon - wait for rising edge events, ignore falling edge",
	    0, { 8, 8 });

static void gpiomon_watch_multiple_lines(void)
{
	test_tool_run("gpiomon", "--format=%o", test_chip_name(0),
		      "1", "2", "3", "4", "5", (char *)NULL);
	test_set_event(0, 2, TEST_EVENT_ALTERNATING, 100);
	usleep(150000);
	test_set_event(0, 3, TEST_EVENT_ALTERNATING, 100);
	usleep(150000);
	test_set_event(0, 4, TEST_EVENT_ALTERNATING, 100);
	usleep(150000);
	test_tool_signal(SIGTERM);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_RET_OK(test_tool_exit_status());
	TEST_ASSERT_NULL(test_tool_stderr());
	TEST_ASSERT_NOT_NULL(test_tool_stdout());
	TEST_ASSERT_STR_EQ(test_tool_stdout(), "2\n3\n4\n");

}
TEST_DEFINE(gpiomon_watch_multiple_lines,
	    "tools: gpiomon - watch multiple lines",
	    0, { 8, 8 });

static void gpiomon_watch_multiple_lines_not_in_order(void)
{
	test_tool_run("gpiomon", "--format=%o", test_chip_name(0),
		      "5", "2", "7", "1", "6", (char *)NULL);
	test_set_event(0, 2, TEST_EVENT_ALTERNATING, 100);
	usleep(150000);
	test_set_event(0, 1, TEST_EVENT_ALTERNATING, 100);
	usleep(150000);
	test_set_event(0, 6, TEST_EVENT_ALTERNATING, 100);
	usleep(150000);
	test_tool_signal(SIGTERM);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_RET_OK(test_tool_exit_status());
	TEST_ASSERT_NULL(test_tool_stderr());
	TEST_ASSERT_NOT_NULL(test_tool_stdout());
	TEST_ASSERT_STR_EQ(test_tool_stdout(), "2\n1\n6\n");

}
TEST_DEFINE(gpiomon_watch_multiple_lines_not_in_order,
	    "tools: gpiomon - watch multiple lines (offsets not in order)",
	    0, { 8, 8 });

static void gpiomon_request_the_same_line_twice(void)
{
	test_tool_run("gpiomon", test_chip_name(0), "2", "2", (char *)NULL);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_EQ(test_tool_exit_status(), 1);
	TEST_ASSERT_NULL(test_tool_stdout());
	TEST_ASSERT_NOT_NULL(test_tool_stderr());
	TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
				 "error waiting for events");
}
TEST_DEFINE(gpiomon_request_the_same_line_twice,
	    "tools: gpiomon - request the same line twice",
	    0, { 8, 8 });

static void gpiomon_no_arguments(void)
{
	test_tool_run("gpiomon", (char *)NULL);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_EQ(test_tool_exit_status(), 1);
	TEST_ASSERT_NULL(test_tool_stdout());
	TEST_ASSERT_NOT_NULL(test_tool_stderr());
	TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
				 "gpiochip must be specified");
}
TEST_DEFINE(gpiomon_no_arguments,
	    "tools: gpiomon - no arguments",
	    0, { });

static void gpiomon_line_not_specified(void)
{
	test_tool_run("gpiomon", test_chip_name(1), (char *)NULL);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_EQ(test_tool_exit_status(), 1);
	TEST_ASSERT_NULL(test_tool_stdout());
	TEST_ASSERT_NOT_NULL(test_tool_stderr());
	TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
				 "GPIO line offset must be specified");
}
TEST_DEFINE(gpiomon_line_not_specified,
	    "tools: gpiomon - line not specified",
	    0, { 4, 4 });

static void gpiomon_line_out_of_range(void)
{
	test_tool_run("gpiomon", test_chip_name(0), "4", (char *)NULL);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_EQ(test_tool_exit_status(), 1);
	TEST_ASSERT_NULL(test_tool_stdout());
	TEST_ASSERT_NOT_NULL(test_tool_stderr());
	TEST_ASSERT_STR_CONTAINS(test_tool_stderr(),
				 "error waiting for events");
}
TEST_DEFINE(gpiomon_line_out_of_range,
	    "tools: gpiomon - line out of range",
	    0, { 4 });

static void gpiomon_custom_format_event_and_offset(void)
{
	test_tool_run("gpiomon", "--num-events=1", "--format=%e %o",
		      test_chip_name(0), "3", (char *)NULL);
	test_set_event(0, 3, TEST_EVENT_RISING, 100);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_RET_OK(test_tool_exit_status());
	TEST_ASSERT_NOT_NULL(test_tool_stdout());
	TEST_ASSERT_NULL(test_tool_stderr());
	TEST_ASSERT_STR_EQ(test_tool_stdout(), "1 3\n");
}
TEST_DEFINE(gpiomon_custom_format_event_and_offset,
	    "tools: gpiomon - custom output format: event and offset",
	    0, { 8, 8 });

static void gpiomon_custom_format_event_and_offset_joined(void)
{
	test_tool_run("gpiomon", "--num-events=1", "--format=%e%o",
		      test_chip_name(0), "3", (char *)NULL);
	test_set_event(0, 3, TEST_EVENT_RISING, 100);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_RET_OK(test_tool_exit_status());
	TEST_ASSERT_NOT_NULL(test_tool_stdout());
	TEST_ASSERT_NULL(test_tool_stderr());
	TEST_ASSERT_STR_EQ(test_tool_stdout(), "13\n");
}
TEST_DEFINE(gpiomon_custom_format_event_and_offset_joined,
	    "tools: gpiomon - custom output format: event and offset, joined strings",
	    0, { 8, 8 });

static void gpiomon_custom_format_timestamp(void)
{
	test_tool_run("gpiomon", "--num-events=1", "--format=%e %o %s.%n",
		      test_chip_name(0), "3", (char *)NULL);
	test_set_event(0, 3, TEST_EVENT_RISING, 100);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_RET_OK(test_tool_exit_status());
	TEST_ASSERT_NOT_NULL(test_tool_stdout());
	TEST_ASSERT_NULL(test_tool_stderr());
	TEST_ASSERT_REGEX_MATCH(test_tool_stdout(), "1 3 [0-9]+\\.[0-9]+");
}
TEST_DEFINE(gpiomon_custom_format_timestamp,
	    "tools: gpiomon - custom output format: timestamp",
	    0, { 8, 8 });

static void gpiomon_custom_format_double_percent_sign(void)
{
	test_tool_run("gpiomon", "--num-events=1", "--format=%%",
		      test_chip_name(0), "3", (char *)NULL);
	test_set_event(0, 3, TEST_EVENT_RISING, 100);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_RET_OK(test_tool_exit_status());
	TEST_ASSERT_NOT_NULL(test_tool_stdout());
	TEST_ASSERT_NULL(test_tool_stderr());
	TEST_ASSERT_STR_EQ(test_tool_stdout(), "%\n");
}
TEST_DEFINE(gpiomon_custom_format_double_percent_sign,
	    "tools: gpiomon - custom output format: double percent sign",
	    0, { 8, 8 });

static void gpiomon_custom_format_double_percent_sign_and_spec(void)
{
	test_tool_run("gpiomon", "--num-events=1", "--format=%%e",
		      test_chip_name(0), "3", (char *)NULL);
	test_set_event(0, 3, TEST_EVENT_RISING, 100);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_RET_OK(test_tool_exit_status());
	TEST_ASSERT_NOT_NULL(test_tool_stdout());
	TEST_ASSERT_NULL(test_tool_stderr());
	TEST_ASSERT_STR_EQ(test_tool_stdout(), "%e\n");
}
TEST_DEFINE(gpiomon_custom_format_double_percent_sign_and_spec,
	    "tools: gpiomon - custom output format: double percent sign with specifier",
	    0, { 8, 8 });

static void gpiomon_custom_format_single_percent_sign(void)
{
	test_tool_run("gpiomon", "--num-events=1", "--format=%",
		      test_chip_name(0), "3", (char *)NULL);
	test_set_event(0, 3, TEST_EVENT_RISING, 100);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_RET_OK(test_tool_exit_status());
	TEST_ASSERT_NOT_NULL(test_tool_stdout());
	TEST_ASSERT_NULL(test_tool_stderr());
	TEST_ASSERT_STR_EQ(test_tool_stdout(), "%\n");
}
TEST_DEFINE(gpiomon_custom_format_single_percent_sign,
	    "tools: gpiomon - custom output format: single percent sign",
	    0, { 8, 8 });

static void gpiomon_custom_format_single_percent_sign_between_chars(void)
{
	test_tool_run("gpiomon", "--num-events=1", "--format=foo % bar",
		      test_chip_name(0), "3", (char *)NULL);
	test_set_event(0, 3, TEST_EVENT_RISING, 100);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_RET_OK(test_tool_exit_status());
	TEST_ASSERT_NOT_NULL(test_tool_stdout());
	TEST_ASSERT_NULL(test_tool_stderr());
	TEST_ASSERT_STR_EQ(test_tool_stdout(), "foo % bar\n");
}
TEST_DEFINE(gpiomon_custom_format_single_percent_sign_between_chars,
	    "tools: gpiomon - custom output format: single percent sign between other characters",
	    0, { 8, 8 });

static void gpiomon_custom_format_unknown_specifier(void)
{
	test_tool_run("gpiomon", "--num-events=1", "--format=%x",
		      test_chip_name(0), "3", (char *)NULL);
	test_set_event(0, 3, TEST_EVENT_RISING, 100);
	test_tool_wait();

	TEST_ASSERT(test_tool_exited());
	TEST_ASSERT_RET_OK(test_tool_exit_status());
	TEST_ASSERT_NOT_NULL(test_tool_stdout());
	TEST_ASSERT_NULL(test_tool_stderr());
	TEST_ASSERT_STR_EQ(test_tool_stdout(), "%x\n");
}
TEST_DEFINE(gpiomon_custom_format_unknown_specifier,
	    "tools: gpiomon - custom output format: unknown specifier",
	    0, { 8, 8 });
