Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/rust/cssparser/src/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 43 kB image not shown  

Quelle  tests.rs   Sprache: unbekannt

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#[cfg(feature = "bench")]
extern crate test;

use serde_json::{json, Map, Value};

#[cfg(feature = "bench")]
use self::test::Bencher;

use super::{
    parse_important, parse_nth, parse_one_declaration, parse_one_rule, stylesheet_encoding,
    AtRuleParser, BasicParseError, BasicParseErrorKind, CowRcStr, DeclarationParser, Delimiter,
    EncodingSupport, ParseError, ParseErrorKind, Parser, ParserInput, ParserState,
    QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, SourceLocation, StyleSheetParser,
    ToCss, Token, TokenSerializationType, UnicodeRange,
};

macro_rules! JArray {
    ($($e: expr,)*) => { JArray![ $( $e ),* ] };
    ($($e: expr),*) => { Value::Array(vec!( $( $e.to_json() ),* )) }
}

fn almost_equals(a: &Value, b: &Value) -> bool {
    let var_name = match (a, b) {
        (Value::Number(a), Value::Number(b)) => {
            let a = a.as_f64().unwrap();
            let b = b.as_f64().unwrap();
            (a - b).abs() <= a.abs() * 1e-6
        }

        (&Value::Bool(a), &Value::Bool(b)) => a == b,
        (Value::String(a), Value::String(b)) => a == b,
        (Value::Array(a), Value::Array(b)) => {
            a.len() == b.len() && a.iter().zip(b.iter()).all(|(a, b)| almost_equals(a, b))
        }
        (&Value::Object(_), &Value::Object(_)) => panic!("Not implemented"),
        (&Value::Null, &Value::Null) => true,
        _ => false,
    };
    var_name
}

fn normalize(json: &mut Value) {
    match *json {
        Value::Array(ref mut list) => {
            for item in list.iter_mut() {
                normalize(item)
            }
        }
        Value::String(ref mut s) => {
            if *s == "extra-input" || *s == "empty" {
                *s = "invalid".to_string()
            }
        }
        _ => {}
    }
}

fn assert_json_eq(results: Value, mut expected: Value, message: &str) {
    normalize(&mut expected);
    if !almost_equals(&results, &expected) {
        println!(
            "{}",
            ::difference::Changeset::new(
                &serde_json::to_string_pretty(&results).unwrap(),
                &serde_json::to_string_pretty(&expected).unwrap(),
                "\n",
            )
        );
        panic!("{}", message)
    }
}

fn run_raw_json_tests<F: Fn(Value, Value)>(json_data: &str, run: F) {
    let items = match serde_json::from_str(json_data) {
        Ok(Value::Array(items)) => items,
        other => panic!("Invalid JSON: {:?}", other),
    };
    assert!(items.len() % 2 == 0);
    let mut input = None;
    for item in items.into_iter() {
        match (&input, item) {
            (&None, json_obj) => input = Some(json_obj),
            (&Some(_), expected) => {
                let input = input.take().unwrap();
                run(input, expected)
            }
        };
    }
}

fn run_json_tests<F: Fn(&mut Parser) -> Value>(json_data: &str, parse: F) {
    run_raw_json_tests(json_data, |input, expected| match input {
        Value::String(input) => {
            let mut parse_input = ParserInput::new(&input);
            let result = parse(&mut Parser::new(&mut parse_input));
            assert_json_eq(result, expected, &input);
        }
        _ => panic!("Unexpected JSON"),
    });
}

#[test]
fn component_value_list() {
    run_json_tests(
        include_str!("css-parsing-tests/component_value_list.json"),
        |input| Value::Array(component_values_to_json(input)),
    );
}

#[test]
fn one_component_value() {
    run_json_tests(
        include_str!("css-parsing-tests/one_component_value.json"),
        |input| {
            let result: Result<Value, ParseError<()>> = input.parse_entirely(|input| {
                Ok(one_component_value_to_json(input.next()?.clone(), input))
            });
            result.unwrap_or(JArray!["error", "invalid"])
        },
    );
}

#[test]
fn declaration_list() {
    run_json_tests(
        include_str!("css-parsing-tests/declaration_list.json"),
        |input| {
            Value::Array(
                RuleBodyParser::new(input, &mut JsonParser)
                    .map(|result| result.unwrap_or(JArray!["error", "invalid"]))
                    .collect(),
            )
        },
    );
}

#[test]
fn one_declaration() {
    run_json_tests(
        include_str!("css-parsing-tests/one_declaration.json"),
        |input| {
            parse_one_declaration(input, &mut JsonParser).unwrap_or(JArray!["error", "invalid"])
        },
    );
}

#[test]
fn rule_list() {
    run_json_tests(include_str!("css-parsing-tests/rule_list.json"), |input| {
        Value::Array(
            RuleBodyParser::new(input, &mut JsonParser)
                .map(|result| result.unwrap_or(JArray!["error", "invalid"]))
                .collect(),
        )
    });
}

#[test]
fn stylesheet() {
    run_json_tests(include_str!("css-parsing-tests/stylesheet.json"), |input| {
        Value::Array(
            StyleSheetParser::new(input, &mut JsonParser)
                .map(|result| result.unwrap_or(JArray!["error", "invalid"]))
                .collect(),
        )
    });
}

#[test]
fn one_rule() {
    run_json_tests(include_str!("css-parsing-tests/one_rule.json"), |input| {
        parse_one_rule(input, &mut JsonParser).unwrap_or(JArray!["error", "invalid"])
    });
}

