// SPDX-License-Identifier: GPL-2.0
/*
 * C++ stream style string formatter and printer used in KUnit for outputting
 * KUnit messages.
 *
 * Copyright (C) 2018, Google LLC.
 * Author: Brendan Higgins <brendanhiggins@google.com>
 */

#include <test/test.h>
#include <test/test-stream.h>
#include <test/string-stream.h>

static void test_stream_set_level(struct test_stream *this,
				  const char *level)
{
	this->level = level;
}

static void test_stream_add(struct test_stream *this, const char *fmt, ...)
{
	va_list args;
	struct string_stream *stream = this->internal_stream;

	va_start(args, fmt);
	if (stream->vadd(stream, fmt, args) < 0)
		test_err(this->test, "Failed to allocate fragment: %s", fmt);

	va_end(args);
}

static void test_stream_append(struct test_stream *this,
			       struct test_stream *other)
{
	struct string_stream *other_stream = other->internal_stream;
	const char *other_content;

	other_content = other_stream->get_string(other_stream);

	if (!other_content) {
		test_err(this->test,
			 "Failed to get string from second argument for appending.");
		return;
	}

	this->add(this, other_content);
	kfree(other_content);
}

static void test_stream_clear(struct test_stream *this)
{
	this->internal_stream->clear(this->internal_stream);
}

static void test_stream_commit(struct test_stream *this)
{
	char *buf;
	struct string_stream_fragment *fragment;
	struct string_stream *stream = this->internal_stream;

	if (!this->level) {
		test_err(this->test,
			 "Stream was committed without a specified log level.");
		this->set_level(this, KERN_ERR);
	}

	buf = stream->get_string(stream);
	if (!buf) {
		test_err(this->test,
			 "Could not allocate buffer, dumping stream:");
		list_for_each_entry(fragment, &stream->fragments, node) {
			test_err(this->test, fragment->fragment);
		}
		goto cleanup;
	}

	test_printk(this->level, this->test, buf);
	kfree(buf);

cleanup:
	this->clear(this);
}

static int test_stream_init(struct KUNIT_RESOURCE_T *res, void *context)
{
	struct test_stream *stream;
	struct KUNIT_T *test = context;

	stream = kzalloc(sizeof(*stream), GFP_KERNEL);
	if (!stream)
		return -ENOMEM;
	res->allocation = stream;
	stream->test = test;
	stream->level = NULL;
	stream->internal_stream = new_string_stream();

	if (!stream->internal_stream)
		return -ENOMEM;

	stream->set_level = test_stream_set_level;
	stream->add = test_stream_add;
	stream->append = test_stream_append;
	stream->commit = test_stream_commit;
	stream->clear = test_stream_clear;
	return 0;
}

static void test_stream_free(struct KUNIT_RESOURCE_T *res)
{
	struct test_stream *stream = res->allocation;

	if (!stream->internal_stream->is_empty(stream->internal_stream)) {
		test_err(stream->test,
			 "End of test case reached with uncommitted stream entries.");
		stream->commit(stream);
	}

	destroy_string_stream(stream->internal_stream);
	kfree(stream);
}

struct test_stream *test_new_stream(struct KUNIT_T *test)
{
	struct KUNIT_RESOURCE_T *res;

	res = test_alloc_resource(test,
				  test_stream_init,
				  test_stream_free,
				  test);

	if (res)
		return res->allocation;
	else
		return NULL;
}
EXPORT_SYMBOL(test_new_stream);