/*
 * Copyright (C) 2014-2025 Intel Corporation
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  * Neither the name of Intel Corporation nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "ptunit.h"

#include "pevent.h"

#include <stdlib.h>
#include <stdio.h>

#if defined(_MSC_VER) && (_MSC_VER < 1900)
#  define snprintf _snprintf_c
#endif


/* A test fixture. */
struct pev_fixture {
	/* A memory buffer. */
	uint8_t buffer[1024];

	/* Two perf events:
	 *
	 *   event[0] is the test setup
	 *   event[1] is the event after writing and reading event[0]
	 */
	struct pev_event event[2];

	/* The perf event configuration. */
	struct pev_config config;

	/* The sample_type to be tested. */
	uint64_t sample_type;

	/* Test samples. */
	struct {
		uint32_t pid, tid;
		uint64_t time;
		uint64_t tsc;
		uint32_t cpu;
		uint64_t identifier;
	} sample;

	/* The test fixture initialization and finalization functions. */
	struct ptunit_result (*init)(struct pev_fixture *);
	struct ptunit_result (*fini)(struct pev_fixture *);
};

static struct ptunit_result pfix_init(struct pev_fixture *pfix)
{
	memset(pfix->buffer, 0xcd, sizeof(pfix->buffer));
	memset(&pfix->sample, 0xcd, sizeof(pfix->sample));

	pev_event_init(&pfix->event[0]);
	pev_event_init(&pfix->event[1]);

	pev_config_init(&pfix->config);

	pfix->sample_type = 0;

	return ptu_passed();
}

static struct ptunit_result pfix_fini(struct pev_fixture *pfix)
{
#if (LIBIPT_SB_VERSION >= 0x201)
	free(pfix->config.sample_config);
#endif
	return ptu_passed();
}

static struct ptunit_result pfix_init_sample_time(struct pev_fixture *pfix)
{
	ptu_test(pfix_init, pfix);

	pfix->config.sample_type |= (uint64_t) PERF_SAMPLE_TIME;
	pfix->config.time_zero = 0xa0b00000ull;
	pfix->config.time_shift = 4;
	pfix->config.time_mult = 3;

	pfix->sample_type = pfix->config.sample_type;
	pfix->sample.time = 0xa0b00cdeull;
	pfix->event[0].sample.time = &pfix->sample.time;

	return ptu_passed();
}

static struct ptunit_result pfix_init_sample_who(struct pev_fixture *pfix)
{
	ptu_test(pfix_init, pfix);

	pfix->config.sample_type |= (uint64_t) PERF_SAMPLE_TID;
	pfix->config.sample_type |= (uint64_t) PERF_SAMPLE_CPU;

	pfix->sample_type = pfix->config.sample_type;
	pfix->sample.pid = 0xa0;
	pfix->sample.tid = 0xa1;
	pfix->sample.cpu = 0xb;

	pfix->event[0].sample.pid = &pfix->sample.pid;
	pfix->event[0].sample.tid = &pfix->sample.tid;
	pfix->event[0].sample.cpu = &pfix->sample.cpu;

	return ptu_passed();
}

#if (LIBIPT_SB_VERSION >= 0x201)
static struct ptunit_result pfix_init_sample_config(struct pev_fixture *pfix)
{
	struct pev_sample_config *sample_config;

	ptu_test(pfix_init, pfix);

	sample_config = malloc(sizeof(*pfix->config.sample_config) +
			       (3 * sizeof(struct pev_sample_type)));
	ptu_ptr(sample_config);

	pfix->config.sample_config = sample_config;
	sample_config->nstypes = 3;
	sample_config->stypes[0].identifier = 1;
	sample_config->stypes[0].sample_type = (uint64_t)
		PERF_SAMPLE_TID | PERF_SAMPLE_IDENTIFIER;
	sample_config->stypes[1].identifier = 2;
	sample_config->stypes[1].sample_type = (uint64_t)
		PERF_SAMPLE_CPU | PERF_SAMPLE_IDENTIFIER;
	sample_config->stypes[2].identifier = 3;
	sample_config->stypes[2].sample_type = (uint64_t)
		PERF_SAMPLE_TIME | PERF_SAMPLE_IDENTIFIER;