#[test]
fn stylesheet_from_bytes() {
    pub struct EncodingRs;

    impl EncodingSupport for EncodingRs {
        type Encoding = &'static encoding_rs::Encoding;

        fn utf8() -> Self::Encoding {
            encoding_rs::UTF_8
        }

        fn is_utf16_be_or_le(encoding: &Self::Encoding) -> bool {
            *encoding == encoding_rs::UTF_16LE || *encoding == encoding_rs::UTF_16BE
        }

        fn from_label(ascii_label: &[u8]) -> Option<Self::Encoding> {
            encoding_rs::Encoding::for_label(ascii_label)
        }
    }

    run_raw_json_tests(
        include_str!("css-parsing-tests/stylesheet_bytes.json"),
        |input, expected| {
            let map = match input {
                Value::Object(map) => map,
                _ => panic!("Unexpected JSON"),
            };

            let result = {
                let css = get_string(&map, "css_bytes")
                    .unwrap()
                    .chars()
                    .map(|c| {
                        assert!(c as u32 <= 0xFF);
                        c as u8
                    })
                    .collect::<Vec<u8>>();
                let protocol_encoding_label =
                    get_string(&map, "protocol_encoding").map(|s| s.as_bytes());
                let environment_encoding = get_string(&map, "environment_encoding")
                    .map(|s| s.as_bytes())
                    .and_then(EncodingRs::from_label);

                let encoding = stylesheet_encoding::<EncodingRs>(
                    &css,
                    protocol_encoding_label,
                    environment_encoding,
                );
                let (css_unicode, used_encoding, _) = encoding.decode(&css);
                let mut input = ParserInput::new(&css_unicode);
                let input = &mut Parser::new(&mut input);
                let rules = StyleSheetParser::new(input, &mut JsonParser)
                    .map(|result| result.unwrap_or(JArray!["error", "invalid"]))
                    .collect::<Vec<_>>();
                JArray![rules, used_encoding.name().to_lowercase()]
            };
            assert_json_eq(result, expected, &Value::Object(map).to_string());
        },
    );

    fn get_string<'a>(map: &'a Map<String, Value>, key: &str) -> Option<&'a str> {
        match map.get(key) {
            Some(Value::String(s)) => Some(s),
            Some(&Value::Null) => None,
            None => None,
            _ => panic!("Unexpected JSON"),
        }
    }
}

#[test]
fn expect_no_error_token() {
    let mut input = ParserInput::new("foo 4px ( / { !bar }");
    assert!(Parser::new(&mut input).expect_no_error_token().is_ok());
    let mut input = ParserInput::new(")");
    assert!(Parser::new(&mut input).expect_no_error_token().is_err());
    let mut input = ParserInput::new("}");
    assert!(Parser::new(&mut input).expect_no_error_token().is_err());
    let mut input = ParserInput::new("(a){]");
    assert!(Parser::new(&mut input).expect_no_error_token().is_err());
    let mut input = ParserInput::new("'\n'");
    assert!(Parser::new(&mut input).expect_no_error_token().is_err());
    let mut input = ParserInput::new("url('\n'");
    assert!(Parser::new(&mut input).expect_no_error_token().is_err());
    let mut input = ParserInput::new("url(a b)");
    assert!(Parser::new(&mut input).expect_no_error_token().is_err());
    let mut input = ParserInput::new("url(\u{7F}))");
    assert!(Parser::new(&mut input).expect_no_error_token().is_err());
}

/// https://github.com/servo/rust-cssparser/issues/71
#[test]
fn outer_block_end_consumed() {
    let mut input = ParserInput::new("(calc(true))");
    let mut input = Parser::new(&mut input);
    assert!(input.expect_parenthesis_block().is_ok());
    assert!(input
        .parse_nested_block(|input| input
            .expect_function_matching("calc")
            .map_err(Into::<ParseError<()>>::into))
        .is_ok());
    println!("{:?}", input.position());
    assert!(input.next().is_err());
}

/// https://github.com/servo/rust-cssparser/issues/174
#[test]
fn bad_url_slice_out_of_bounds() {
    let mut input = ParserInput::new("url(\u{1}\\");
    let mut parser = Parser::new(&mut input);
    let result = parser.next_including_whitespace_and_comments(); // This used to panic
    assert_eq!(result, Ok(&Token::BadUrl("\u{1}\\".into())));
}

/// https://bugzilla.mozilla.org/show_bug.cgi?id=1383975
#[test]
fn bad_url_slice_not_at_char_boundary() {
    let mut input = ParserInput::new("url(9\n۰");
    let mut parser = Parser::new(&mut input);
    let result = parser.next_including_whitespace_and_comments(); // This used to panic
    assert_eq!(result, Ok(&Token::BadUrl("9\n۰".into())));
}

#[test]
fn unquoted_url_escaping() {
    let token = Token::UnquotedUrl(
        "\
         \x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\
         \x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f \
         !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]\
         ^_`abcdefghijklmnopqrstuvwxyz{|}~\x7fé\
         "
        .into(),
    );
    let serialized = token.to_css_string();
    assert_eq!(
        serialized,
        "\
         url(\
         \\1 \\2 \\3 \\4 \\5 \\6 \\7 \\8 \\9 \\a \\b \\c \\d \\e \\f \\10 \
         \\11 \\12 \\13 \\14 \\15 \\16 \\17 \\18 \\19 \\1a \\1b \\1c \\1d \\1e \\1f \\20 \
         !\\\"#$%&\\'\\(\\)*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]\
         ^_`abcdefghijklmnopqrstuvwxyz{|}~\\7f é\
         )\
         "
    );
    let mut input = ParserInput::new(&serialized);
    assert_eq!(Parser::new(&mut input).next(), Ok(&token));
}

