Merge branch '5.1' into 5.2

* 5.1:
  Display debug info
  [HttpClient] don't fallback to HTTP/1.1 when HTTP/2 streams break
  fix lexing nested sequences/mappings
This commit is contained in:
Christian Flothmann 2020-11-21 10:39:55 +01:00
commit bdadbf31a0
6 changed files with 320 additions and 84 deletions

View File

@ -99,17 +99,12 @@ jobs:
- name: Install system dependencies - name: Install system dependencies
run: | run: |
echo "::group::add apt sources"
sudo wget -O - http://packages.couchbase.com/ubuntu/couchbase.key | sudo apt-key add -
echo "deb http://packages.couchbase.com/ubuntu bionic bionic/main" | sudo tee /etc/apt/sources.list.d/couchbase.list
echo "::endgroup::"
echo "::group::apt-get update" echo "::group::apt-get update"
sudo apt-get update sudo apt-get update
echo "::endgroup::" echo "::endgroup::"
echo "::group::install tools & libraries" echo "::group::install tools & libraries"
sudo apt-get install libcouchbase-dev librdkafka-dev sudo apt-get install librdkafka-dev
echo "::endgroup::" echo "::endgroup::"
- name: Configure Couchbase - name: Configure Couchbase
@ -128,6 +123,11 @@ jobs:
php-version: "${{ matrix.php }}" php-version: "${{ matrix.php }}"
tools: pecl tools: pecl
- name: Display versions
run: |
php -r 'foreach (get_loaded_extensions() as $extension) echo $extension . " " . phpversion($extension) . PHP_EOL;'
php -i
- name: Load fixtures - name: Load fixtures
uses: docker://bitnami/openldap uses: docker://bitnami/openldap
with: with:

View File

@ -42,7 +42,7 @@ class CouchbaseBucketAdapter extends AbstractAdapter
public function __construct(\CouchbaseBucket $bucket, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) public function __construct(\CouchbaseBucket $bucket, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
{ {
if (!static::isSupported()) { if (!static::isSupported()) {
throw new CacheException('Couchbase >= 2.6.0 is required.'); throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.');
} }
$this->maxIdLength = static::MAX_KEY_LENGTH; $this->maxIdLength = static::MAX_KEY_LENGTH;
@ -66,7 +66,7 @@ class CouchbaseBucketAdapter extends AbstractAdapter
} }
if (!static::isSupported()) { if (!static::isSupported()) {
throw new CacheException('Couchbase >= 2.6.0 is required.'); throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.');
} }
set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); }); set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); });
@ -125,7 +125,7 @@ class CouchbaseBucketAdapter extends AbstractAdapter
public static function isSupported(): bool public static function isSupported(): bool
{ {
return \extension_loaded('couchbase') && version_compare(phpversion('couchbase'), '2.6.0', '>='); return \extension_loaded('couchbase') && version_compare(phpversion('couchbase'), '2.6.0', '>=') && version_compare(phpversion('couchbase'), '3.0', '<');
} }
private static function getOptions(string $options): array private static function getOptions(string $options): array

View File