	pfix->sample_type = sample_config->stypes[1].sample_type;
	pfix->sample.cpu = 0xb;
	pfix->sample.identifier = sample_config->stypes[1].identifier;

	pfix->event[0].sample.cpu = &pfix->sample.cpu;
	pfix->event[0].sample.identifier = &pfix->sample.identifier;

	return ptu_passed();
}
#endif

static struct ptunit_result pfix_read_write(struct pev_fixture *pfix)
{
	uint8_t *begin, *end;
	int size[2];

	begin = pfix->buffer;
	end = begin + sizeof(pfix->buffer);

	size[0] = pev_write(&pfix->event[0], begin, end, &pfix->config);
	ptu_int_gt(size[0], 0);

	size[1] = pev_read(&pfix->event[1], begin, end, &pfix->config);
	ptu_int_gt(size[1], 0);

	ptu_int_eq(size[1], size[0]);

	return ptu_passed();
}

static struct ptunit_result pfix_check_sample_time(struct pev_fixture *pfix)
{
	const uint64_t *time[2];
	uint64_t tsc;
	int errcode;

	time[0] = pfix->event[0].sample.time;
	time[1] = pfix->event[1].sample.time;

	ptu_ptr(time[0]);
	ptu_ptr(time[1]);

	ptu_uint_eq(*time[1], *time[0]);

	errcode = pev_time_to_tsc(&tsc, *time[0], &pfix->config);
	ptu_int_eq(errcode, 0);
	ptu_uint_eq(pfix->event[1].sample.tsc, tsc);

	return ptu_passed();
}

static struct ptunit_result pfix_check_sample_tid(struct pev_fixture *pfix)
{
	const uint32_t *pid[2], *tid[2];

	pid[0] = pfix->event[0].sample.pid;
	pid[1] = pfix->event[1].sample.pid;
	tid[0] = pfix->event[0].sample.tid;
	tid[1] = pfix->event[1].sample.tid;

	ptu_ptr(pid[0]);
	ptu_ptr(pid[1]);
	ptu_ptr(tid[0]);
	ptu_ptr(tid[1]);

	ptu_uint_eq(*pid[1], *pid[0]);
	ptu_uint_eq(*tid[1], *tid[0]);

	return ptu_passed();
}

static struct ptunit_result pfix_check_sample_cpu(struct pev_fixture *pfix)
{
	const uint32_t *cpu[2];

	cpu[0] = pfix->event[0].sample.cpu;
	cpu[1] = pfix->event[1].sample.cpu;

	ptu_ptr(cpu[0]);
	ptu_ptr(cpu[1]);

	ptu_uint_eq(*cpu[1], *cpu[0]);

	return ptu_passed();
}

static struct ptunit_result pfix_check_sample(struct pev_fixture *pfix)
{
	if (pfix->sample_type & PERF_SAMPLE_TID)
		ptu_test(pfix_check_sample_tid, pfix);
	else {
		ptu_null(pfix->event[1].sample.pid);
		ptu_null(pfix->event[1].sample.tid);
	}

	if (pfix->sample_type & PERF_SAMPLE_TIME)
		ptu_test(pfix_check_sample_time, pfix);
	else
		ptu_null(pfix->event[1].sample.time);

	if (pfix->sample_type & PERF_SAMPLE_CPU)
		ptu_test(pfix_check_sample_cpu, pfix);
	else
		ptu_null(pfix->event[1].sample.cpu);

	return ptu_passed();
}

static struct ptunit_result time_to_tsc_null(void)
{
	struct pev_config config;
	uint64_t tsc;
	int errcode;

	memset(&config, 0, sizeof(config));

	errcode = pev_time_to_tsc(NULL, 0x0ull, &config);
	ptu_int_eq(errcode, -pte_internal);

	errcode = pev_time_to_tsc(&tsc, 0x0ull, NULL);
	ptu_int_eq(errcode, -pte_internal);

	return ptu_passed();
}

static struct ptunit_result time_from_tsc_null(void)
{
	struct pev_config config;
	uint64_t time;
	int errcode;

	memset(&config, 0, sizeof(config));

	errcode = pev_time_from_tsc(NULL, 0x0ull, &config);
	ptu_int_eq(errcode, -pte_internal);