#[test]
fn test_expect_url() {
    fn parse<'a>(s: &mut ParserInput<'a>) -> Result<CowRcStr<'a>, BasicParseError<'a>> {
        Parser::new(s).expect_url()
    }
    let mut input = ParserInput::new("url()");
    assert_eq!(parse(&mut input).unwrap(), "");
    let mut input = ParserInput::new("url( ");
    assert_eq!(parse(&mut input).unwrap(), "");
    let mut input = ParserInput::new("url( abc");
    assert_eq!(parse(&mut input).unwrap(), "abc");
    let mut input = ParserInput::new("url( abc \t)");
    assert_eq!(parse(&mut input).unwrap(), "abc");
    let mut input = ParserInput::new("url( 'abc' \t)");
    assert_eq!(parse(&mut input).unwrap(), "abc");
    let mut input = ParserInput::new("url(abc more stuff)");
    assert!(parse(&mut input).is_err());
    // The grammar at https://drafts.csswg.org/css-values/#urls plans for `<url-modifier>*`
    // at the position of "more stuff", but no such modifier is defined yet.
    let mut input = ParserInput::new("url('abc' more stuff)");
    assert!(parse(&mut input).is_err());
}

#[test]
fn nth() {
    run_json_tests(include_str!("css-parsing-tests/An+B.json"), |input| {
        input
            .parse_entirely(|i| {
                let result: Result<_, ParseError<()>> = parse_nth(i).map_err(Into::into);
                result
            })
            .ok()
            .map(|(v0, v1)| json!([v0, v1]))
            .unwrap_or(Value::Null)
    });
}

#[test]
fn parse_comma_separated_ignoring_errors() {
    let input = "red, green something, yellow, whatever, blue";
    let mut input = ParserInput::new(input);
    let mut input = Parser::new(&mut input);
    let result = input.parse_comma_separated_ignoring_errors(|input| {
        let loc = input.current_source_location();
        let ident = input.expect_ident()?;
        crate::color::parse_named_color(ident).map_err(|()| {
            loc.new_unexpected_token_error::<ParseError<()>>(Token::Ident(ident.clone()))
        })
    });
    assert_eq!(result.len(), 3);
    assert_eq!(result[0], (255, 0, 0));
    assert_eq!(result[1], (255, 255, 0));
    assert_eq!(result[2], (0, 0, 255));
}

#[test]
fn unicode_range() {
    run_json_tests(include_str!("css-parsing-tests/urange.json"), |input| {
        let result: Result<_, ParseError<()>> = input.parse_comma_separated(|input| {
            let result = UnicodeRange::parse(input).ok().map(|r| (r.start, r.end));
            if input.is_exhausted() {
                Ok(result)
            } else {
                while input.next().is_ok() {}
                Ok(None)
            }
        });
        result
            .unwrap()
            .iter()
            .map(|v| {
                if let Some((v0, v1)) = v {
                    json!([v0, v1])
                } else {
                    Value::Null
                }
            })
            .collect::<Vec<_>>()
            .to_json()
    });
}

#[test]
fn serializer_not_preserving_comments() {
    serializer(false)
}

#[test]
fn serializer_preserving_comments() {
    serializer(true)
}

fn serializer(preserve_comments: bool) {
    run_json_tests(
        include_str!("css-parsing-tests/component_value_list.json"),
        |input| {
            fn write_to(
                mut previous_token: TokenSerializationType,
                input: &mut Parser,
                string: &mut String,
                preserve_comments: bool,
            ) {
                while let Ok(token) = if preserve_comments {
                    input.next_including_whitespace_and_comments().cloned()
                } else {
                    input.next_including_whitespace().cloned()
                } {
                    let token_type = token.serialization_type();
                    if !preserve_comments && previous_token.needs_separator_when_before(token_type)
                    {
                        string.push_str("/**/")
                    }
                    previous_token = token_type;
                    token.to_css(string).unwrap();
                    let closing_token = match token {
                        Token::Function(_) | Token::ParenthesisBlock => {
                            Some(Token::CloseParenthesis)
                        }
                        Token::SquareBracketBlock => Some(Token::CloseSquareBracket),
                        Token::CurlyBracketBlock => Some(Token::CloseCurlyBracket),
                        _ => None,
                    };
                    if let Some(closing_token) = closing_token {
                        let result: Result<_, ParseError<()>> = input.parse_nested_block(|input| {
                            write_to(previous_token, input, string, preserve_comments);
                            Ok(())
                        });
                        result.unwrap();
                        closing_token.to_css(string).unwrap();
                    }
                }
            }
            let mut serialized = String::new();
            write_to(
                TokenSerializationType::Nothing,
                input,
                &mut serialized,
                preserve_comments,
            );
            let mut input = ParserInput::new(&serialized);
            let parser = &mut Parser::new(&mut input);
            Value::Array(component_values_to_json(parser))
        },
    );
}

#[test]
fn serialize_bad_tokens() {
    let mut input = ParserInput::new("url(foo\\) b\\)ar)'ba\\'\"z\n4");
    let mut parser = Parser::new(&mut input);

    let token = parser.next().unwrap().clone();
    assert!(matches!(token, Token::BadUrl(_)));
    assert_eq!(token.to_css_string(), "url(foo\\) b\\)ar)");

    let token = parser.next().unwrap().clone();
    assert!(matches!(token, Token::BadString(_)));
    assert_eq!(token.to_css_string(), "\"ba'\\\"z");

    let token = parser.next().unwrap().clone();
    assert!(matches!(token, Token::Number { .. }));
    assert_eq!(token.to_css_string(), "4");

    assert!(parser.next().is_err());
}

