mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
[client] wayland: test presentation feedback lifecycle
This commit is contained in:
@@ -102,6 +102,39 @@ set_tests_properties(wayland-motion-tests PROPERTIES
|
|||||||
TIMEOUT 10
|
TIMEOUT 10
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(ENABLE_WAYLAND)
|
||||||
|
add_executable(wayland-presentation-tests
|
||||||
|
wayland_presentation_test.c
|
||||||
|
../displayservers/Wayland/presentation.c
|
||||||
|
)
|
||||||
|
add_dependencies(wayland-presentation-tests wayland_protocol)
|
||||||
|
target_include_directories(wayland-presentation-tests PRIVATE
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/../displayservers/Wayland"
|
||||||
|
"${CMAKE_BINARY_DIR}/wayland"
|
||||||
|
)
|
||||||
|
target_link_libraries(wayland-presentation-tests
|
||||||
|
${EXE_FLAGS}
|
||||||
|
lg_common
|
||||||
|
)
|
||||||
|
set(WAYLAND_PRESENTATION_CASES
|
||||||
|
feedback-first
|
||||||
|
swap-first
|
||||||
|
discard
|
||||||
|
swap-fail
|
||||||
|
token-zero
|
||||||
|
invalid-time
|
||||||
|
free-pending
|
||||||
|
)
|
||||||
|
foreach(name IN LISTS WAYLAND_PRESENTATION_CASES)
|
||||||
|
add_test(NAME wayland-presentation-${name}
|
||||||
|
COMMAND wayland-presentation-tests ${name}
|
||||||
|
)
|
||||||
|
set_tests_properties(wayland-presentation-${name} PROPERTIES
|
||||||
|
TIMEOUT 10
|
||||||
|
)
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
add_executable(displayserver-input-tests
|
add_executable(displayserver-input-tests
|
||||||
displayserver_input_test.c
|
displayserver_input_test.c
|
||||||
../displayservers/Wayland/input_event.c
|
../displayservers/Wayland/input_event.c
|
||||||
|
|||||||
497
client/tests/wayland_presentation_test.c
Normal file
497
client/tests/wayland_presentation_test.c
Normal file
@@ -0,0 +1,497 @@
|
|||||||
|
/**
|
||||||
|
* Looking Glass
|
||||||
|
* Copyright © 2017-2026 The Looking Glass Authors
|
||||||
|
* https://looking-glass.io
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
|
* Software Foundation; either version 2 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||||
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "wayland.h"
|
||||||
|
#include "test.h"
|
||||||
|
|
||||||
|
#include "common/debug.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#define ARRAY_LENGTH(a) (sizeof(a) / sizeof((a)[0]))
|
||||||
|
#define MAX_FEEDBACK 8U
|
||||||
|
#define MAX_NOTICE 8U
|
||||||
|
#define SEC UINT64_C(1000000000)
|
||||||
|
|
||||||
|
enum ProxyKind
|
||||||
|
{
|
||||||
|
PROXY_PRESENTATION,
|
||||||
|
PROXY_FEEDBACK,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Proxy
|
||||||
|
{
|
||||||
|
enum ProxyKind kind;
|
||||||
|
void (**listener)(void);
|
||||||
|
void * data;
|
||||||
|
bool dead;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Proto
|
||||||
|
{
|
||||||
|
struct Proxy presentation;
|
||||||
|
struct Proxy feedback[MAX_FEEDBACK];
|
||||||
|
unsigned int feedbackN;
|
||||||
|
unsigned int feedbackDestroyN;
|
||||||
|
unsigned int presentationDestroyN;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Notice
|
||||||
|
{
|
||||||
|
uint64_t token;
|
||||||
|
uint64_t time;
|
||||||
|
bool valid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Log
|
||||||
|
{
|
||||||
|
struct Notice notice[MAX_NOTICE];
|
||||||
|
unsigned int noticeN;
|
||||||
|
unsigned int graphRegisterN;
|
||||||
|
unsigned int graphCompactN;
|
||||||
|
unsigned int graphUnregisterN;
|
||||||
|
uint64_t period;
|
||||||
|
bool havePeriod;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WaylandDSState wlWm;
|
||||||
|
|
||||||
|
static struct Proto proto;
|
||||||
|
static struct Log rec;
|
||||||
|
static uint64_t now;
|
||||||
|
|
||||||
|
const struct wl_interface wp_presentation_feedback_interface =
|
||||||
|
{
|
||||||
|
.name = "wp_presentation_feedback",
|
||||||
|
.version = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
int clock_gettime(clockid_t id, struct timespec * ts)
|
||||||
|
{
|
||||||
|
CHECK(id == CLOCK_MONOTONIC);
|
||||||
|
ts->tv_sec = now / SEC;
|
||||||
|
ts->tv_nsec = now % SEC;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wl_list_init(struct wl_list * list)
|
||||||
|
{
|
||||||
|
list->next = list;
|
||||||
|
list->prev = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wl_list_insert(struct wl_list * list, struct wl_list * elm)
|
||||||
|
{
|
||||||
|
elm->prev = list;
|
||||||
|
elm->next = list->next;
|
||||||
|
list->next->prev = elm;
|
||||||
|
list->next = elm;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wl_list_remove(struct wl_list * elm)
|
||||||
|
{
|
||||||
|
elm->prev->next = elm->next;
|
||||||
|
elm->next->prev = elm->prev;
|
||||||
|
elm->next = NULL;
|
||||||
|
elm->prev = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int wl_list_empty(const struct wl_list * list)
|
||||||
|
{
|
||||||
|
return list->next == list;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t wl_proxy_get_version(struct wl_proxy * proxy)
|
||||||
|
{
|
||||||
|
CHECK(proxy);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int wl_proxy_add_listener(struct wl_proxy * proxy,
|
||||||
|
void (**listener)(void), void * data)
|
||||||
|
{
|
||||||
|
struct Proxy * p = (struct Proxy *)proxy;
|
||||||
|
CHECK(p);
|
||||||
|
CHECK(!p->dead);
|
||||||
|
CHECK(!p->listener);
|
||||||
|
p->listener = listener;
|
||||||
|
p->data = data;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void destroy(struct Proxy * p)
|
||||||
|
{
|
||||||
|
CHECK(p);
|
||||||
|
CHECK(!p->dead);
|
||||||
|
p->dead = true;
|
||||||
|
|
||||||
|
if (p->kind == PROXY_PRESENTATION)
|
||||||
|
++proto.presentationDestroyN;
|
||||||
|
else
|
||||||
|
++proto.feedbackDestroyN;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wl_proxy * wl_proxy_marshal_flags(struct wl_proxy * proxy,
|
||||||
|
uint32_t opcode, const struct wl_interface * interface,
|
||||||
|
uint32_t version, uint32_t flags, ...)
|
||||||
|
{
|
||||||
|
struct Proxy * p = (struct Proxy *)proxy;
|
||||||
|
(void)opcode;
|
||||||
|
(void)version;
|
||||||
|
|
||||||
|
CHECK(p == &proto.presentation);
|
||||||
|
if (flags & WL_MARSHAL_FLAG_DESTROY)
|
||||||
|
{
|
||||||
|
CHECK(!interface);
|
||||||
|
destroy(p);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(interface == &wp_presentation_feedback_interface);
|
||||||
|
CHECK(proto.feedbackN < ARRAY_LENGTH(proto.feedback));
|
||||||
|
p = &proto.feedback[proto.feedbackN++];
|
||||||
|
p->kind = PROXY_FEEDBACK;
|
||||||
|
return (struct wl_proxy *)p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wl_proxy_destroy(struct wl_proxy * proxy)
|
||||||
|
{
|
||||||
|
destroy((struct Proxy *)proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_handleFramePresented(uint64_t token, uint64_t time, bool valid)
|
||||||
|
{
|
||||||
|
CHECK(rec.noticeN < ARRAY_LENGTH(rec.notice));
|
||||||
|
struct Notice * notice = &rec.notice[rec.noticeN++];
|
||||||
|
notice->token = token;
|
||||||
|
notice->time = time;
|
||||||
|
notice->valid = valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphHandle app_registerGraph(const char * name, RingBuffer buffer,
|
||||||
|
float min, float max, GraphFormatFn formatFn)
|
||||||
|
{
|
||||||
|
CHECK(strcmp(name, "PHOTON") == 0);
|
||||||
|
CHECK(buffer);
|
||||||
|
CHECK(min == 0.0f);
|
||||||
|
CHECK(max == 30.0f);
|
||||||
|
CHECK(!formatFn);
|
||||||
|
++rec.graphRegisterN;
|
||||||
|
return (GraphHandle)&rec;
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_setGraphCompact(GraphHandle handle, bool compact)
|
||||||
|
{
|
||||||
|
CHECK(handle == (GraphHandle)&rec);
|
||||||
|
CHECK(compact);
|
||||||
|
++rec.graphCompactN;
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_unregisterGraph(GraphHandle handle)
|
||||||
|
{
|
||||||
|
CHECK(handle == (GraphHandle)&rec);
|
||||||
|
++rec.graphUnregisterN;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool waylandOutputGetFramePeriod(uint64_t * period)
|
||||||
|
{
|
||||||
|
if (!rec.havePeriod)
|
||||||
|
return false;
|
||||||
|
*period = rec.period;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct Proxy * feedback(unsigned int no)
|
||||||
|
{
|
||||||
|
CHECK(no < proto.feedbackN);
|
||||||
|
struct Proxy * p = &proto.feedback[no];
|
||||||
|
CHECK(p->kind == PROXY_FEEDBACK);
|
||||||
|
CHECK(p->listener);
|
||||||
|
CHECK(!p->dead);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clockReady(void)
|
||||||
|
{
|
||||||
|
struct Proxy * p = &proto.presentation;
|
||||||
|
CHECK(p->listener);
|
||||||
|
const struct wp_presentation_listener * listener =
|
||||||
|
(const struct wp_presentation_listener *)p->listener;
|
||||||
|
listener->clock_id(p->data, (struct wp_presentation *)p, CLOCK_MONOTONIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void present(unsigned int no, uint64_t time, uint32_t nsec)
|
||||||
|
{
|
||||||
|
struct Proxy * p = feedback(no);
|
||||||
|
const struct wp_presentation_feedback_listener * listener =
|
||||||
|
(const struct wp_presentation_feedback_listener *)p->listener;
|
||||||
|
const uint64_t sec = time / SEC;
|
||||||
|
listener->presented(p->data, (struct wp_presentation_feedback *)p,
|
||||||
|
sec >> 32, sec, nsec, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void discard(unsigned int no)
|
||||||
|
{
|
||||||
|
struct Proxy * p = feedback(no);
|
||||||
|
const struct wp_presentation_feedback_listener * listener =
|
||||||
|
(const struct wp_presentation_feedback_listener *)p->listener;
|
||||||
|
listener->discarded(p->data, (struct wp_presentation_feedback *)p);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void checkNotice(unsigned int no, uint64_t token,
|
||||||
|
uint64_t time, bool valid)
|
||||||
|
{
|
||||||
|
CHECK(no < rec.noticeN);
|
||||||
|
CHECK(rec.notice[no].token == token);
|
||||||
|
CHECK(rec.notice[no].time == time);
|
||||||
|
CHECK(rec.notice[no].valid == valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void start(void)
|
||||||
|
{
|
||||||
|
memset(&wlWm, 0, sizeof(wlWm));
|
||||||
|
memset(&proto, 0, sizeof(proto));
|
||||||
|
memset(&rec, 0, sizeof(rec));
|
||||||
|
now = 10 * SEC;
|
||||||
|
proto.presentation.kind = PROXY_PRESENTATION;
|
||||||
|
wlWm.presentation = (struct wp_presentation *)&proto.presentation;
|
||||||
|
wlWm.surface = (struct wl_surface *)(uintptr_t)1;
|
||||||
|
|
||||||
|
CHECK(waylandPresentationInit());
|
||||||
|
CHECK(rec.graphRegisterN == 1);
|
||||||
|
CHECK(rec.graphCompactN == 1);
|
||||||
|
CHECK(rec.graphUnregisterN == 0);
|
||||||
|
CHECK(!waylandPresentationFrame(1));
|
||||||
|
CHECK(proto.feedbackN == 0);
|
||||||
|
clockReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void finish(void)
|
||||||
|
{
|
||||||
|
waylandPresentationFree();
|
||||||
|
CHECK(!wlWm.presentation);
|
||||||
|
CHECK(proto.presentation.dead);
|
||||||
|
CHECK(proto.presentationDestroyN == 1);
|
||||||
|
CHECK(rec.graphUnregisterN == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testFeedbackFirst(void)
|
||||||
|
{
|
||||||
|
start();
|
||||||
|
struct WaylandPresentationFrame * frame = waylandPresentationFrame(11);
|
||||||
|
CHECK(frame);
|
||||||
|
|
||||||
|
present(0, 10 * SEC, 200000000);
|
||||||
|
CHECK(rec.noticeN == 0);
|
||||||
|
CHECK(proto.feedbackDestroyN == 1);
|
||||||
|
|
||||||
|
now = 10 * SEC + 100000000;
|
||||||
|
bool tracked;
|
||||||
|
waylandPresentationSwapDone(frame, true, &tracked);
|
||||||
|
CHECK(tracked);
|
||||||
|
CHECK(rec.noticeN == 1);
|
||||||
|
checkNotice(0, 11, 100000000, true);
|
||||||
|
|
||||||
|
finish();
|
||||||
|
CHECK(rec.noticeN == 1);
|
||||||
|
CHECK(proto.feedbackDestroyN == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testSwapFirst(void)
|
||||||
|
{
|
||||||
|
start();
|
||||||
|
struct WaylandPresentationFrame * frame = waylandPresentationFrame(12);
|
||||||
|
CHECK(frame);
|
||||||
|
|
||||||
|
now = 10 * SEC + 100000000;
|
||||||
|
bool tracked;
|
||||||
|
waylandPresentationSwapDone(frame, true, &tracked);
|
||||||
|
CHECK(tracked);
|
||||||
|
CHECK(rec.noticeN == 0);
|
||||||
|
|
||||||
|
present(0, 10 * SEC, 200000000);
|
||||||
|
CHECK(rec.noticeN == 1);
|
||||||
|
checkNotice(0, 12, 100000000, true);
|
||||||
|
|
||||||
|
finish();
|
||||||
|
CHECK(rec.noticeN == 1);
|
||||||
|
CHECK(proto.feedbackDestroyN == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testDiscard(void)
|
||||||
|
{
|
||||||
|
start();
|
||||||
|
struct WaylandPresentationFrame * first = waylandPresentationFrame(21);
|
||||||
|
CHECK(first);
|
||||||
|
now = 10 * SEC + 100000000;
|
||||||
|
bool tracked;
|
||||||
|
waylandPresentationSwapDone(first, true, &tracked);
|
||||||
|
CHECK(tracked);
|
||||||
|
discard(0);
|
||||||
|
CHECK(rec.noticeN == 1);
|
||||||
|
checkNotice(0, 21, 0, false);
|
||||||
|
|
||||||
|
struct WaylandPresentationFrame * second = waylandPresentationFrame(22);
|
||||||
|
CHECK(second);
|
||||||
|
discard(1);
|
||||||
|
CHECK(rec.noticeN == 1);
|
||||||
|
waylandPresentationSwapDone(second, true, &tracked);
|
||||||
|
CHECK(tracked);
|
||||||
|
CHECK(rec.noticeN == 2);
|
||||||
|
checkNotice(1, 22, 0, false);
|
||||||
|
|
||||||
|
finish();
|
||||||
|
CHECK(rec.noticeN == 2);
|
||||||
|
CHECK(proto.feedbackDestroyN == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testSwapFail(void)
|
||||||
|
{
|
||||||
|
start();
|
||||||
|
struct WaylandPresentationFrame * frame = waylandPresentationFrame(31);
|
||||||
|
CHECK(frame);
|
||||||
|
|
||||||
|
bool tracked = true;
|
||||||
|
waylandPresentationSwapDone(frame, false, &tracked);
|
||||||
|
CHECK(!tracked);
|
||||||
|
CHECK(rec.noticeN == 1);
|
||||||
|
checkNotice(0, 31, 0, false);
|
||||||
|
|
||||||
|
present(0, 10 * SEC, 200000000);
|
||||||
|
CHECK(rec.noticeN == 1);
|
||||||
|
CHECK(proto.feedbackDestroyN == 1);
|
||||||
|
|
||||||
|
tracked = true;
|
||||||
|
waylandPresentationSwapDone(NULL, true, &tracked);
|
||||||
|
CHECK(!tracked);
|
||||||
|
CHECK(rec.noticeN == 1);
|
||||||
|
|
||||||
|
finish();
|
||||||
|
CHECK(rec.noticeN == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testTokenZero(void)
|
||||||
|
{
|
||||||
|
start();
|
||||||
|
struct WaylandPresentationFrame * frame = waylandPresentationFrame(0);
|
||||||
|
CHECK(frame);
|
||||||
|
|
||||||
|
now = 10 * SEC + 100000000;
|
||||||
|
bool tracked = true;
|
||||||
|
waylandPresentationSwapDone(frame, true, &tracked);
|
||||||
|
CHECK(!tracked);
|
||||||
|
present(0, 10 * SEC, 200000000);
|
||||||
|
CHECK(rec.noticeN == 0);
|
||||||
|
|
||||||
|
finish();
|
||||||
|
CHECK(rec.noticeN == 0);
|
||||||
|
CHECK(proto.feedbackDestroyN == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testInvalidTime(void)
|
||||||
|
{
|
||||||
|
start();
|
||||||
|
struct WaylandPresentationFrame * frame = waylandPresentationFrame(41);
|
||||||
|
CHECK(frame);
|
||||||
|
now = 10 * SEC + 100000000;
|
||||||
|
bool tracked;
|
||||||
|
waylandPresentationSwapDone(frame, true, &tracked);
|
||||||
|
CHECK(tracked);
|
||||||
|
present(0, 10 * SEC, 1000000000);
|
||||||
|
CHECK(rec.noticeN == 1);
|
||||||
|
checkNotice(0, 41, 0, false);
|
||||||
|
|
||||||
|
frame = waylandPresentationFrame(42);
|
||||||
|
CHECK(frame);
|
||||||
|
now = 10 * SEC + 200000000;
|
||||||
|
waylandPresentationSwapDone(frame, true, &tracked);
|
||||||
|
CHECK(tracked);
|
||||||
|
present(1, 10 * SEC, 100000000);
|
||||||
|
CHECK(rec.noticeN == 2);
|
||||||
|
checkNotice(1, 42, 0, false);
|
||||||
|
|
||||||
|
finish();
|
||||||
|
CHECK(rec.noticeN == 2);
|
||||||
|
CHECK(proto.feedbackDestroyN == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testFreePending(void)
|
||||||
|
{
|
||||||
|
start();
|
||||||
|
struct WaylandPresentationFrame * frame = waylandPresentationFrame(51);
|
||||||
|
CHECK(frame);
|
||||||
|
now = 10 * SEC + 100000000;
|
||||||
|
bool tracked;
|
||||||
|
waylandPresentationSwapDone(frame, true, &tracked);
|
||||||
|
CHECK(tracked);
|
||||||
|
CHECK(rec.noticeN == 0);
|
||||||
|
CHECK(proto.feedbackDestroyN == 0);
|
||||||
|
|
||||||
|
finish();
|
||||||
|
CHECK(rec.noticeN == 1);
|
||||||
|
checkNotice(0, 51, 0, false);
|
||||||
|
CHECK(proto.feedbackDestroyN == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Test
|
||||||
|
{
|
||||||
|
const char * name;
|
||||||
|
void (*run)(void);
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct Test tests[] =
|
||||||
|
{
|
||||||
|
{ "feedback-first", testFeedbackFirst },
|
||||||
|
{ "swap-first" , testSwapFirst },
|
||||||
|
{ "discard" , testDiscard },
|
||||||
|
{ "swap-fail" , testSwapFail },
|
||||||
|
{ "token-zero" , testTokenZero },
|
||||||
|
{ "invalid-time" , testInvalidTime },
|
||||||
|
{ "free-pending" , testFreePending },
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char ** argv)
|
||||||
|
{
|
||||||
|
debug_init();
|
||||||
|
|
||||||
|
if (argc == 2)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < ARRAY_LENGTH(tests); ++i)
|
||||||
|
if (strcmp(argv[1], tests[i].name) == 0)
|
||||||
|
{
|
||||||
|
tests[i].run();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "unknown test: %s\n", argv[1]);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc != 1)
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_LENGTH(tests); ++i)
|
||||||
|
tests[i].run();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user