	errcode = pev_time_from_tsc(&time, 0x0ull, NULL);
	ptu_int_eq(errcode, -pte_internal);

	return ptu_passed();
}

static struct ptunit_result time_to_tsc(void)
{
	struct pev_config config;
	uint64_t tsc;
	int errcode;

	pev_config_init(&config);
	config.time_shift = 4;
	config.time_mult = 3;
	config.time_zero = 0xa00b00ull;

	errcode = pev_time_to_tsc(&tsc, 0xa00b43ull, &config);
	ptu_int_eq(errcode, 0);
	ptu_uint_eq(tsc, 0x165ull);

	return ptu_passed();
}

static struct ptunit_result time_from_tsc(void)
{
	struct pev_config config;
	uint64_t time;
	int errcode;

	pev_config_init(&config);
	config.time_shift = 4;
	config.time_mult = 3;
	config.time_zero = 0xa00b00ull;

	errcode = pev_time_from_tsc(&time, 0x23bull, &config);
	ptu_int_eq(errcode, 0);
	ptu_uint_eq(time, 0xa00b6bull);

	return ptu_passed();
}

static struct ptunit_result time_to_tsc_bad_config(void)
{
	struct pev_config config;
	uint64_t tsc;
	int errcode;

	memset(&config, 0, sizeof(config));
	config.time_mult = 1;

	errcode = pev_time_to_tsc(&tsc, 0x1ull, &config);
	ptu_int_eq(errcode, -pte_bad_config);

	config.size = sizeof(config);
	config.time_mult = 0;

	errcode = pev_time_to_tsc(&tsc, 0x1ull, &config);
	ptu_int_eq(errcode, -pte_bad_config);

	return ptu_passed();
}

static struct ptunit_result time_from_tsc_bad_config(void)
{
	struct pev_config config;
	uint64_t time;
	int errcode;

	memset(&config, 0, sizeof(config));
	config.time_mult = 1;

	errcode = pev_time_from_tsc(&time, 0x0ull, &config);
	ptu_int_eq(errcode, -pte_bad_config);

	config.size = sizeof(config);
	config.time_mult = 0;

	errcode = pev_time_from_tsc(&time, 0x0ull, &config);
	ptu_int_eq(errcode, -pte_bad_config);

	return ptu_passed();
}

static struct ptunit_result read_bad_config(void)
{
	union {
		struct perf_event_header header;
		uint8_t buffer[128];
	} input;
	struct pev_config config;
	struct pev_event event;
	int errcode;

	memset(input.buffer, 0, sizeof(input.buffer));
	input.header.type = PERF_RECORD_ITRACE_START;
	input.header.size = sizeof(*event.record.itrace_start) + 0x8;

	memset(&config, 0, sizeof(config));
	config.sample_type |= (uint64_t) PERF_SAMPLE_CPU;

	errcode = pev_read(&event, input.buffer,
			   input.buffer + sizeof(input.buffer), &config);
	ptu_int_eq(errcode, -pte_bad_config);

	return ptu_passed();
}

static struct ptunit_result write_bad_config(void)
{
	struct pev_record_itrace_start itrace_start;
	struct pev_config config;
	struct pev_event event;
	uint32_t cpu;
	uint8_t buffer[128];
	int errcode;

	memset(&itrace_start, 0, sizeof(itrace_start));

	pev_event_init(&event);
	event.type = PERF_RECORD_ITRACE_START;
	event.record.itrace_start = &itrace_start;
	event.sample.cpu = &cpu;

	memset(&config, 0, sizeof(config));
	config.sample_type |= (uint64_t) PERF_SAMPLE_CPU;

	errcode = pev_write(&event, buffer, buffer + sizeof(buffer), &config);
	ptu_int_eq(errcode, -pte_bad_config);

	return ptu_passed();
}

static struct ptunit_result nosync(uint16_t type)
{
	union {
		struct perf_event_header header;
		uint8_t buffer[128];
	} input;
	struct pev_config config;
	struct pev_event event;
	int errcode;

	pev_config_init(&config);

	memset(input.buffer, 0xcc, sizeof(input.buffer));
	input.header.type = type;
	input.header.misc = 0;
	input.header.size = sizeof(input.header.type);