#[test]
fn line_numbers() {
    let mut input = ParserInput::new(concat!(
        "fo\\30\r\n",
        "0o bar/*\n",
        "*/baz\r\n",
        "\n",
        "url(\r\n",
        "  u \r\n",
        ")\"a\\\r\n",
        "b\""
    ));
    let mut input = Parser::new(&mut input);
    assert_eq!(
        input.current_source_location(),
        SourceLocation { line: 0, column: 1 }
    );
    assert_eq!(
        input.next_including_whitespace(),
        Ok(&Token::Ident("fo00o".into()))
    );
    assert_eq!(
        input.current_source_location(),
        SourceLocation { line: 1, column: 3 }
    );
    assert_eq!(
        input.next_including_whitespace(),
        Ok(&Token::WhiteSpace(" "))
    );
    assert_eq!(
        input.current_source_location(),
        SourceLocation { line: 1, column: 4 }
    );
    assert_eq!(
        input.next_including_whitespace(),
        Ok(&Token::Ident("bar".into()))
    );
    assert_eq!(
        input.current_source_location(),
        SourceLocation { line: 1, column: 7 }
    );
    assert_eq!(
        input.next_including_whitespace_and_comments(),
        Ok(&Token::Comment("\n"))
    );
    assert_eq!(
        input.current_source_location(),
        SourceLocation { line: 2, column: 3 }
    );
    assert_eq!(
        input.next_including_whitespace(),
        Ok(&Token::Ident("baz".into()))
    );
    assert_eq!(
        input.current_source_location(),
        SourceLocation { line: 2, column: 6 }
    );
    let state = input.state();

    assert_eq!(
        input.next_including_whitespace(),
        Ok(&Token::WhiteSpace("\r\n\n"))
    );
    assert_eq!(
        input.current_source_location(),
        SourceLocation { line: 4, column: 1 }
    );

    assert_eq!(
        state.source_location(),
        SourceLocation { line: 2, column: 6 }
    );

    assert_eq!(
        input.next_including_whitespace(),
        Ok(&Token::UnquotedUrl("u".into()))
    );
    assert_eq!(
        input.current_source_location(),
        SourceLocation { line: 6, column: 2 }
    );

    assert_eq!(
        input.next_including_whitespace(),
        Ok(&Token::QuotedString("ab".into()))
    );
    assert_eq!(
        input.current_source_location(),
        SourceLocation { line: 7, column: 3 }
    );
    assert!(input.next_including_whitespace().is_err());
}

#[test]
fn overflow() {
    let css = r"
         2147483646
         2147483647
         2147483648
         10000000000000
         1000000000000000000000000000000000000000
         1{309 zeros}

         -2147483647
         -2147483648
         -2147483649
         -10000000000000
         -1000000000000000000000000000000000000000
         -1{309 zeros}

         3.30282347e+38
         3.40282347e+38
         3.402824e+38

         -3.30282347e+38
         -3.40282347e+38
         -3.402824e+38

    "
    .replace("{309 zeros}", &"0".repeat(309));
    let mut input = ParserInput::new(&css);
    let mut input = Parser::new(&mut input);

    assert_eq!(input.expect_integer(), Ok(2147483646));
    assert_eq!(input.expect_integer(), Ok(2147483647));
    assert_eq!(input.expect_integer(), Ok(2147483647)); // Clamp on overflow
    assert_eq!(input.expect_integer(), Ok(2147483647));
    assert_eq!(input.expect_integer(), Ok(2147483647));
    assert_eq!(input.expect_integer(), Ok(2147483647));

    assert_eq!(input.expect_integer(), Ok(-2147483647));
    assert_eq!(input.expect_integer(), Ok(-2147483648));
    assert_eq!(input.expect_integer(), Ok(-2147483648)); // Clamp on overflow
    assert_eq!(input.expect_integer(), Ok(-2147483648));
    assert_eq!(input.expect_integer(), Ok(-2147483648));
    assert_eq!(input.expect_integer(), Ok(-2147483648));

    assert_eq!(input.expect_number(), Ok(3.302_823_5e38));
    assert_eq!(input.expect_number(), Ok(f32::MAX));
    assert_eq!(input.expect_number(), Ok(f32::INFINITY));

    assert_eq!(input.expect_number(), Ok(-3.302_823_5e38));
    assert_eq!(input.expect_number(), Ok(f32::MIN));
    assert_eq!(input.expect_number(), Ok(f32::NEG_INFINITY));
}

#[test]
fn line_delimited() {
    let mut input = ParserInput::new(" { foo ; bar } baz;,");
    let mut input = Parser::new(&mut input);
    assert_eq!(input.next(), Ok(&Token::CurlyBracketBlock));
    assert!({
        let result: Result<_, ParseError<()>> =
            input.parse_until_after(Delimiter::Semicolon, |_| Ok(42));
        result
    }
    .is_err());
    assert_eq!(input.next(), Ok(&Token::Comma));
    assert!(input.next().is_err());
}

