0,0 → 1,787 |
#include <ctype.h> |
#include <inttypes.h> |
#include <stdbool.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <string.h> |
|
#include <libcss/libcss.h> |
|
#include "stylesheet.h" |
#include "utils/utils.h" |
|
#include "testutils.h" |
|
/** \todo at some point, we need to extend this to handle nested blocks */ |
typedef struct exp_entry { |
int type; |
#define MAX_RULE_NAME_LEN (128) |
char name[MAX_RULE_NAME_LEN]; |
size_t bclen; |
size_t bcused; |
uint8_t *bytecode; |
|
size_t stlen; |
size_t stused; |
struct stentry { |
size_t off; |
char *string; |
} *stringtab; |
} exp_entry; |
|
typedef struct line_ctx { |
size_t buflen; |
size_t bufused; |
uint8_t *buf; |
|
size_t explen; |
size_t expused; |
exp_entry *exp; |
|
bool indata; |
bool inerrors; |
bool inexp; |
|
bool inrule; |
} line_ctx; |
|
static bool handle_line(const char *data, size_t datalen, void *pw); |
static void css__parse_expected(line_ctx *ctx, const char *data, size_t len); |
static void run_test(const uint8_t *data, size_t len, |
exp_entry *exp, size_t explen); |
static bool validate_rule_selector(css_rule_selector *s, exp_entry *e); |
static void validate_rule_charset(css_rule_charset *s, exp_entry *e, |
int testnum); |
static void validate_rule_import(css_rule_import *s, exp_entry *e, |
int testnum); |
|
static void dump_selector_list(css_selector *list, char **ptr); |
static void dump_selector(css_selector *selector, char **ptr); |
static void dump_selector_detail(css_selector_detail *detail, char **ptr); |
static void dump_string(lwc_string *string, char **ptr); |
|
static void *myrealloc(void *data, size_t len, void *pw) |
{ |
UNUSED(pw); |
|
return realloc(data, len); |
} |
|
static css_error resolve_url(void *pw, |
const char *base, lwc_string *rel, lwc_string **abs) |
{ |
UNUSED(pw); |
UNUSED(base); |
|
/* About as useless as possible */ |
*abs = lwc_string_ref(rel); |
|
return CSS_OK; |
} |
|
static bool fail_because_lwc_leaked = false; |
|
static void |
printing_lwc_iterator(lwc_string *str, void *pw) |
{ |
UNUSED(pw); |
|
printf(" DICT: %*s\n", (int)(lwc_string_length(str)), lwc_string_data(str)); |
fail_because_lwc_leaked = true; |
} |
|
static void destroy_expected(line_ctx *ctx) |
{ |
while (ctx->expused > 0) { |
exp_entry *victim = &ctx->exp[--ctx->expused]; |
|
if (victim->bytecode != NULL) |
free(victim->bytecode); |
|
while (victim->stused > 0) { |
free(victim->stringtab[--victim->stused].string); |
} |
|
free(victim->stringtab); |
} |
} |
|
int main(int argc, char **argv) |
{ |
line_ctx ctx; |
|
if (argc != 2) { |
printf("Usage: %s <filename>\n", argv[0]); |
return 1; |
} |
|
ctx.buflen = css__parse_filesize(argv[1]); |
if (ctx.buflen == 0) |
return 1; |
|
ctx.buf = malloc(ctx.buflen); |
if (ctx.buf == NULL) { |
printf("Failed allocating %u bytes\n", |
(unsigned int) ctx.buflen); |
return 1; |
} |
|
ctx.buf[0] = '\0'; |
ctx.bufused = 0; |
ctx.explen = 0; |
ctx.expused = 0; |
ctx.exp = NULL; |
ctx.indata = false; |
ctx.inerrors = false; |
ctx.inexp = false; |
|
assert(css__parse_testfile(argv[1], handle_line, &ctx) == true); |
|
/* and run final test */ |
if (ctx.bufused > 0) |
run_test(ctx.buf, ctx.bufused, ctx.exp, ctx.expused); |
|
free(ctx.buf); |
|
destroy_expected(&ctx); |
free(ctx.exp); |
|
lwc_iterate_strings(printing_lwc_iterator, NULL); |
|
assert(fail_because_lwc_leaked == false); |
|
printf("PASS\n"); |
|
return 0; |
} |
|
bool handle_line(const char *data, size_t datalen, void *pw) |
{ |
line_ctx *ctx = (line_ctx *) pw; |
|
if (data[0] == '#') { |
if (ctx->inexp) { |
/* This marks end of testcase, so run it */ |
|
run_test(ctx->buf, ctx->bufused, |
ctx->exp, ctx->expused); |
|
ctx->buf[0] = '\0'; |
ctx->bufused = 0; |
|
destroy_expected(ctx); |
} |
|
if (ctx->indata && strncasecmp(data+1, "errors", 6) == 0) { |
ctx->indata = false; |
ctx->inerrors = true; |
ctx->inexp = false; |
} else if (ctx->inerrors && |
strncasecmp(data+1, "expected", 8) == 0) { |
ctx->indata = false; |
ctx->inerrors = false; |
ctx->inexp = true; |
ctx->inrule = false; |
} else if (ctx->inexp && strncasecmp(data+1, "data", 4) == 0) { |
ctx->indata = true; |
ctx->inerrors = false; |
ctx->inexp = false; |
} else if (ctx->indata) { |
memcpy(ctx->buf + ctx->bufused, data, datalen); |
ctx->bufused += datalen; |
} else { |
ctx->indata = (strncasecmp(data+1, "data", 4) == 0); |
ctx->inerrors = (strncasecmp(data+1, "errors", 6) == 0); |
ctx->inexp = (strncasecmp(data+1, "expected", 8) == 0); |
} |
} else { |
if (ctx->indata) { |
memcpy(ctx->buf + ctx->bufused, data, datalen); |
ctx->bufused += datalen; |
} |
if (ctx->inexp) { |
if (data[datalen - 1] == '\n') |
datalen -= 1; |
|
css__parse_expected(ctx, data, datalen); |
} |
} |
|
return true; |
} |
|
void css__parse_expected(line_ctx *ctx, const char *data, size_t len) |
{ |
/* Ignore blanks or lines that don't start with | */ |
if (len == 0 || data[0] != '|') |
return; |
|
if (ctx->inrule == false) { |
char *name; |
int type; |
|
start_rule: |
type = strtol(data + 1, &name, 10); |
|
while (isspace(*name)) |
name++; |
|
/* Append to list of expected rules */ |
if (ctx->expused == ctx->explen) { |
size_t num = ctx->explen == 0 ? 4 : ctx->explen; |
|
exp_entry *temp = realloc(ctx->exp, |
num * 2 * sizeof(exp_entry)); |
if (temp == NULL) { |
assert(0 && "No memory for expected rules"); |
} |
|
ctx->exp = temp; |
ctx->explen = num * 2; |
} |
|
ctx->exp[ctx->expused].type = type; |
memcpy(ctx->exp[ctx->expused].name, name, |
min(len - (name - data), MAX_RULE_NAME_LEN)); |
ctx->exp[ctx->expused].name[min(len - (name - data), |
MAX_RULE_NAME_LEN - 1)] = '\0'; |
ctx->exp[ctx->expused].bclen = 0; |
ctx->exp[ctx->expused].bcused = 0; |
ctx->exp[ctx->expused].bytecode = NULL; |
ctx->exp[ctx->expused].stlen = 0; |
ctx->exp[ctx->expused].stused = 0; |
ctx->exp[ctx->expused].stringtab = NULL; |
|
ctx->expused++; |
|
ctx->inrule = true; |
} else { |
char *next = (char *) data + 1; |
exp_entry *rule = &ctx->exp[ctx->expused - 1]; |
|
if (data[2] != ' ') { |
ctx->inrule = false; |
goto start_rule; |
} |
|
while (next < data + len) { |
/* Skip whitespace */ |
while (next < data + len && isspace(*next)) |
next++; |
|
if (next == data + len) |
break; |
|
if (rule->bcused >= rule->bclen) { |
size_t num = rule->bcused == 0 ? 4 : |
rule->bcused; |
|
uint8_t *temp = realloc(rule->bytecode, |
num * 2); |
if (temp == NULL) { |
assert(0 && "No memory for bytecode"); |
} |
|
rule->bytecode = temp; |
rule->bclen = num * 2; |
} |
|
if (*next == 'P') { |
/* Pointer */ |
const char *str; |
|
while (next < data + len && *next != '(') |
next++; |
str = next + 1; |
while (next < data + len && *next != ')') |
next++; |
next++; |
|
if (rule->stused >= rule->stlen) { |
size_t num = rule->stused == 0 ? 4 : |
rule->stused; |
|
struct stentry *temp = realloc( |
rule->stringtab, |
num * 2 * sizeof(struct stentry)); |
if (temp == NULL) { |
assert(0 && |
"No memory for string table"); |
} |
|
rule->stringtab = temp; |
rule->stlen = num * 2; |
} |
|
rule->stringtab[rule->stused].off = |
rule->bcused; |
rule->stringtab[rule->stused].string = |
malloc(next - str); |
assert(rule->stringtab[rule->stused].string != |
NULL); |
memcpy(rule->stringtab[rule->stused].string, |
str, next - str - 1); |
rule->stringtab[rule->stused].string[ |
next - str - 1] = '\0'; |
|
rule->bcused += sizeof(css_code_t); |
rule->stused++; |
} else { |
/* Assume hexnum */ |
uint32_t val = strtoul(next, &next, 16); |
|
/* Append to bytecode */ |
memcpy(rule->bytecode + rule->bcused, |
&val, sizeof(val)); |
rule->bcused += sizeof(val); |
} |
} |
} |
} |
|
static void report_fail(const uint8_t *data, size_t datalen, exp_entry *e) |
{ |
uint32_t bcoff; |
|
printf(" Data: %.*s\n", (int)datalen, data); |
|
printf(" Expected entry:\n"); |
printf(" entry type:%d name:%s\n", e->type, e->name); |
printf(" bytecode len:%ld used:%ld\n", e->bclen, e->bcused); |
printf(" bytecode "); |
for (bcoff = 0; bcoff < e->bcused; bcoff++) { |
printf("%.2x ", ((uint8_t *) e->bytecode)[bcoff]); |
} |
printf("\n string table len:%ld used %ld\n", e->stlen, e->stused); |
/* |
struct stentry { |
size_t off; |
char *string; |
} *stringtab; |
*/ |
} |
|
void run_test(const uint8_t *data, size_t len, exp_entry *exp, size_t explen) |
{ |
css_stylesheet_params params; |
css_stylesheet *sheet; |
css_rule *rule; |
css_error error; |
size_t e; |
static int testnum; |
bool failed; |
|
params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1; |
params.level = CSS_LEVEL_21; |
params.charset = "UTF-8"; |
params.url = "foo"; |
params.title = NULL; |
params.allow_quirks = false; |
params.inline_style = false; |
params.resolve = resolve_url; |
params.resolve_pw = NULL; |
params.import = NULL; |
params.import_pw = NULL; |
params.color = NULL; |
params.color_pw = NULL; |
params.font = NULL; |
params.font_pw = NULL; |
|
assert(css_stylesheet_create(¶ms, myrealloc, NULL, |
&sheet) == CSS_OK); |
|
error = css_stylesheet_append_data(sheet, data, len); |
if (error != CSS_OK && error != CSS_NEEDDATA) { |
printf("Failed appending data: %d\n", error); |
assert(0); |
} |
|
error = css_stylesheet_data_done(sheet); |
assert(error == CSS_OK || error == CSS_IMPORTS_PENDING); |
|
while (error == CSS_IMPORTS_PENDING) { |
lwc_string *url; |
uint64_t media; |
|
error = css_stylesheet_next_pending_import(sheet, |
&url, &media); |
assert(error == CSS_OK || error == CSS_INVALID); |
|
if (error == CSS_OK) { |
css_stylesheet *import; |
char *buf = alloca(lwc_string_length(url) + 1); |
|
memcpy(buf, lwc_string_data(url), |
lwc_string_length(url)); |
buf[lwc_string_length(url)] = '\0'; |
|
params.url = buf; |
|
assert(css_stylesheet_create(¶ms, |
myrealloc, NULL, &import) == CSS_OK); |
|
assert(css_stylesheet_register_import(sheet, |
import) == CSS_OK); |
|
error = CSS_IMPORTS_PENDING; |
lwc_string_unref(url); |
} |
} |
|
e = 0; |
testnum++; |
|
printf("Test %d: ", testnum); |
|
if (sheet->rule_count != explen) { |
printf("%d: Got %d rules. Expected %u\n", |
testnum, sheet->rule_count, (int) explen); |
assert(0 && "Unexpected number of rules"); |
} |
|
for (rule = sheet->rule_list; rule != NULL; rule = rule->next, e++) { |
if (rule->type != exp[e].type) { |
printf("%d: Got type %d. Expected %d\n", |
testnum, rule->type, exp[e].type); |
assert(0 && "Types differ"); |
} |
|
switch (rule->type) { |
case CSS_RULE_SELECTOR: |
failed = validate_rule_selector((css_rule_selector *) rule, &exp[e]); |
break; |
case CSS_RULE_CHARSET: |
validate_rule_charset((css_rule_charset *) rule, |
&exp[e], testnum); |
failed = false; |
break; |
case CSS_RULE_IMPORT: |
validate_rule_import((css_rule_import *) rule, |
&exp[e], testnum); |
failed = false; |
break; |
default: |
printf("%d: Unhandled rule type %d\n", |
testnum, rule->type); |
failed = false; |
break; |
} |
|
if (failed) { |
report_fail(data, len, &exp[e]); |
assert(0); |
} |
} |
|
assert(e == explen); |
|
css_stylesheet_destroy(sheet); |
|
printf("PASS\n"); |
} |
|
|
bool validate_rule_selector(css_rule_selector *s, exp_entry *e) |
{ |
char name[MAX_RULE_NAME_LEN]; |
char *ptr = name; |
uint32_t i; |
|
/* Build selector string */ |
for (i = 0; i < s->base.items; i++) { |
dump_selector_list(s->selectors[i], &ptr); |
if (i != (uint32_t) (s->base.items - 1)) { |
memcpy(ptr, ", ", 2); |
ptr += 2; |
} |
} |
*ptr = '\0'; |
|
/* Compare with expected selector */ |
if (strcmp(e->name, name) != 0) { |
printf("FAIL Mismatched names\n" |
" Got name '%s'. Expected '%s'\n", |
name, e->name); |
return true; |
} |
|
/* Now compare bytecode */ |
if (e->bytecode != NULL && s->style == NULL) { |
printf("FAIL No bytecode\n" |
" Expected bytecode but none created\n"); |
return true; |
} else if (e->bytecode == NULL && s->style != NULL) { |
printf("FAIL Unexpected bytecode\n" |
" No bytecode expected but some created\n"); |
return true; |
} else if (e->bytecode != NULL && s->style != NULL) { |
size_t i; |
|
if ((s->style->used * sizeof(css_code_t)) != e->bcused) { |
printf("FAIL Bytecode lengths differ\n" |
" Got length %ld, Expected %u\n", |
(s->style->used * sizeof(css_code_t)), |
(int) e->bcused); |
return true; |
} |
|
for (i = 0; i < e->bcused; i++) { |
size_t j; |
|
for (j = 0; j < e->stused; j++) { |
if (e->stringtab[j].off == i) |
break; |
} |
|
if (j != e->stused) { |
/* String */ |
lwc_string *p; |
|
css__stylesheet_string_get(s->style->sheet, (s->style->bytecode[i / sizeof(css_code_t)]), &p); |
|
if (lwc_string_length(p) != |
strlen(e->stringtab[j].string) || |
memcmp(lwc_string_data(p), |
e->stringtab[j].string, |
lwc_string_length(p)) != 0) { |
printf("FAIL Strings differ\n" |
" Got string '%.*s'. " |
"Expected '%s'\n", |
(int) lwc_string_length(p), |
lwc_string_data(p), |
e->stringtab[j].string); |
return true; |
} |
|
i += sizeof (css_code_t) - 1; |
} else if (((uint8_t *) s->style->bytecode)[i] != |
e->bytecode[i]) { |
printf("FAIL Bytecode differs\n" |
" Bytecode differs at %u\n ", |
(int) i); |
while (i < e->bcused) { |
printf("%.2x ", |
((uint8_t *) s->style->bytecode)[i]); |
i++; |
} |
printf("\n"); |
return true; |
} |
} |
} |
return false; |
} |
|
void validate_rule_charset(css_rule_charset *s, exp_entry *e, int testnum) |
{ |
char name[MAX_RULE_NAME_LEN]; |
char *ptr = name; |
|
dump_string(s->encoding, &ptr); |
*ptr = '\0'; |
|
if (strcmp(name, e->name) != 0) { |
printf("%d: Got charset '%s'. Expected '%s'\n", |
testnum, name, e->name); |
assert(0 && "Mismatched charsets"); |
} |
} |
|
void validate_rule_import(css_rule_import *s, exp_entry *e, int testnum) |
{ |
if (strncmp(lwc_string_data(s->url), e->name, |
lwc_string_length(s->url)) != 0) { |
printf("%d: Got URL '%.*s'. Expected '%s'\n", |
testnum, (int) lwc_string_length(s->url), |
lwc_string_data(s->url), |
e->name); |
assert(0 && "Mismatched URLs"); |
} |
|
css_stylesheet_destroy(s->sheet); |
} |
|
void dump_selector_list(css_selector *list, char **ptr) |
{ |
if (list->combinator != NULL) { |
dump_selector_list(list->combinator, ptr); |
} |
|
switch (list->data.comb) { |
case CSS_COMBINATOR_NONE: |
break; |
case CSS_COMBINATOR_ANCESTOR: |
(*ptr)[0] = ' '; |
*ptr += 1; |
break; |
case CSS_COMBINATOR_PARENT: |
memcpy(*ptr, " > ", 3); |
*ptr += 3; |
break; |
case CSS_COMBINATOR_SIBLING: |
memcpy(*ptr, " + ", 3); |
*ptr += 3; |
break; |
case CSS_COMBINATOR_GENERIC_SIBLING: |
memcpy(*ptr, " ~ ", 3); |
*ptr += 3; |
break; |
} |
|
dump_selector(list, ptr); |
} |
|
void dump_selector(css_selector *selector, char **ptr) |
{ |
css_selector_detail *d = &selector->data; |
|
while (true) { |
dump_selector_detail(d, ptr); |
|
if (d->next == 0) |
break; |
|
d++; |
} |
} |
|
void dump_selector_detail(css_selector_detail *detail, char **ptr) |
{ |
if (detail->negate) |
*ptr += sprintf(*ptr, ":not("); |
|
switch (detail->type) { |
case CSS_SELECTOR_ELEMENT: |
if (lwc_string_length(detail->qname.name) == 1 && |
lwc_string_data(detail->qname.name)[0] == '*' && |
detail->next == 0) { |
dump_string(detail->qname.name, ptr); |
} else if (lwc_string_length(detail->qname.name) != 1 || |
lwc_string_data(detail->qname.name)[0] != '*') { |
dump_string(detail->qname.name, ptr); |
} |
break; |
case CSS_SELECTOR_CLASS: |
**ptr = '.'; |
*ptr += 1; |
dump_string(detail->qname.name, ptr); |
break; |
case CSS_SELECTOR_ID: |
**ptr = '#'; |
*ptr += 1; |
dump_string(detail->qname.name, ptr); |
break; |
case CSS_SELECTOR_PSEUDO_CLASS: |
case CSS_SELECTOR_PSEUDO_ELEMENT: |
**ptr = ':'; |
*ptr += 1; |
dump_string(detail->qname.name, ptr); |
if (detail->value_type == CSS_SELECTOR_DETAIL_VALUE_STRING) { |
if (detail->value.string != NULL) { |
**ptr = '('; |
*ptr += 1; |
dump_string(detail->value.string, ptr); |
**ptr = ')'; |
*ptr += 1; |
} |
} else { |
*ptr += sprintf(*ptr, "(%dn+%d)", |
detail->value.nth.a, |
detail->value.nth.b); |
} |
break; |
case CSS_SELECTOR_ATTRIBUTE: |
**ptr = '['; |
*ptr += 1; |
dump_string(detail->qname.name, ptr); |
**ptr = ']'; |
*ptr += 1; |
break; |
case CSS_SELECTOR_ATTRIBUTE_EQUAL: |
**ptr = '['; |
*ptr += 1; |
dump_string(detail->qname.name, ptr); |
(*ptr)[0] = '='; |
(*ptr)[1] = '"'; |
*ptr += 2; |
dump_string(detail->value.string, ptr); |
(*ptr)[0] = '"'; |
(*ptr)[1] = ']'; |
*ptr += 2; |
break; |
case CSS_SELECTOR_ATTRIBUTE_DASHMATCH: |
**ptr = '['; |
*ptr += 1; |
dump_string(detail->qname.name, ptr); |
(*ptr)[0] = '|'; |
(*ptr)[1] = '='; |
(*ptr)[2] = '"'; |
*ptr += 3; |
dump_string(detail->value.string, ptr); |
(*ptr)[0] = '"'; |
(*ptr)[1] = ']'; |
*ptr += 2; |
break; |
case CSS_SELECTOR_ATTRIBUTE_INCLUDES: |
**ptr = '['; |
*ptr += 1; |
dump_string(detail->qname.name, ptr); |
(*ptr)[0] = '~'; |
(*ptr)[1] = '='; |
(*ptr)[2] = '"'; |
*ptr += 3; |
dump_string(detail->value.string, ptr); |
(*ptr)[0] = '"'; |
(*ptr)[1] = ']'; |
*ptr += 2; |
break; |
case CSS_SELECTOR_ATTRIBUTE_PREFIX: |
**ptr = '['; |
*ptr += 1; |
dump_string(detail->qname.name, ptr); |
(*ptr)[0] = '^'; |
(*ptr)[1] = '='; |
(*ptr)[2] = '"'; |
*ptr += 3; |
dump_string(detail->value.string, ptr); |
(*ptr)[0] = '"'; |
(*ptr)[1] = ']'; |
*ptr += 2; |
break; |
case CSS_SELECTOR_ATTRIBUTE_SUFFIX: |
**ptr = '['; |
*ptr += 1; |
dump_string(detail->qname.name, ptr); |
(*ptr)[0] = '$'; |
(*ptr)[1] = '='; |
(*ptr)[2] = '"'; |
*ptr += 3; |
dump_string(detail->value.string, ptr); |
(*ptr)[0] = '"'; |
(*ptr)[1] = ']'; |
*ptr += 2; |
break; |
case CSS_SELECTOR_ATTRIBUTE_SUBSTRING: |
**ptr = '['; |
*ptr += 1; |
dump_string(detail->qname.name, ptr); |
(*ptr)[0] = '*'; |
(*ptr)[1] = '='; |
(*ptr)[2] = '"'; |
*ptr += 3; |
dump_string(detail->value.string, ptr); |
(*ptr)[0] = '"'; |
(*ptr)[1] = ']'; |
*ptr += 2; |
break; |
} |
|
if (detail->negate) |
*ptr += sprintf(*ptr, ")"); |
} |
|
void dump_string(lwc_string *string, char **ptr) |
{ |
*ptr += sprintf(*ptr, "%.*s", (int) lwc_string_length(string), |
lwc_string_data(string)); |
} |