/*****************************************************************************
* css_test.c: CSS Parser test
*****************************************************************************
* Copyright (C) 2019 VideoLabs, VideoLAN and VLC Authors
*
* 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.
*
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#undef NDEBUG
#include <assert.h>
#include <vlc_common.h>
#include "css_parser.h"
#define BAILOUT(run) { fprintf(stderr,
"failed %s line %d\n", run, __LINE__); \
goto error; }
#define CHECK(test) run = (test); fprintf(stderr,
"* Running test %s\n", run);
#define EXPECT(foo)
if(!(foo)) BAILOUT(run)
#define PARSE_CSS(r) \
const char *run = r;\
vlc_css_parser_t p;\
vlc_css_parser_Init(&p);\
bool b = vlc_css_parser_ParseBytes(&p, (
const uint8_t *)css, strlen(css));\
EXPECT(b);\
vlc_css_parser_Debug(&p);\
const vlc_css_rule_t *rule = p.rules.p_first
static int test_element_selectors(
void)
{
const char * css =
"el1 { float0: 1; }\n"
".class1 { hex1: #F0000f; }\n"
"#id1 { text2: \"foo bar\
"; }\n"
;
PARSE_CSS(
"test_element_selectors");
CHECK(
"element selector");
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,
"el1"));
EXPECT(rule->p_selectors->type == SELECTOR_SIMPLE);
const vlc_css_declaration_t *decl = rule->p_declarations;
EXPECT(decl && !strcmp(rule->p_declarations->psz_property,
"float0"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[
0].term.type == TYPE_NONE);
EXPECT(decl->expr->seq[
0].term.val ==
1.
0);
CHECK(
"class selector");
rule = rule->p_next;
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,
"class1"));
EXPECT(rule->p_selectors->type == SPECIFIER_CLASS);
decl = rule->p_declarations;
EXPECT(decl && !strcmp(rule->p_declarations->psz_property,
"hex1"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[
0].term.type == TYPE_HEXCOLOR);
EXPECT(!strcmp(decl->expr->seq[
0].term.psz,
"#F0000f"));
CHECK(
"id selector");
rule = rule->p_next;
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,
"#id1"));
EXPECT(rule->p_selectors->type == SPECIFIER_ID);
decl = rule->p_declarations;
EXPECT(decl && !strcmp(rule->p_declarations->psz_property,
"text2"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[
0].term.type == TYPE_STRING);
EXPECT(!strcmp(decl->expr->seq[
0].term.psz,
"foo bar"));
vlc_css_parser_Clean(&p);
return 0;
error:
vlc_css_parser_Clean(&p);
return 1;
}
static int test_attributes_selectors(
void)
{
const char * css =
"attrib[foo=\"bar\
"] { text2: \"foobar\
"; }\n"
"attrib2[foo] { text2: \"foobar\
"; }\n"
"attribincludes[foo~=\"bar\
"] { text2: \"foobar\
"; }\n"
"attribdashmatch[foo|=\"bar\
"] { text2: \"foobar\
"; }\n"
"attribstarts[foo^=\"bar\
"] { text2: \"foobar\
"; }\n"
"attribends[foo$=\"bar\
"] { text2: \"foobar\
"; }\n"
"attribcontains[foo*=\"bar\
"] { text2: \"foobar\
"; }\n"
;
PARSE_CSS(
"test_attributes_selectors");
CHECK(
"attribute selector equals");
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,
"attrib"));
EXPECT(rule->p_selectors->type == SELECTOR_SIMPLE);
EXPECT(rule->p_selectors->specifiers.p_first);
EXPECT(rule->p_selectors->specifiers.p_first->type == SPECIFIER_ATTRIB);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->psz_name,
"foo"));
EXPECT(rule->p_selectors->specifiers.p_first->p_matchsel);
EXPECT(rule->p_selectors->specifiers.p_first->p_matchsel->match == MATCH_EQUALS);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->p_matchsel->psz_name,
"bar"));
CHECK(
"attribute selector key only");
rule = rule->p_next;
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,
"attrib2"));
EXPECT(rule->p_selectors->type == SELECTOR_SIMPLE);
EXPECT(rule->p_selectors->specifiers.p_first);
EXPECT(rule->p_selectors->specifiers.p_first->type == SPECIFIER_ATTRIB);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->psz_name,
"foo"));
EXPECT(rule->p_selectors->specifiers.p_first->p_matchsel == NULL);
CHECK(
"attribute selector ~=");
rule = rule->p_next;
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,
"attribincludes"));
EXPECT(rule->p_selectors->type == SELECTOR_SIMPLE);
EXPECT(rule->p_selectors->specifiers.p_first);
EXPECT(rule->p_selectors->specifiers.p_first->type == SPECIFIER_ATTRIB);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->psz_name,
"foo"));
EXPECT(rule->p_selectors->specifiers.p_first->match == MATCH_INCLUDES);
EXPECT(rule->p_selectors->specifiers.p_first->p_matchsel);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->p_matchsel->psz_name,
"bar"));
CHECK(
"attribute selector |=");
rule = rule->p_next;
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,
"attribdashmatch"));
EXPECT(rule->p_selectors->type == SELECTOR_SIMPLE);
EXPECT(rule->p_selectors->specifiers.p_first);
EXPECT(rule->p_selectors->specifiers.p_first->type == SPECIFIER_ATTRIB);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->psz_name,
"foo"));
EXPECT(rule->p_selectors->specifiers.p_first->match == MATCH_DASHMATCH);
EXPECT(rule->p_selectors->specifiers.p_first->p_matchsel);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->p_matchsel->psz_name,
"bar"));
CHECK(
"attribute selector ^=");
rule = rule->p_next;
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,
"attribstarts"));
EXPECT(rule->p_selectors->type == SELECTOR_SIMPLE);
EXPECT(rule->p_selectors->specifiers.p_first);
EXPECT(rule->p_selectors->specifiers.p_first->type == SPECIFIER_ATTRIB);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->psz_name,
"foo"));
EXPECT(rule->p_selectors->specifiers.p_first->match == MATCH_BEGINSWITH);
EXPECT(rule->p_selectors->specifiers.p_first->p_matchsel);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->p_matchsel->psz_name,
"bar"));
CHECK(
"attribute selector $=");
rule = rule->p_next;
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,
"attribends"));
EXPECT(rule->p_selectors->type == SELECTOR_SIMPLE);
EXPECT(rule->p_selectors->specifiers.p_first);
EXPECT(rule->p_selectors->specifiers.p_first->type == SPECIFIER_ATTRIB);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->psz_name,
"foo"));
EXPECT(rule->p_selectors->specifiers.p_first->match == MATCH_ENDSWITH);
EXPECT(rule->p_selectors->specifiers.p_first->p_matchsel);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->p_matchsel->psz_name,
"bar"));
CHECK(
"attribute selector *=");
rule = rule->p_next;
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,
"attribcontains"));
EXPECT(rule->p_selectors->type == SELECTOR_SIMPLE);
EXPECT(rule->p_selectors->specifiers.p_first);
EXPECT(rule->p_selectors->specifiers.p_first->type == SPECIFIER_ATTRIB);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->psz_name,
"foo"));
EXPECT(rule->p_selectors->specifiers.p_first->match == MATCH_CONTAINS);
EXPECT(rule->p_selectors->specifiers.p_first->p_matchsel);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->p_matchsel->psz_name,
"bar"));
vlc_css_parser_Clean(&p);
return 0;
error:
vlc_css_parser_Clean(&p);
return 1;
}
static int test_pseudo_selectors(
void)
{
const char * css =
":pseudo { text2: \"foobar\
"; }\n"
"::cue(b) { color: red; }\n" /* WebVTT special */
"::cue(v[voice=\"Tom\
"]) { color: blue; }\n"
;
PARSE_CSS(
"test_pseudo_selectors");
CHECK(
"pseudoclass selector");
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,
"pseudo"));
EXPECT(rule->p_selectors->type == SELECTOR_PSEUDOCLASS);
CHECK(
"pseudoelement ::cue");
rule = rule->p_next;
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,
"cue"));
EXPECT(rule->p_selectors->type == SELECTOR_PSEUDOELEMENT);
CHECK(
"pseudoelement ::cue(v[voice])");
rule = rule->p_next;
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,
"cue"));
EXPECT(rule->p_selectors->type == SELECTOR_PSEUDOELEMENT);
EXPECT(rule->p_selectors->specifiers.p_first);
EXPECT(rule->p_selectors->specifiers.p_first->type == SELECTOR_SIMPLE);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->psz_name,
"v"));
EXPECT(rule->p_selectors->specifiers.p_first->specifiers.p_first);
EXPECT(rule->p_selectors->specifiers.p_first->specifiers.p_first->type == SPECIFIER_AT
TRIB);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->specifiers.p_first->psz_name, "voice"));
EXPECT(rule->p_selectors->specifiers.p_first->specifiers.p_first->p_matchsel);
EXPECT(rule->p_selectors->specifiers.p_first->specifiers.p_first->p_matchsel->match == MATCH_EQUALS);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->specifiers.p_first->p_matchsel->psz_name, "Tom"));
vlc_css_parser_Clean(&p);
return 0;
error:
vlc_css_parser_Clean(&p);
return 1;
}
static int test_combinators(void)
{
const char * css =
"parent1 child1 { float0: 1; }\n"
"el2,el3 { float0: 1; }\n"
"el4+el0 { float0: 1; }\n"
"el5~el0 { float0: 1; }\n"
"el6>el0 { float0: 1; }\n"
;
PARSE_CSS("test_combinators");
CHECK("selectors combination parent child");
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,"parent1"));
EXPECT(rule->p_selectors->specifiers.p_first);
EXPECT(rule->p_selectors->specifiers.p_first->combinator == RELATION_DESCENDENT);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->psz_name, "child1"));
CHECK("selectors combination alternative");
rule = rule->p_next;
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,"el2"));
EXPECT(rule->p_selectors->p_next);
EXPECT(!strcmp(rule->p_selectors->p_next->psz_name,"el3"));
CHECK("selectors combination directadjacent");
rule = rule->p_next;
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,"el4"));
EXPECT(rule->p_selectors->specifiers.p_first);
EXPECT(rule->p_selectors->specifiers.p_first->combinator == RELATION_DIRECTADJACENT);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->psz_name, "el0"));
CHECK("selectors combination directprecedent");
rule = rule->p_next;
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,"el5"));
EXPECT(rule->p_selectors->specifiers.p_first);
EXPECT(rule->p_selectors->specifiers.p_first->combinator == RELATION_INDIRECTADJACENT);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->psz_name, "el0"));
CHECK("selectors combination child");
rule = rule->p_next;
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,"el6"));
EXPECT(rule->p_selectors->specifiers.p_first);
EXPECT(rule->p_selectors->specifiers.p_first->combinator == RELATION_CHILD);
EXPECT(!strcmp(rule->p_selectors->specifiers.p_first->psz_name, "el0"));
vlc_css_parser_Clean(&p);
return 0;
error:
vlc_css_parser_Clean(&p);
return 1;
}
static int test_values(void)
{
const char * css =
"values { "
" neg: -1;"
" ems: 100em;"
" exs: 100ex;"
" pixels: 100px;"
" points: 100pt; "
" mm: 100mm;"
" percent: 100%;"
" ms: 100ms;"
" hz: 100Hz;"
" degrees: 100deg;"
" dimension: 100 -200em 300px;"
" string: \"foobar\";"
" function: foo(1);"
" identifier: foobar;"
" hexcolor: #ff00ff;"
" unicoderange: U+00-FF;"
" uri: url(http://crap/);"
"}\n"
;
PARSE_CSS("test_values");
CHECK("values");
EXPECT(rule && rule->b_valid);
EXPECT(!strcmp(rule->p_selectors->psz_name,"values"));
const vlc_css_declaration_t *decl = rule->p_declarations;
EXPECT(decl && !strcmp(decl->psz_property, "neg"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[0].term.type == TYPE_NONE);
EXPECT(decl->expr->seq[0].term.val == -1.0);
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "ems"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[0].term.type == TYPE_EMS);
EXPECT(decl->expr->seq[0].term.val == 100);
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "exs"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[0].term.type == TYPE_EXS);
EXPECT(decl->expr->seq[0].term.val == 100);
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "pixels"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[0].term.type == TYPE_PIXELS);
EXPECT(decl->expr->seq[0].term.val == 100);
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "points"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[0].term.type == TYPE_POINTS);
EXPECT(decl->expr->seq[0].term.val == 100);
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "mm"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[0].term.type == TYPE_MILLIMETERS);
EXPECT(decl->expr->seq[0].term.val == 100);
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "percent"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[0].term.type == TYPE_PERCENT);
EXPECT(decl->expr->seq[0].term.val == 100);
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "ms"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[0].term.type == TYPE_MILLISECONDS);
EXPECT(decl->expr->seq[0].term.val == 100);
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "hz"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[0].term.type == TYPE_HERTZ);
EXPECT(decl->expr->seq[0].term.val == 100);
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "degrees"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[0].term.type == TYPE_DEGREES);
EXPECT(decl->expr->seq[0].term.val == 100);
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "dimension"));
EXPECT(decl->expr && decl->expr->i_count == 3);
EXPECT(decl->expr->seq[0].term.type == TYPE_NONE);
EXPECT(decl->expr->seq[0].term.val == 100);
EXPECT(decl->expr->seq[1].term.type == TYPE_EMS);
EXPECT(decl->expr->seq[1].term.val == -200);
EXPECT(decl->expr->seq[2].term.type == TYPE_PIXELS);
EXPECT(decl->expr->seq[2].term.val == 300);
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "string"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[0].term.type == TYPE_STRING);
EXPECT(!strcmp(decl->expr->seq[0].term.psz, "foobar"));
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "function"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[0].term.type == TYPE_FUNCTION);
/* todo */
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "identifier"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[0].term.type == TYPE_IDENTIFIER);
EXPECT(!strcmp(decl->expr->seq[0].term.psz, "foobar"));
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "hexcolor"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[0].term.type == TYPE_HEXCOLOR);
EXPECT(!strcmp(decl->expr->seq[0].term.psz, "#ff00ff"));
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "unicoderange"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[0].term.type == TYPE_UNICODERANGE);
EXPECT(!strcmp(decl->expr->seq[0].term.psz, "U+00-FF"));
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "uri"));
EXPECT(decl->expr && decl->expr->i_count);
EXPECT(decl->expr->seq[0].term.type == TYPE_URI);
EXPECT(!strcmp(decl->expr->seq[0].term.psz, "url(http://crap/)"));
vlc_css_parser_Clean(&p);
return 0;
error:
vlc_css_parser_Clean(&p);
return 1;
}
static int test_important_and_operators(void)
{
const char *css =
"values {"
" important-compact: red!important;"
" important-spaced: blue ! important;"
" slash: 1 / 2;"
" comma: foo, bar;"
"}\n";
PARSE_CSS("test_important_and_operators");
CHECK("important and operators");
EXPECT(rule && rule->b_valid);
const vlc_css_declaration_t *decl = rule->p_declarations;
EXPECT(decl && !strcmp(decl->psz_property, "important-compact"));
EXPECT(decl->expr && decl->expr->i_count == 1);
EXPECT(decl->expr->seq[0].term.type == TYPE_IDENTIFIER);
EXPECT(!strcmp(decl->expr->seq[0].term.psz, "red"));
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "important-spaced"));
EXPECT(decl->expr && decl->expr->i_count == 1);
EXPECT(decl->expr->seq[0].term.type == TYPE_IDENTIFIER);
EXPECT(!strcmp(decl->expr->seq[0].term.psz, "blue"));
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "slash"));
EXPECT(decl->expr && decl->expr->i_count == 2);
EXPECT(decl->expr->seq[1].op == '/');
decl = decl->p_next;
EXPECT(decl && !strcmp(decl->psz_property, "comma"));
EXPECT(decl->expr && decl->expr->i_count == 2);
EXPECT(decl->expr->seq[1].op == ',');
EXPECT(!decl->p_next);
vlc_css_parser_Clean(&p);
return 0;
error:
vlc_css_parser_Clean(&p);
return 1;
}
static int test_error_cases(void)
{
const char *css =
/* Invalid charset */
"@charset \"UTF-8\" extra;\n"
"@charset ;\n"
/* Invalid block */
"el1 { color { ; color: red } }\n"
"el2 { color: red; }\n"
"el3 { color: red !important fail; }\n"
"el4 { font-family: ; }\n"
"el5 { weight: *; }\n"
"el6 { color: red; !important }\n"
/* Invalid selectors */
"#123abc { color: red; }\n" /* HASH starting with digit triggers YYERROR */
"el7 + ;\n" /* combinators without right side */
"el8 ~ ;\n"
"el9 > ;\n"
"el10 , ;\n"
/* Invalid declarations */
"div { ; }\n"
"div { color: ; }\n"
"div { color: *; }\n"
/* Invalid function / expr */
"div { foo(): 1; }\n"
"div { bar( ; }\n"
;
PARSE_CSS("test_error_cases");
/* We just want to make sure the parser
* does not crash on error handling and backtracking
* and frees memory */
VLC_UNUSED(rule);
vlc_css_parser_Clean(&p);
return 0;
error:
vlc_css_parser_Clean(&p);
return 1;
}
int main(void)
{
return test_element_selectors() ||
test_attributes_selectors() ||
test_pseudo_selectors() ||
test_combinators() ||
test_values() ||
test_important_and_operators() ||
test_error_cases();
}