#[test]
fn identifier_serialization() {
    // Null bytes
    assert_eq!(Token::Ident("\0".into()).to_css_string(), "\u{FFFD}");
    assert_eq!(Token::Ident("a\0".into()).to_css_string(), "a\u{FFFD}");
    assert_eq!(Token::Ident("\0b".into()).to_css_string(), "\u{FFFD}b");
    assert_eq!(Token::Ident("a\0b".into()).to_css_string(), "a\u{FFFD}b");

    // Replacement character
    assert_eq!(Token::Ident("\u{FFFD}".into()).to_css_string(), "\u{FFFD}");
    assert_eq!(
        Token::Ident("a\u{FFFD}".into()).to_css_string(),
        "a\u{FFFD}"
    );
    assert_eq!(
        Token::Ident("\u{FFFD}b".into()).to_css_string(),
        "\u{FFFD}b"
    );
    assert_eq!(
        Token::Ident("a\u{FFFD}b".into()).to_css_string(),
        "a\u{FFFD}b"
    );

    // Number prefix
    assert_eq!(Token::Ident("0a".into()).to_css_string(), "\\30 a");
    assert_eq!(Token::Ident("1a".into()).to_css_string(), "\\31 a");
    assert_eq!(Token::Ident("2a".into()).to_css_string(), "\\32 a");
    assert_eq!(Token::Ident("3a".into()).to_css_string(), "\\33 a");
    assert_eq!(Token::Ident("4a".into()).to_css_string(), "\\34 a");
    assert_eq!(Token::Ident("5a".into()).to_css_string(), "\\35 a");
    assert_eq!(Token::Ident("6a".into()).to_css_string(), "\\36 a");
    assert_eq!(Token::Ident("7a".into()).to_css_string(), "\\37 a");
    assert_eq!(Token::Ident("8a".into()).to_css_string(), "\\38 a");
    assert_eq!(Token::Ident("9a".into()).to_css_string(), "\\39 a");

    // Letter number prefix
    assert_eq!(Token::Ident("a0b".into()).to_css_string(), "a0b");
    assert_eq!(Token::Ident("a1b".into()).to_css_string(), "a1b");
    assert_eq!(Token::Ident("a2b".into()).to_css_string(), "a2b");
    assert_eq!(Token::Ident("a3b".into()).to_css_string(), "a3b");
    assert_eq!(Token::Ident("a4b".into()).to_css_string(), "a4b");
    assert_eq!(Token::Ident("a5b".into()).to_css_string(), "a5b");
    assert_eq!(Token::Ident("a6b".into()).to_css_string(), "a6b");
    assert_eq!(Token::Ident("a7b".into()).to_css_string(), "a7b");
    assert_eq!(Token::Ident("a8b".into()).to_css_string(), "a8b");
    assert_eq!(Token::Ident("a9b".into()).to_css_string(), "a9b");

    // Dash number prefix
    assert_eq!(Token::Ident("-0a".into()).to_css_string(), "-\\30 a");
    assert_eq!(Token::Ident("-1a".into()).to_css_string(), "-\\31 a");
    assert_eq!(Token::Ident("-2a".into()).to_css_string(), "-\\32 a");
    assert_eq!(Token::Ident("-3a".into()).to_css_string(), "-\\33 a");
    assert_eq!(Token::Ident("-4a".into()).to_css_string(), "-\\34 a");
    assert_eq!(Token::Ident("-5a".into()).to_css_string(), "-\\35 a");
    assert_eq!(Token::Ident("-6a".into()).to_css_string(), "-\\36 a");
    assert_eq!(Token::Ident("-7a".into()).to_css_string(), "-\\37 a");
    assert_eq!(Token::Ident("-8a".into()).to_css_string(), "-\\38 a");
    assert_eq!(Token::Ident("-9a".into()).to_css_string(), "-\\39 a");

    // Double dash prefix
    assert_eq!(Token::Ident("--a".into()).to_css_string(), "--a");

    // Various tests
    assert_eq!(
        Token::Ident("\x01\x02\x1E\x1F".into()).to_css_string(),
        "\\1 \\2 \\1e \\1f "
    );
    assert_eq!(
        Token::Ident("\u{0080}\x2D\x5F\u{00A9}".into()).to_css_string(),
        "\u{0080}\x2D\x5F\u{00A9}"
    );
    assert_eq!(Token::Ident("\x7F\u{0080}\u{0081}\u{0082}\u{0083}\u{0084}\u{0085}\u{0086}\u{0087}\u{0088}\u{0089}\
        \u{008A}\u{008B}\u{008C}\u{008D}\u{008E}\u{008F}\u{0090}\u{0091}\u{0092}\u{0093}\u{0094}\u{0095}\u{0096}\
        \u{0097}\u{0098}\u{0099}\u{009A}\u{009B}\u{009C}\u{009D}\u{009E}\u{009F}".into()).to_css_string(),
        "\\7f \u{0080}\u{0081}\u{0082}\u{0083}\u{0084}\u{0085}\u{0086}\u{0087}\u{0088}\u{0089}\u{008A}\u{008B}\u{008C}\
        \u{008D}\u{008E}\u{008F}\u{0090}\u{0091}\u{0092}\u{0093}\u{0094}\u{0095}\u{0096}\u{0097}\u{0098}\u{0099}\
        \u{009A}\u{009B}\u{009C}\u{009D}\u{009E}\u{009F}");
    assert_eq!(
        Token::Ident("\u{00A0}\u{00A1}\u{00A2}".into()).to_css_string(),
        "\u{00A0}\u{00A1}\u{00A2}"
    );
    assert_eq!(
        Token::Ident("a0123456789b".into()).to_css_string(),
        "a0123456789b"
    );
    assert_eq!(
        Token::Ident("abcdefghijklmnopqrstuvwxyz".into()).to_css_string(),
        "abcdefghijklmnopqrstuvwxyz"
    );
    assert_eq!(
        Token::Ident("ABCDEFGHIJKLMNOPQRSTUVWXYZ".into()).to_css_string(),
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    );
    assert_eq!(
        Token::Ident("\x20\x21\x78\x79".into()).to_css_string(),
        "\\ \\!xy"
    );

    // astral symbol (U+1D306 TETRAGRAM FOR CENTRE)
    assert_eq!(
        Token::Ident("\u{1D306}".into()).to_css_string(),
        "\u{1D306}"
    );
}

trait ToJson {
    fn to_json(&self) -> Value;
}

impl<T> ToJson for T
where
    T: Clone,
    Value: From<T>,
{
    fn to_json(&self) -> Value {
        Value::from(self.clone())
    }
}

impl<'a> ToJson for CowRcStr<'a> {
    fn to_json(&self) -> Value {
        let s: &str = self;
        s.to_json()
    }
}

#[bench]
#[cfg(feature = "bench")]
fn delimiter_from_byte(b: &mut Bencher) {
    use crate::Delimiters;
    b.iter(|| {
        for _ in 0..1000 {
            for i in 0..256 {
                std::hint::black_box(Delimiters::from_byte(Some(i as u8)));
            }
        }
    })
}

#[cfg(feature = "bench")]
const BACKGROUND_IMAGE: &'static str = include_str!("big-data-url.css");

#[cfg(feature = "bench")]
#[bench]
fn unquoted_url(b: &mut Bencher) {
    b.iter(|| {
        let mut input = ParserInput::new(BACKGROUND_IMAGE);
        let mut input = Parser::new(&mut input);
        input.look_for_var_or_env_functions();

        let result = input.try_parse(|input| input.expect_url());

        assert!(result.is_ok());

        input.seen_var_or_env_functions();
        (result.is_ok(), input.seen_var_or_env_functions())
    })
}