	errcode = pev_read(&event, input.buffer,
			   input.buffer + sizeof(input.buffer), &config);
	ptu_int_eq(errcode, -pte_nosync);

	return ptu_passed();
}

static struct ptunit_result bad_string(uint16_t type)
{
	union {
		struct perf_event_header header;
		uint8_t buffer[512];
	} input;

	struct pev_config config;
	struct pev_event event;
	int errcode;

	pev_config_init(&config);

	memset(input.buffer, 0xcc, sizeof(input.buffer));
	input.header.type = type;
	input.header.misc = 0;
	input.header.size = 0x50;

	errcode = pev_read(&event, input.buffer,
			   input.buffer + sizeof(input.buffer), &config);
	ptu_int_eq(errcode, -pte_bad_packet);

	return ptu_passed();
}

static struct ptunit_result record_mmap(struct pev_fixture *pfix)
{
	union {
		struct pev_record_mmap record;
		char buffer[1024];
	} mmap;

	memset(&mmap, 0, sizeof(mmap));

	mmap.record.pid = 0xa;
	mmap.record.tid = 0xb;
	mmap.record.addr = 0xa00100ull;
	mmap.record.len = 0x110ull;
	mmap.record.pgoff = 0xb0000ull;
	snprintf(mmap.record.filename,
		 sizeof(mmap.buffer) - sizeof(mmap.record), "%s", "foo.so");

	pfix->event[0].record.mmap = &mmap.record;
	pfix->event[0].type = PERF_RECORD_MMAP;

	ptu_test(pfix_read_write, pfix);
	ptu_test(pfix_check_sample, pfix);

	ptu_int_eq(pfix->event[1].type, pfix->event[0].type);
	ptu_ptr(pfix->event[1].record.mmap);
	ptu_uint_eq(pfix->event[1].record.mmap->pid, mmap.record.pid);
	ptu_uint_eq(pfix->event[1].record.mmap->tid, mmap.record.tid);
	ptu_uint_eq(pfix->event[1].record.mmap->addr, mmap.record.addr);
	ptu_uint_eq(pfix->event[1].record.mmap->len, mmap.record.len);
	ptu_uint_eq(pfix->event[1].record.mmap->pgoff, mmap.record.pgoff);
	ptu_str_eq(pfix->event[1].record.mmap->filename, mmap.record.filename);

	return ptu_passed();
}

static struct ptunit_result record_lost(struct pev_fixture *pfix)
{
	struct pev_record_lost lost;

	lost.id = 0xa042ull;
	lost.lost = 0xeull;

	pfix->event[0].record.lost = &lost;
	pfix->event[0].type = PERF_RECORD_LOST;

	ptu_test(pfix_read_write, pfix);
	ptu_test(pfix_check_sample, pfix);

	ptu_int_eq(pfix->event[1].type, pfix->event[0].type);
	ptu_ptr(pfix->event[1].record.lost);
	ptu_uint_eq(pfix->event[1].record.lost->id, lost.id);
	ptu_uint_eq(pfix->event[1].record.lost->lost, lost.lost);

	return ptu_passed();
}

static struct ptunit_result record_comm(struct pev_fixture *pfix)
{
	union {
		struct pev_record_comm record;
		char buffer[1024];
	} comm;

	memset(&comm, 0, sizeof(comm));

	comm.record.pid = 0xa;
	comm.record.tid = 0xb;
	snprintf(comm.record.comm, sizeof(comm.buffer) - sizeof(comm.record),
		 "%s", "foo -b ar");

	pfix->event[0].record.comm = &comm.record;
	pfix->event[0].type = PERF_RECORD_COMM;

	ptu_test(pfix_read_write, pfix);
	ptu_test(pfix_check_sample, pfix);

	ptu_int_eq(pfix->event[1].type, pfix->event[0].type);
	ptu_ptr(pfix->event[1].record.comm);
	ptu_uint_eq(pfix->event[1].record.comm->pid, comm.record.pid);
	ptu_uint_eq(pfix->event[1].record.comm->tid, comm.record.tid);
	ptu_str_eq(pfix->event[1].record.comm->comm, comm.record.comm);