@ -16,7 +16,8 @@ use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Symfony\Component\Cache\Adapter\CouchbaseBucketAdapter; use Symfony\Component\Cache\Adapter\CouchbaseBucketAdapter;
/** /**
* @requires extension couchbase 2.6.0 * @requires extension couchbase <3.0.0
* @requires extension couchbase >=2.6.0
* @group integration * @group integration
* *
* @author Antonio Jose Cerezo Aranda <aj.cerezo@gmail.com> * @author Antonio Jose Cerezo Aranda <aj.cerezo@gmail.com>
@ -32,6 +33,10 @@ class CouchbaseBucketAdapterTest extends AdapterTestCase
public static function setupBeforeClass(): void public static function setupBeforeClass(): void
{ {
if (!CouchbaseBucketAdapter::isSupported()) {
self::markTestSkipped('Couchbase >= 2.6.0 < 3.0.0 is required.');
}
self::$client = AbstractAdapter::createConnection('couchbase://'.getenv('COUCHBASE_HOST').'/cache', self::$client = AbstractAdapter::createConnection('couchbase://'.getenv('COUCHBASE_HOST').'/cache',
['username' => getenv('COUCHBASE_USER'), 'password' => getenv('COUCHBASE_PASS')] ['username' => getenv('COUCHBASE_USER'), 'password' => getenv('COUCHBASE_PASS')]
); );

View File

@ -306,10 +306,7 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
curl_multi_remove_handle($multi->handle, $ch); curl_multi_remove_handle($multi->handle, $ch);
$waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter $waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter
curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor); curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor);
curl_setopt($ch, \CURLOPT_FORBID_REUSE, true);
if ('1' === $waitFor[1]) {
curl_setopt($ch, \CURLOPT_HTTP_VERSION, \CURL_HTTP_VERSION_1_1);
}
if (0 === curl_multi_add_handle($multi->handle, $ch)) { if (0 === curl_multi_add_handle($multi->handle, $ch)) {
continue; continue;

View File

@ -358,7 +358,7 @@ class Parser
} }
try { try {
return Inline::parse($this->parseQuotedString($this->currentLine), $flags, $this->refs); return Inline::parse($this->lexInlineQuotedString(), $flags, $this->refs);
} catch (ParseException $e) { } catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine); $e->setSnippet($this->currentLine);
@ -371,7 +371,7 @@ class Parser
} }
try { try {
$parsedMapping = Inline::parse($this->lexInlineMapping($this->currentLine), $flags, $this->refs); $parsedMapping = Inline::parse($this->lexInlineMapping(), $flags, $this->refs);
while ($this->moveToNextLine()) { while ($this->moveToNextLine()) {
if (!$this->isCurrentLineEmpty()) { if (!$this->isCurrentLineEmpty()) {
@ -392,7 +392,7 @@ class Parser
} }
try { try {
$parsedSequence = Inline::parse($this->lexInlineSequence($this->currentLine), $flags, $this->refs); $parsedSequence = Inline::parse($this->lexInlineSequence(), $flags, $this->refs);
while ($this->moveToNextLine()) { while ($this->moveToNextLine()) {
if (!$this->isCurrentLineEmpty()) { if (!$this->isCurrentLineEmpty()) {
@ -667,6 +667,11 @@ class Parser
return implode("\n", $data); return implode("\n", $data);
} }
private function hasMoreLines(): bool
{
return (\count($this->lines) - 1) > $this->currentLineNb;
}
/** /**
* Moves the parser to the next line. * Moves the parser to the next line.
*/ */
@ -744,9 +749,13 @@ class Parser
try { try {
if ('' !== $value && '{' === $value[0]) { if ('' !== $value && '{' === $value[0]) {
return Inline::parse($this->lexInlineMapping($value), $flags, $this->refs); $cursor = \strlen($this->currentLine) - \strlen($value);
return Inline::parse($this->lexInlineMapping($cursor), $flags, $this->refs);
} elseif ('' !== $value && '[' === $value[0]) { } elseif ('' !== $value && '[' === $value[0]) {
return Inline::parse($this->lexInlineSequence($value), $flags, $this->refs); $cursor = \strlen($this->currentLine) - \strlen($value);
return Inline::parse($this->lexInlineSequence($cursor), $flags, $this->refs);
} }
$quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null; $quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null;
@ -1145,107 +1154,148 @@ class Parser
throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename); throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
} }
private function parseQuotedString(string $yaml): ?string private function lexInlineQuotedString(int &$cursor = 0): string
{ {
if ('' === $yaml || ('"' !== $yaml[0] && "'" !== $yaml[0])) { $quotation = $this->currentLine[$cursor];
throw new \InvalidArgumentException(sprintf('"%s" is not a quoted string.', $yaml)); $value = $quotation;
} ++$cursor;
$lines = [$yaml]; $previousLineWasNewline = true;
$previousLineWasTerminatedWithBackslash = false;
while ($this->moveToNextLine()) { do {
$lines[] = $this->currentLine; if ($this->isCurrentLineBlank()) {
if (!$this->isCurrentLineEmpty() && $yaml[0] === $this->currentLine[-1]) {
break;
}
}
$value = '';
for ($i = 0, $linesCount = \count($lines), $previousLineWasNewline = false, $previousLineWasTerminatedWithBackslash = false; $i < $linesCount; ++$i) {
$trimmedLine = trim($lines[$i]);
if ('' === $trimmedLine) {
$value .= "\n"; $value .= "\n";
} elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
$value .= ' '; $value .= ' ';
} }
if ('' !== $trimmedLine && '\\' === $lines[$i][-1]) { for (; \strlen($this->currentLine) > $cursor; ++$cursor) {
$value .= ltrim(substr($lines[$i], 0, -1)); switch ($this->currentLine[$cursor]) {
} elseif ('' !== $trimmedLine) { case '\\':
$value .= $trimmedLine; if (isset($this->currentLine[++$cursor])) {
$value .= '\\'.$this->currentLine[$cursor];
}
break;
case $quotation:
++$cursor;
if ("'" === $quotation && isset($this->currentLine[$cursor]) && "'" === $this->currentLine[$cursor]) {
$value .= "''";
break;
}
return $value.$quotation;
default:
$value .= $this->currentLine[$cursor];
}
} }
if ('' === $trimmedLine) { if ($this->isCurrentLineBlank()) {
$previousLineWasNewline = true; $previousLineWasNewline = true;
$previousLineWasTerminatedWithBackslash = false; $previousLineWasTerminatedWithBackslash = false;
} elseif ('\\' === $lines[$i][-1]) { } elseif ('\\' === $this->currentLine[-1]) {
$previousLineWasNewline = false; $previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = true; $previousLineWasTerminatedWithBackslash = true;
} else { } else {
$previousLineWasNewline = false; $previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = false; $previousLineWasTerminatedWithBackslash = false;
} }
}
return $value; if ($this->hasMoreLines()) {
$cursor = 0;
}
} while ($this->moveToNextLine());
throw new ParseException('Malformed inline YAML string');
} }
private function lexInlineMapping(string $yaml): string private function lexUnquotedString(int &$cursor): string
{ {
if ('' === $yaml || '{' !== $yaml[0]) { $offset = $cursor;
throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml)); $cursor += strcspn($this->currentLine, '[]{},: ', $cursor);
}
for ($i = 1; isset($yaml[$i]) && '}' !== $yaml[$i]; ++$i) { return substr($this->currentLine, $offset, $cursor - $offset);
}
if (isset($yaml[$i]) && '}' === $yaml[$i]) {
return $yaml;
}
$lines = [$yaml];
while ($this->moveToNextLine()) {
$lines[] = $this->currentLine;
}
return implode("\n", $lines);
} }
private function lexInlineSequence(string $yaml): string private function lexInlineMapping(int &$cursor = 0): string
{ {
if ('' === $yaml || '[' !== $yaml[0]) { return $this->lexInlineStructure($cursor, '}');
throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml)); }
}
for ($i = 1; isset($yaml[$i]) && ']' !== $yaml[$i]; ++$i) { private function lexInlineSequence(int &$cursor = 0): string
} {
return $this->lexInlineStructure($cursor, ']');
}
if (isset($yaml[$i]) && ']' === $yaml[$i]) { private function lexInlineStructure(int &$cursor, string $closingTag): string
return $yaml; {
} $value = $this->currentLine[$cursor];
++$cursor;
$value = $yaml; do {
$this->consumeWhitespaces($cursor);
while ($this->moveToNextLine()) { while (isset($this->currentLine[$cursor])) {
for ($i = 1; isset($this->currentLine[$i]) && ']' !== $this->currentLine[$i]; ++$i) { switch ($this->currentLine[$cursor]) {
case '"':
case "'":
$value .= $this->lexInlineQuotedString($cursor);
break;
case ':':
case ',':
$value .= $this->currentLine[$cursor];
++$cursor;
break;
case '{':
$value .= $this->lexInlineMapping($cursor);
break;
case '[':
$value .= $this->lexInlineSequence($cursor);
break;
case $closingTag:
$value .= $this->currentLine[$cursor];
++$cursor;
return $value;
case '#':
break 2;
default:
$value .= $this->lexUnquotedString($cursor);
}
if ($this->consumeWhitespaces($cursor)) {
$value .= ' ';
}
} }
$trimmedValue = trim($this->currentLine); if ($this->hasMoreLines()) {
$cursor = 0;
}
} while ($this->moveToNextLine());
if ('' !== $trimmedValue && '#' === $trimmedValue[0]) { throw new ParseException('Malformed inline YAML string');
continue; }
private function consumeWhitespaces(int &$cursor): bool
{
$whitespacesConsumed = 0;
do {
$whitespaceOnlyTokenLength = strspn($this->currentLine, ' ', $cursor);
$whitespacesConsumed += $whitespaceOnlyTokenLength;
$cursor += $whitespaceOnlyTokenLength;
if (isset($this->currentLine[$cursor])) {
return 0 < $whitespacesConsumed;
} }
$value .= $trimmedValue; if ($this->hasMoreLines()) {
$cursor = 0;
if (isset($this->currentLine[$i]) && ']' === $this->currentLine[$i]) {
break;
} }
} } while ($this->moveToNextLine());
return $value; return 0 < $whitespacesConsumed;
} }
} }