#[cfg_attr(all(miri, feature = "skip_long_tests"), ignore)]
#[cfg(feature = "bench")]
#[bench]
fn numeric(b: &mut Bencher) {
    b.iter(|| {
        for _ in 0..1000000 {
            let mut input = ParserInput::new("10px");
            let mut input = Parser::new(&mut input);
            let _ = test::black_box(input.next());
        }
    })
}

struct JsonParser;

#[cfg_attr(all(miri, feature = "skip_long_tests"), ignore)]
#[test]
fn no_stack_overflow_multiple_nested_blocks() {
    let mut input: String = "{{".into();
    for _ in 0..20 {
        let dup = input.clone();
        input.push_str(&dup);
    }
    let mut input = ParserInput::new(&input);
    let mut input = Parser::new(&mut input);
    while input.next().is_ok() {}
}

impl<'i> DeclarationParser<'i> for JsonParser {
    type Declaration = Value;
    type Error = ();

    fn parse_value<'t>(
        &mut self,
        name: CowRcStr<'i>,
        input: &mut Parser<'i, 't>,
    ) -> Result<Value, ParseError<'i, ()>> {
        let mut value = vec![];
        let mut important = false;
        loop {
            let start = input.state();
            if let Ok(mut token) = input.next_including_whitespace().cloned() {
                // Hack to deal with css-parsing-tests assuming that
                // `!important` in the middle of a declaration value is OK.
                // This can never happen per spec
                // (even CSS Variables forbid top-level `!`)
                if token == Token::Delim('!') {
                    input.reset(&start);
                    if parse_important(input).is_ok() && input.is_exhausted() {
                        important = true;
                        break;
                    }
                    input.reset(&start);
                    token = input.next_including_whitespace().unwrap().clone();
                }
                value.push(one_component_value_to_json(token, input));
            } else {
                break;
            }
        }
        Ok(JArray!["declaration", name, value, important,])
    }
}

impl<'i> AtRuleParser<'i> for JsonParser {
    type Prelude = Vec<Value>;
    type AtRule = Value;
    type Error = ();

    fn parse_prelude<'t>(
        &mut self,
        name: CowRcStr<'i>,
        input: &mut Parser<'i, 't>,
    ) -> Result<Vec<Value>, ParseError<'i, ()>> {
        let prelude = vec![
            "at-rule".to_json(),
            name.to_json(),
            Value::Array(component_values_to_json(input)),
        ];
        match_ignore_ascii_case! { &*name,
            "charset" => {
                Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name.clone())))
            },
            _ => Ok(prelude),
        }
    }

    fn rule_without_block(
        &mut self,
        mut prelude: Vec<Value>,
        _: &ParserState,
    ) -> Result<Value, ()> {
        prelude.push(Value::Null);
        Ok(Value::Array(prelude))
    }

    fn parse_block<'t>(
        &mut self,
        mut prelude: Vec<Value>,
        _: &ParserState,
        input: &mut Parser<'i, 't>,
    ) -> Result<Value, ParseError<'i, ()>> {
        prelude.push(Value::Array(component_values_to_json(input)));
        Ok(Value::Array(prelude))
    }
}

impl<'i> QualifiedRuleParser<'i> for JsonParser {
    type Prelude = Vec<Value>;
    type QualifiedRule = Value;
    type Error = ();

    fn parse_prelude<'t>(
        &mut self,
        input: &mut Parser<'i, 't>,
    ) -> Result<Vec<Value>, ParseError<'i, ()>> {
        Ok(component_values_to_json(input))
    }

    fn parse_block<'t>(
        &mut self,
        prelude: Vec<Value>,
        _: &ParserState,
        input: &mut Parser<'i, 't>,
    ) -> Result<Value, ParseError<'i, ()>> {
        Ok(JArray![
            "qualified rule",
            prelude,
            component_values_to_json(input),
        ])
    }
}

impl<'i> RuleBodyItemParser<'i, Value, ()> for JsonParser {
    fn parse_qualified(&self) -> bool {
        true
    }
    fn parse_declarations(&self) -> bool {
        true
    }
}

fn component_values_to_json(input: &mut Parser) -> Vec<Value> {
    let mut values = vec![];
    while let Ok(token) = input.next_including_whitespace().cloned() {
        values.push(one_component_value_to_json(token, input));
    }
    values
}