	return ptu_passed();
}

static struct ptunit_result record_exit(struct pev_fixture *pfix)
{
	struct pev_record_exit exit;

	exit.pid = 0xa;
	exit.ppid = 0xaa;
	exit.tid = 0xb;
	exit.ptid = 0xab;
	exit.time = 0xabcdefull;

	pfix->event[0].record.exit = &exit;
	pfix->event[0].type = PERF_RECORD_EXIT;

	ptu_test(pfix_read_write, pfix);
	ptu_test(pfix_check_sample, pfix);

	ptu_int_eq(pfix->event[1].type, pfix->event[0].type);
	ptu_ptr(pfix->event[1].record.exit);
	ptu_uint_eq(pfix->event[1].record.exit->pid, exit.pid);
	ptu_uint_eq(pfix->event[1].record.exit->ppid, exit.ppid);
	ptu_uint_eq(pfix->event[1].record.exit->tid, exit.tid);
	ptu_uint_eq(pfix->event[1].record.exit->ptid, exit.ptid);
	ptu_uint_eq(pfix->event[1].record.exit->time, exit.time);

	return ptu_passed();
}

static struct ptunit_result record_throttle(struct pev_fixture *pfix)
{
	struct pev_record_throttle throttle;

	throttle.time = 0xabcdeull;
	throttle.id = 0xa042ull;
	throttle.stream_id = 0xb00ull;

	pfix->event[0].record.throttle = &throttle;
	pfix->event[0].type = PERF_RECORD_THROTTLE;

	ptu_test(pfix_read_write, pfix);
	ptu_test(pfix_check_sample, pfix);

	ptu_int_eq(pfix->event[1].type, pfix->event[0].type);
	ptu_ptr(pfix->event[1].record.throttle);
	ptu_uint_eq(pfix->event[1].record.throttle->time, throttle.time);
	ptu_uint_eq(pfix->event[1].record.throttle->id, throttle.id);
	ptu_uint_eq(pfix->event[1].record.throttle->stream_id,
		    throttle.stream_id);

	return ptu_passed();
}

static struct ptunit_result record_unthrottle(struct pev_fixture *pfix)
{
	struct pev_record_throttle throttle;

	throttle.time = 0xc00042ull;
	throttle.id = 0x23ull;
	throttle.stream_id = 0x0ull;

	pfix->event[0].record.throttle = &throttle;
	pfix->event[0].type = PERF_RECORD_UNTHROTTLE;

	ptu_test(pfix_read_write, pfix);
	ptu_test(pfix_check_sample, pfix);

	ptu_int_eq(pfix->event[1].type, pfix->event[0].type);
	ptu_ptr(pfix->event[1].record.throttle);
	ptu_uint_eq(pfix->event[1].record.throttle->time, throttle.time);
	ptu_uint_eq(pfix->event[1].record.throttle->id, throttle.id);
	ptu_uint_eq(pfix->event[1].record.throttle->stream_id,
		    throttle.stream_id);

	return ptu_passed();
}

static struct ptunit_result record_fork(struct pev_fixture *pfix)
{
	struct pev_record_fork fork;

	fork.pid = 0xa;
	fork.ppid = 0xaa;
	fork.tid = 0xb;
	fork.ptid = 0xab;
	fork.time = 0xabcdefull;

	pfix->event[0].record.fork = &fork;
	pfix->event[0].type = PERF_RECORD_FORK;

	ptu_test(pfix_read_write, pfix);
	ptu_test(pfix_check_sample, pfix);

	ptu_int_eq(pfix->event[1].type, pfix->event[0].type);
	ptu_ptr(pfix->event[1].record.fork);
	ptu_uint_eq(pfix->event[1].record.fork->pid, fork.pid);
	ptu_uint_eq(pfix->event[1].record.fork->ppid, fork.ppid);
	ptu_uint_eq(pfix->event[1].record.fork->tid, fork.tid);
	ptu_uint_eq(pfix->event[1].record.fork->ptid, fork.ptid);
	ptu_uint_eq(pfix->event[1].record.fork->time, fork.time);

	return ptu_passed();
}

