Правильное использование ReadOnlySequence <T>при реализации ReadOnlySequenceSegment <T> - PullRequest
0 голосов
/ 21 мая 2019

Я пытаюсь реализовать сетевую библиотеку, чтобы завершить свою работу с нуля Astron , так как у меня есть несколько дней, и у меня есть несколько вопросов.Чтобы правильно обрабатывать Nagle, у меня должна быть логика разбора кадра.Поэтому, делая это с использованием нового конвейерного API, я заметил, что вывод был предоставлен в виде последовательности только для чтения, поэтому мне пришлось создать собственный SequenceReader, но у меня возникли некоторые проблемы при его тестировании.Я думаю, что я неправильно понял новый Span<T> и Memory<T> API: /

  1. Уместно ли имитировать сегмент последовательности?

Чтобы проверить какДля всех возможных вариантов поведения мне пришлось реализовать абстрактный класс ReadOnlySequenceSegment<T>, который я использовал с помощью библиотеки Moq, чтобы узнать, когда поступает следующий сегмент, вот он:

    public class SequenceSegment : ReadOnlySequenceSegment<byte>
    {
        private readonly Mock<SequenceSegment> _mock;
        private ReadOnlySequenceSegment<byte> _next;

        public new ReadOnlySequenceSegment<byte> Next
        {
            get
            {
                var mockNext = _mock.Object.Next; // simulate get from the mock
                return _next;
            }
            protected set => _next = value;
        }

        public SequenceSegment(ReadOnlyMemory<byte> memory)
        {
            Memory = memory;
            _mock = new Mock<SequenceSegment>(memory);
        }

        public SequenceSegment Add(ReadOnlyMemory<byte> mem)
        {
            var segment = new SequenceSegment(mem)
            {
                RunningIndex = RunningIndex + Memory.Length
            };
            Next = segment;
            return segment;
        }

        public void VerifyGetNext() => _mock.VerifyGet(ss => ss.Next);
    }

Как выможно увидеть, что мне пришлось переопределить свойство Next с помощью ключевого слова new, что является плохой практикой, но я предполагаю, что во время тестирования это нормально?

Вот тест, который не проходит:

        [Fact]
        public void TryRead_ShouldReturnTrue_OnSegmentSplitted()
        {
            var buffer = _createBuffer(); // int + int = 8bytes buffer
            var firstSegment = new SequenceSegment(buffer.Slice(0, 3));
            var secondSegment = firstSegment.Add(buffer.Slice(3, 5));

            var input = new ReadOnlySequence<byte>(
                firstSegment, 0,
                secondSegment, 4);

            var reader = new SequenceReader(input);
            reader.TryRead(BinaryPrimitives.ReadInt32LittleEndian, out var firstValue); // throw here
            reader.TryRead(BinaryPrimitives.ReadInt32LittleEndian, out var secondValue);

            Assert.Equal(_firstValue, firstValue);
            Assert.Equal(_secondValue, secondValue);
            firstSegment.VerifyGetNext();
        }

Тестовый вывод:

Message: System.ArgumentOutOfRangeException : Specified argument was out of the range of valid values.
Parameter name: start

Я прокомментировал строку, которая выдает исключение в тесте, поэтому я предполагаю, что моя логика последовательности в порядке?Давайте посмотрим код моего SequenceReader со строкой, которая выдает комментарий:

    public class SequenceReader
    {
        private const int _maxStackalloc = 128;
        protected ReadOnlySequence<byte> _input;

        public SequenceReader(ReadOnlySequence<byte> input) => _input = input;

        public delegate T ReadDelegate<out T>(ReadOnlySpan<byte> src);

        /// <summary>
        /// Try to read a <see cref="T"/> with the <see cref="ReadDelegate{T}"/> specified as an arg.
        /// The <see cref="SequenceReader"/> then advance the current position according to the size of <see cref="T"/>.
        /// <see cref="T"/> must be a struct :
        /// <see cref="byte"/>, <see cref="sbyte"/>, <see cref="bool"/>, <see cref="short"/>, 
        /// <see cref="ushort"/>, <see cref="int"/>, <see cref="uint"/>, <see cref="long"/>, 
        /// <see cref="ulong"/>, <see cref="float"/>, <see cref="double"/>, <see cref="decimal"/>, 
        /// </summary>
        /// <typeparam name="T">The type to read.</typeparam>
        /// <param name="read">The delegate to read the <see cref="T"/>. Must be a method from <see cref="BinaryPrimitives"/></param>
        /// <param name="result">The result returned.</param>
        /// <returns>Returns true if the read was successful, else returns false.</returns>
        public unsafe bool TryRead<T>(ReadDelegate<T> read, out T result) where T : unmanaged
        {
            result = default;
            var size = sizeof(T);
            if (size > _maxStackalloc) return false;
            if (size > _input.Length) return false;

            if (_input.First.Length >= size)
                result = read(_input.First.Span);
            else
            {
                Span<byte> local = stackalloc byte[size];
                _input.Slice(size).CopyTo(local); // throws at the slice
                result = read(local);
            }

            _input = _input.Slice(size);
            return true;
        }
    }

Я уже пытался изменить строку на _input.Slice(0, size), но ничего не изменилось, также не в этом успешном тесте:

        [Fact]
        public void TryRead_ShouldReturnTrue_OnSegmentComplete()
        {
            var input = new ReadOnlySequence<byte>(_createBuffer());

            var reader = new SequenceReader(input);
            reader.TryRead(BinaryPrimitives.ReadInt32LittleEndian, out var firstValue);
            reader.TryRead(BinaryPrimitives.ReadInt32LittleEndian, out var secondValue);

            Assert.Equal(_firstValue, firstValue);
            Assert.Equal(_secondValue, secondValue);
        }

Мне интересно, что я делаю неправильно, пожалуйста, если у вас есть идеи, было бы здорово, если бы вы могли мне помочь!

...