fn one_component_value_to_json(token: Token, input: &mut Parser) -> Value {
    fn numeric(value: f32, int_value: Option<i32>, has_sign: bool) -> Vec<Value> {
        vec![
            Token::Number {
                value,
                int_value,
                has_sign,
            }
            .to_css_string()
            .to_json(),
            match int_value {
                Some(i) => i.to_json(),
                None => value.to_json(),
            },
            match int_value {
                Some(_) => "integer",
                None => "number",
            }
            .to_json(),
        ]
    }

    fn nested(input: &mut Parser) -> Vec<Value> {
        let result: Result<_, ParseError<()>> =
            input.parse_nested_block(|input| Ok(component_values_to_json(input)));
        result.unwrap()
    }

    match token {
        Token::Ident(value) => JArray!["ident", value],
        Token::AtKeyword(value) => JArray!["at-keyword", value],
        Token::Hash(value) => JArray!["hash", value, "unrestricted"],
        Token::IDHash(value) => JArray!["hash", value, "id"],
        Token::QuotedString(value) => JArray!["string", value],
        Token::UnquotedUrl(value) => JArray!["url", value],
        Token::Delim('\\') => "\\".to_json(),
        Token::Delim(value) => value.to_string().to_json(),

        Token::Number {
            value,
            int_value,
            has_sign,
        } => Value::Array({
            let mut v = vec!["number".to_json()];
            v.extend(numeric(value, int_value, has_sign));
            v
        }),
        Token::Percentage {
            unit_value,
            int_value,
            has_sign,
        } => Value::Array({
            let mut v = vec!["percentage".to_json()];
            v.extend(numeric(unit_value * 100., int_value, has_sign));
            v
        }),
        Token::Dimension {
            value,
            int_value,
            has_sign,
            unit,
        } => Value::Array({
            let mut v = vec!["dimension".to_json()];
            v.extend(numeric(value, int_value, has_sign));
            v.push(unit.to_json());
            v
        }),

        Token::WhiteSpace(_) => " ".to_json(),
        Token::Comment(_) => "/**/".to_json(),
        Token::Colon => ":".to_json(),
        Token::Semicolon => ";".to_json(),
        Token::Comma => ",".to_json(),
        Token::IncludeMatch => "~=".to_json(),
        Token::DashMatch => "|=".to_json(),
        Token::PrefixMatch => "^=".to_json(),
        Token::SuffixMatch => "$=".to_json(),
        Token::SubstringMatch => "*=".to_json(),
        Token::CDO => "<!--".to_json(),
        Token::CDC => "-->".to_json(),

        Token::Function(name) => Value::Array({
            let mut v = vec!["function".to_json(), name.to_json()];
            v.extend(nested(input));
            v
        }),
        Token::ParenthesisBlock => Value::Array({
            let mut v = vec!["()".to_json()];
            v.extend(nested(input));
            v
        }),
        Token::SquareBracketBlock => Value::Array({
            let mut v = vec!["[]".to_json()];
            v.extend(nested(input));
            v
        }),
        Token::CurlyBracketBlock => Value::Array({
            let mut v = vec!["{}".to_json()];
            v.extend(nested(input));
            v
        }),
        Token::BadUrl(_) => JArray!["error", "bad-url"],
        Token::BadString(_) => JArray!["error", "bad-string"],
        Token::CloseParenthesis => JArray!["error", ")"],
        Token::CloseSquareBracket => JArray!["error", "]"],
        Token::CloseCurlyBracket => JArray!["error", "}"],
    }
}

/// A previous version of procedural-masquerade had a bug where it
/// would normalize consecutive whitespace to a single space,
/// including in string literals.
#[test]
fn procedural_masquerade_whitespace() {
    ascii_case_insensitive_phf_map! {
        map -> () = {
            "  \t\n" => ()
        }
    }
    assert_eq!(map::get("  \t\n"), Some(&()));
    assert_eq!(map::get(" "), None);

    match_ignore_ascii_case! { "  \t\n",
        " " => panic!("1"),
        "  \t\n" => {},
        _ => panic!("2"),
    }

    match_ignore_ascii_case! { " ",
        "  \t\n" => panic!("3"),
        " " => {},
        _ => panic!("4"),
    }
}

#[test]
fn parse_until_before_stops_at_delimiter_or_end_of_input() {
    // For all j and k, inputs[i].1[j] should parse the same as inputs[i].1[k]
    // when we use delimiters inputs[i].0.
    let inputs = vec![
        (
            Delimiter::Bang | Delimiter::Semicolon,
            // Note that the ';extra' is fine, because the ';' acts the same as
            // the end of input.
            vec!["token stream;extra", "token stream!", "token stream"],
        ),
        (Delimiter::Bang | Delimiter::Semicolon, vec![";", "!", ""]),
    ];
    for equivalent in inputs {
        for (j, x) in equivalent.1.iter().enumerate() {
            for y in equivalent.1[j + 1..].iter() {
                let mut ix = ParserInput::new(x);
                let mut ix = Parser::new(&mut ix);

                let mut iy = ParserInput::new(y);
                let mut iy = Parser::new(&mut iy);

                let _ = ix.parse_until_before::<_, _, ()>(equivalent.0, |ix| {
                    iy.parse_until_before::<_, _, ()>(equivalent.0, |iy| {
                        loop {
                            let ox = ix.next();
                            let oy = iy.next();
                            assert_eq!(ox, oy);
                            if ox.is_err() {
                                break;
                            }
                        }
                        Ok(())
                    })
                });
            }
        }
    }
}

#[test]
fn parser_maintains_current_line() {
    let mut input = ParserInput::new("ident ident;\nident ident ident;\nident");
    let mut parser = Parser::new(&mut input);
    assert_eq!(parser.current_line(), "ident ident;");
    assert_eq!(parser.next(), Ok(&Token::Ident("ident".into())));
    assert_eq!(parser.next(), Ok(&Token::Ident("ident".into())));
    assert_eq!(parser.next(), Ok(&Token::Semicolon));

    assert_eq!(parser.next(), Ok(&Token::Ident("ident".into())));
    assert_eq!(parser.current_line(), "ident ident ident;");
    assert_eq!(parser.next(), Ok(&Token::Ident("ident".into())));
    assert_eq!(parser.next(), Ok(&Token::Ident("ident".into())));
    assert_eq!(parser.next(), Ok(&Token::Semicolon));

    assert_eq!(parser.next(), Ok(&Token::Ident("ident".into())));
    assert_eq!(parser.current_line(), "ident");
}

#[test]
fn cdc_regression_test() {
    let mut input = ParserInput::new("-->x");
    let mut parser = Parser::new(&mut input);
    parser.skip_cdc_and_cdo();
    assert_eq!(parser.next(), Ok(&Token::Ident("x".into())));
    assert_eq!(
        parser.next(),
        Err(BasicParseError {
            kind: BasicParseErrorKind::EndOfInput,
            location: SourceLocation { line: 0, column: 5 }
        })
    );
}

#[test]
fn parse_entirely_reports_first_error() {
    #[derive(PartialEq, Debug)]
    enum E {
        Foo,
    }
    let mut input = ParserInput::new("ident");
    let mut parser = Parser::new(&mut input);
    let result: Result<(), _> = parser.parse_entirely(|p| Err(p.new_custom_error(E::Foo)));
    assert_eq!(
        result,
        Err(ParseError {
            kind: ParseErrorKind::Custom(E::Foo),
            location: SourceLocation { line: 0, column: 1 },
        })
    );
}