static struct ptunit_result record_mmap2(struct pev_fixture *pfix)
{
	union {
		struct pev_record_mmap2 record;
		char buffer[1024];
	} mmap2;

	memset(&mmap2, 0, sizeof(mmap2));

	mmap2.record.pid = 0xa;
	mmap2.record.tid = 0xb;
	mmap2.record.addr = 0xa00100ull;
	mmap2.record.len = 0x110ull;
	mmap2.record.pgoff = 0xb0000ull;
	mmap2.record.maj = 7;
	mmap2.record.min = 2;
	mmap2.record.ino = 0x8080ull;
	mmap2.record.ino_generation = 0x4ull;
	mmap2.record.prot = 0x755;
	mmap2.record.flags = 0;
	snprintf(mmap2.record.filename,
		 sizeof(mmap2.buffer) - sizeof(mmap2.record), "%s", "foo.so");

	pfix->event[0].record.mmap2 = &mmap2.record;
	pfix->event[0].type = PERF_RECORD_MMAP2;

	ptu_test(pfix_read_write, pfix);
	ptu_test(pfix_check_sample, pfix);

	ptu_int_eq(pfix->event[1].type, pfix->event[0].type);
	ptu_ptr(pfix->event[1].record.mmap2);
	ptu_uint_eq(pfix->event[1].record.mmap2->pid, mmap2.record.pid);
	ptu_uint_eq(pfix->event[1].record.mmap2->tid, mmap2.record.tid);
	ptu_uint_eq(pfix->event[1].record.mmap2->addr, mmap2.record.addr);
	ptu_uint_eq(pfix->event[1].record.mmap2->len, mmap2.record.len);
	ptu_uint_eq(pfix->event[1].record.mmap2->pgoff, mmap2.record.pgoff);
	ptu_uint_eq(pfix->event[1].record.mmap2->maj, mmap2.record.maj);
	ptu_uint_eq(pfix->event[1].record.mmap2->min, mmap2.record.min);
	ptu_uint_eq(pfix->event[1].record.mmap2->ino, mmap2.record.ino);
	ptu_uint_eq(pfix->event[1].record.mmap2->ino_generation,
		    mmap2.record.ino_generation);
	ptu_uint_eq(pfix->event[1].record.mmap2->prot, mmap2.record.prot);
	ptu_uint_eq(pfix->event[1].record.mmap2->flags, mmap2.record.flags);
	ptu_str_eq(pfix->event[1].record.mmap2->filename,
		   mmap2.record.filename);

	return ptu_passed();
}

static struct ptunit_result record_aux(struct pev_fixture *pfix)
{
	struct pev_record_aux aux;

	aux.aux_offset = 0xc00042ull;
	aux.aux_size = 0x23ull;
	aux.flags = 0x0ull;

	pfix->event[0].record.aux = &aux;
	pfix->event[0].type = PERF_RECORD_AUX;

	ptu_test(pfix_read_write, pfix);
	ptu_test(pfix_check_sample, pfix);

	ptu_int_eq(pfix->event[1].type, pfix->event[0].type);
	ptu_ptr(pfix->event[1].record.aux);
	ptu_uint_eq(pfix->event[1].record.aux->aux_offset, aux.aux_offset);
	ptu_uint_eq(pfix->event[1].record.aux->aux_size, aux.aux_size);
	ptu_uint_eq(pfix->event[1].record.aux->flags, aux.flags);

	return ptu_passed();
}

static struct ptunit_result record_itrace_start(struct pev_fixture *pfix)
{
	struct pev_record_itrace_start itrace_start;

	itrace_start.pid = 0xa;
	itrace_start.tid = 0xb;

	pfix->event[0].record.itrace_start = &itrace_start;
	pfix->event[0].type = PERF_RECORD_ITRACE_START;

	ptu_test(pfix_read_write, pfix);
	ptu_test(pfix_check_sample, pfix);

	ptu_int_eq(pfix->event[1].type, pfix->event[0].type);
	ptu_ptr(pfix->event[1].record.itrace_start);
	ptu_uint_eq(pfix->event[1].record.itrace_start->pid, itrace_start.pid);
	ptu_uint_eq(pfix->event[1].record.itrace_start->tid, itrace_start.tid);

	return ptu_passed();
}