View File

@ -1659,6 +1659,16 @@ EOF;
'foo': 'bar', 'foo': 'bar',
'bar': 'baz' 'bar': 'baz'
} }
YAML
,
],
'mapping with unquoted strings and values' => [
['foo' => 'bar', 'bar' => 'baz'],
<<<YAML
{
foo: bar,
bar: baz
}
YAML YAML
, ,
], ],
@ -1672,6 +1682,53 @@ YAML
YAML YAML
, ,
], ],
'sequence with unquoted items' => [
['foo', 'bar'],
<<<YAML
[
foo,
bar
]
YAML
,
],
'nested mapping terminating at end of line' => [
[
'foo' => [
'bar' => 'foobar',
],
],
<<<YAML
{ foo: { bar: foobar }
}
YAML
,
],
'nested sequence terminating at end of line' => [
[
'foo',
[
'bar',
'baz',
],
],
<<<YAML
[ foo, [bar, baz]
]
YAML
],
'nested sequence spanning multiple lines' => [
[
['entry1', []],
['entry2'],
],
<<<YAML
[
['entry1', {}],
['entry2']
]
YAML
],
'sequence nested in mapping' => [ 'sequence nested in mapping' => [
['foo' => ['bar', 'foobar'], 'bar' => ['baz']], ['foo' => ['bar', 'foobar'], 'bar' => ['baz']],
<<<YAML <<<YAML
@ -1698,6 +1755,22 @@ foobar: [foo,
YAML YAML
, ,
], ],
'sequence spanning multiple lines nested in mapping with a following mapping' => [
[
'foobar' => [
'foo',
'bar',
],
'bar' => 'baz',
],
<<<YAML
foobar: [
foo,
bar,
]
bar: baz
YAML
],
'nested sequence nested in mapping starting on the same line' => [ 'nested sequence nested in mapping starting on the same line' => [
[ [
'foo' => [ 'foo' => [
@ -1823,6 +1896,110 @@ YAML
foo: 'bar foo: 'bar
baz' baz'
YAML
],
'mixed mapping with inline notation having separated lines' => [
[
'map' => [
'key' => 'value',
'a' => 'b',
],
'param' => 'some',
],
<<<YAML
map: {
key: "value",
a: "b"
}
param: "some"
YAML
],
'mixed mapping with inline notation on one line' => [
[
'map' => [
'key' => 'value',
'a' => 'b',
],
'param' => 'some',
],
<<<YAML
map: {key: "value", a: "b"}
param: "some"
YAML
],
'mixed mapping with compact inline notation on one line' => [
[
'map' => [
'key' => 'value',
'a' => 'b',
],
'param' => 'some',
],
<<<YAML
map: {key: "value",
a: "b"}
param: "some"
YAML
],
'nested collections containing strings with bracket chars' => [
[
[']'],
['}'],
['ba[r'],
['[ba]r'],
['bar]'],
['foo' => 'bar{'],
['foo' => 'b{ar}'],
['foo' => 'bar}'],
],
<<<YAML
[
[
"]"
],
[
"}"
],
[
"ba[r"
],
[
'[ba]r'
],
[
"bar]"
],
{
foo: "bar{"
},
{
foo: "b{ar}"
},
{
foo: 'bar}'
}
]
YAML
],
'escaped characters in quoted strings' => [
[
['te"st'],
['test'],
["te'st"],
['te"st]'],
['te"st'],
['test'],
["te'st"],
['te"st]'],
],
<<<YAML
[
["te\"st"],["test"],['te''st'],["te\"st]"],
["te\"st"],
["test"],
['te''st'],
["te\"st]"]
]
YAML YAML
], ],
]; ];
@ -1846,6 +2023,13 @@ YAML;
$this->assertEquals(new TaggedValue('foo', ['foo' => 'bar']), $this->parser->parse('!foo {foo: bar}', Yaml::PARSE_CUSTOM_TAGS)); $this->assertEquals(new TaggedValue('foo', ['foo' => 'bar']), $this->parser->parse('!foo {foo: bar}', Yaml::PARSE_CUSTOM_TAGS));
} }
public function testInvalidInlineSequenceContainingStringWithEscapedQuotationCharacter()
{
$this->expectException(ParseException::class);
$this->parser->parse('["\\"]');
}
/** /**
* @dataProvider taggedValuesProvider * @dataProvider taggedValuesProvider
*/ */