#[test]
fn parse_sourcemapping_comments() {
    let tests = vec![
        ("/*# sourceMappingURL=here*/", Some("here")),
        ("/*# sourceMappingURL=here  */", Some("here")),
        ("/*@ sourceMappingURL=here*/", Some("here")),
        (
            "/*@ sourceMappingURL=there*/ /*# sourceMappingURL=here*/",
            Some("here"),
        ),
        ("/*# sourceMappingURL=here there  */", Some("here")),
        ("/*# sourceMappingURL=  here  */", Some("")),
        ("/*# sourceMappingURL=*/", Some("")),
        ("/*# sourceMappingUR=here  */", None),
        ("/*! sourceMappingURL=here  */", None),
        ("/*# sourceMappingURL = here  */", None),
        ("/*   # sourceMappingURL=here   */", None),
    ];

    for test in tests {
        let mut input = ParserInput::new(test.0);
        let mut parser = Parser::new(&mut input);
        while parser.next_including_whitespace().is_ok() {}
        assert_eq!(parser.current_source_map_url(), test.1);
    }
}

#[test]
fn parse_sourceurl_comments() {
    let tests = vec![
        ("/*# sourceURL=here*/", Some("here")),
        ("/*# sourceURL=here  */", Some("here")),
        ("/*@ sourceURL=here*/", Some("here")),
        ("/*@ sourceURL=there*/ /*# sourceURL=here*/", Some("here")),
        ("/*# sourceURL=here there  */", Some("here")),
        ("/*# sourceURL=  here  */", Some("")),
        ("/*# sourceURL=*/", Some("")),
        ("/*# sourceMappingUR=here  */", None),
        ("/*! sourceURL=here  */", None),
        ("/*# sourceURL = here  */", None),
        ("/*   # sourceURL=here   */", None),
    ];

    for test in tests {
        let mut input = ParserInput::new(test.0);
        let mut parser = Parser::new(&mut input);
        while parser.next_including_whitespace().is_ok() {}
        assert_eq!(parser.current_source_url(), test.1);
    }
}

#[cfg_attr(all(miri, feature = "skip_long_tests"), ignore)]
#[test]
fn roundtrip_percentage_token() {
    fn test_roundtrip(value: &str) {
        let mut input = ParserInput::new(value);
        let mut parser = Parser::new(&mut input);
        let token = parser.next().unwrap();
        assert_eq!(token.to_css_string(), value);
    }
    // Test simple number serialization
    for i in 0..101 {
        test_roundtrip(&format!("{}%", i));
        for j in 0..10 {
            if j != 0 {
                test_roundtrip(&format!("{}.{}%", i, j));
            }
            for k in 1..10 {
                test_roundtrip(&format!("{}.{}{}%", i, j, k));
            }
        }
    }
}

#[test]
fn utf16_columns() {
    // This particular test serves two purposes.  First, it checks
    // that the column number computations are correct.  Second, it
    // checks that tokenizer code paths correctly differentiate
    // between the different UTF-8 encoding bytes.  In particular
    // different leader bytes and continuation bytes are treated
    // differently, so we make sure to include all lengths in the
    // tests, using the string "QΡ✈��".  Also, remember that because
    // the column is in units of UTF-16, the 4-byte sequence results
    // in two columns.
    let tests = vec![
        ("", 1),
        ("ascii", 6),
        ("/*QΡ✈��*/", 10),
        ("'QΡ✈��*'", 9),
        ("\"\\\"'QΡ✈��*'", 12),
        ("\\Q\\Ρ\\✈\\��", 10),
        ("QΡ✈��", 6),
        ("QΡ✈��\\Q\\Ρ\\✈\\��", 15),
        ("newline\r\nQΡ✈��", 6),
        ("url(QΡ✈��\\Q\\Ρ\\✈\\��)", 20),
        ("url(QΡ✈��)", 11),
        ("url(\r\nQΡ✈��\\Q\\Ρ\\✈\\��)", 16),
        ("url(\r\nQΡ✈��\\Q\\Ρ\\✈\\��", 15),
        ("url(\r\nQΡ✈��\\Q\\Ρ\\✈\\�� x", 17),
        ("QΡ✈��()", 8),
        // Test that under/over-flow of current_line_start_position is
        // handled properly; see the special case in consume_4byte_intro.
        ("��", 3),
    ];

    for test in tests {
        let mut input = ParserInput::new(test.0);
        let mut parser = Parser::new(&mut input);

        // Read all tokens.
        loop {
            match parser.next() {
                Err(BasicParseError {
                    kind: BasicParseErrorKind::EndOfInput,
                    ..
                }) => {
                    break;
                }
                Err(_) => {
                    // should this be an explicit panic instead?
                    unreachable!();
                }
                Ok(_) => {}
            };
        }

        // Check the resulting column.
        assert_eq!(parser.current_source_location().column, test.1);
    }
}

#[test]
fn servo_define_css_keyword_enum() {
    macro_rules! define_css_keyword_enum {
        (pub enum $name:ident { $($variant:ident = $css:pat,)+ }) => {
            #[derive(PartialEq, Debug)]
            pub enum $name {
                $($variant),+
            }

            impl $name {
                pub fn from_ident(ident: &str) -> Result<$name, ()> {
                    match_ignore_ascii_case! { ident,
                        $($css => Ok($name::$variant),)+
                        _ => Err(())
                    }
                }
            }
        }
    }
    define_css_keyword_enum! {
        pub enum UserZoom {
            Zoom = "zoom",
            Fixed = "fixed",
        }
    }

    assert_eq!(UserZoom::from_ident("fixed"), Ok(UserZoom::Fixed));
}

[ Dauer der Verarbeitung: 0.12 Sekunden  (vorverarbeitet)  ]