static struct ptunit_result record_lost_samples(struct pev_fixture *pfix)
{
	struct pev_record_lost_samples lost_samples;

	lost_samples.lost = 0xc00042ull;

	pfix->event[0].record.lost_samples = &lost_samples;
	pfix->event[0].type = PERF_RECORD_LOST_SAMPLES;

	ptu_test(pfix_read_write, pfix);
	ptu_test(pfix_check_sample, pfix);

	ptu_int_eq(pfix->event[1].type, pfix->event[0].type);
	ptu_ptr(pfix->event[1].record.lost_samples);
	ptu_uint_eq(pfix->event[1].record.lost_samples->lost,
		    lost_samples.lost);

	return ptu_passed();
}

static struct ptunit_result record_switch_task(struct pev_fixture *pfix,
					       int switch_out)
{
	pfix->event[0].type = PERF_RECORD_SWITCH;
	pfix->event[0].misc = switch_out ? PERF_RECORD_MISC_SWITCH_OUT : 0;

	ptu_test(pfix_read_write, pfix);
	ptu_test(pfix_check_sample, pfix);

	ptu_int_eq(pfix->event[1].type, pfix->event[0].type);
	ptu_int_eq(pfix->event[1].misc, pfix->event[0].misc);

	return ptu_passed();
}

static struct ptunit_result record_switch_cpu_wide(struct pev_fixture *pfix,
						   int switch_out)
{
	struct pev_record_switch_cpu_wide switch_cpu_wide;

	switch_cpu_wide.next_prev_pid = 0xa;
	switch_cpu_wide.next_prev_tid = 0xb;

	pfix->event[0].record.switch_cpu_wide = &switch_cpu_wide;
	pfix->event[0].type = PERF_RECORD_SWITCH_CPU_WIDE;
	pfix->event[0].misc = switch_out ? PERF_RECORD_MISC_SWITCH_OUT : 0;

	ptu_test(pfix_read_write, pfix);
	ptu_test(pfix_check_sample, pfix);

	ptu_int_eq(pfix->event[1].type, pfix->event[0].type);
	ptu_int_eq(pfix->event[1].misc, pfix->event[0].misc);
	ptu_ptr(pfix->event[1].record.switch_cpu_wide);
	ptu_uint_eq(pfix->event[1].record.switch_cpu_wide->next_prev_pid,
		    switch_cpu_wide.next_prev_pid);
	ptu_uint_eq(pfix->event[1].record.switch_cpu_wide->next_prev_tid,
		    switch_cpu_wide.next_prev_tid);

	return ptu_passed();
}

int main(int argc, char **argv)
{
	struct pev_fixture pfix, pfix_time, pfix_who;
#if (LIBIPT_SB_VERSION >= 0x201)
	struct pev_fixture pfix_sconf;
#endif
	struct ptunit_suite suite;

	pfix.init = pfix_init;
	pfix.fini = NULL;

	pfix_time.init = pfix_init_sample_time;
	pfix_time.fini = NULL;

	pfix_who.init = pfix_init_sample_who;
	pfix_who.fini = NULL;

#if (LIBIPT_SB_VERSION >= 0x201)
	pfix_sconf.init = pfix_init_sample_config;
	pfix_sconf.fini = pfix_fini;
#endif
	suite = ptunit_mk_suite(argc, argv);

	ptu_run(suite, time_to_tsc_null);
	ptu_run(suite, time_from_tsc_null);

	ptu_run(suite, time_to_tsc);
	ptu_run(suite, time_from_tsc);

	ptu_run(suite, time_to_tsc_bad_config);
	ptu_run(suite, time_from_tsc_bad_config);
	ptu_run(suite, read_bad_config);
	ptu_run(suite, write_bad_config);

	ptu_run_p(suite, bad_string, PERF_RECORD_MMAP);
	ptu_run_p(suite, bad_string, PERF_RECORD_COMM);
	ptu_run_p(suite, bad_string, PERF_RECORD_MMAP2);

	ptu_run_p(suite, nosync, PERF_RECORD_MMAP);
	ptu_run_p(suite, nosync, PERF_RECORD_COMM);
	ptu_run_p(suite, nosync, 0);

	ptu_run_f(suite, record_mmap, pfix);
	ptu_run_f(suite, record_lost, pfix);
	ptu_run_f(suite, record_comm, pfix);
	ptu_run_f(suite, record_exit, pfix);
	ptu_run_f(suite, record_throttle, pfix);
	ptu_run_f(suite, record_unthrottle, pfix);
	ptu_run_f(suite, record_fork, pfix);
	ptu_run_f(suite, record_mmap2, pfix);
	ptu_run_f(suite, record_aux, pfix);
	ptu_run_f(suite, record_itrace_start, pfix);
	ptu_run_f(suite, record_lost_samples, pfix);
	ptu_run_fp(suite, record_switch_task, pfix, 0);
	ptu_run_fp(suite, record_switch_task, pfix, 1);
	ptu_run_fp(suite, record_switch_cpu_wide, pfix, 0);
	ptu_run_fp(suite, record_switch_cpu_wide, pfix, 1);

	ptu_run_f(suite, record_mmap, pfix_time);
	ptu_run_f(suite, record_lost, pfix_time);
	ptu_run_f(suite, record_comm, pfix_time);
	ptu_run_f(suite, record_exit, pfix_time);
	ptu_run_f(suite, record_throttle, pfix_time);
	ptu_run_f(suite, record_unthrottle, pfix_time);
	ptu_run_f(suite, record_fork, pfix_time);
	ptu_run_f(suite, record_mmap2, pfix_time);
	ptu_run_f(suite, record_aux, pfix_time);
	ptu_run_f(suite, record_itrace_start, pfix_time);
	ptu_run_f(suite, record_lost_samples, pfix_time);
	ptu_run_fp(suite, record_switch_task, pfix_time, 0);
	ptu_run_fp(suite, record_switch_task, pfix_time, 1);
	ptu_run_fp(suite, record_switch_cpu_wide, pfix_time, 0);
	ptu_run_fp(suite, record_switch_cpu_wide, pfix_time, 1);

	ptu_run_f(suite, record_mmap, pfix_who);
	ptu_run_f(suite, record_lost, pfix_who);
	ptu_run_f(suite, record_comm, pfix_who);
	ptu_run_f(suite, record_exit, pfix_who);
	ptu_run_f(suite, record_throttle, pfix_who);
	ptu_run_f(suite, record_unthrottle, pfix_who);
	ptu_run_f(suite, record_fork, pfix_who);
	ptu_run_f(suite, record_mmap2, pfix_who);
	ptu_run_f(suite, record_aux, pfix_who);
	ptu_run_f(suite, record_itrace_start, pfix_who);
	ptu_run_f(suite, record_lost_samples, pfix_who);
	ptu_run_fp(suite, record_switch_task, pfix_who, 0);
	ptu_run_fp(suite, record_switch_task, pfix_who, 1);
	ptu_run_fp(suite, record_switch_cpu_wide, pfix_who, 0);
	ptu_run_fp(suite, record_switch_cpu_wide, pfix_who, 1);

#if (LIBIPT_SB_VERSION >= 0x201)
	ptu_run_f(suite, record_mmap, pfix_sconf);
	ptu_run_f(suite, record_lost, pfix_sconf);
	ptu_run_f(suite, record_comm, pfix_sconf);
	ptu_run_f(suite, record_exit, pfix_sconf);
	ptu_run_f(suite, record_throttle, pfix_sconf);
	ptu_run_f(suite, record_unthrottle, pfix_sconf);
	ptu_run_f(suite, record_fork, pfix_sconf);
	ptu_run_f(suite, record_mmap2, pfix_sconf);
	ptu_run_f(suite, record_aux, pfix_sconf);
	ptu_run_f(suite, record_itrace_start, pfix_sconf);
	ptu_run_f(suite, record_lost_samples, pfix_sconf);
	ptu_run_fp(suite, record_switch_task, pfix_sconf, 0);
	ptu_run_fp(suite, record_switch_task, pfix_sconf, 1);
	ptu_run_fp(suite, record_switch_cpu_wide, pfix_sconf, 0);
	ptu_run_fp(suite, record_switch_cpu_wide, pfix_sconf, 1);
#endif

	return ptunit_report(&suite);
}
