Jump to page: 1 2
Thread overview
Can't recreate a range?
Apr 29, 2020
Casey
Apr 29, 2020
Paul Backus
Apr 30, 2020
Casey
Apr 30, 2020
Paul Backus
Apr 30, 2020
Simen Kjærås
Apr 30, 2020
Casey
Apr 30, 2020
Casey
Apr 30, 2020
Casey
Apr 30, 2020
Paul Backus
Apr 30, 2020
H. S. Teoh
Apr 30, 2020
Casey
Apr 30, 2020
Paul Backus
Apr 29, 2020
Simen Kjærås
Apr 30, 2020
Casey
April 29, 2020
So, I'm trying to run some tests and I had code that looks similar to this:

unittest
{
        auto range = readStream(File("test_data/multiple.xml").byLine);
        int count = 0;
        while (!range.empty)
        {
                count++;
                range.popFront();
        }
        assert(count == 3);
}

However, if I have a second block above/below it doing the same thing, the last assert will fail because in the second block it appears the ranges have been joined together.  E.g.:

unittest
{
        auto range = readStream(File("test_data/multiple.xml").byLine);
        int count = 0;
        while (!range.empty)
        {
                count++;
                range.popFront();
        }
        assert(count == 3); // Passes
}

unittest
{
        auto range = readStream(File("test_data/another.xml").byLine);
        int count = 0;
        while (!range.empty)
        {
                count++;
                range.popFront();
        }
        assert(count == 2); // Fails
}

To me, it looks like range is not  being overwritten.  The code for readStream looks similar to this:

auto readStream(Range)(auto ref Range r) if (isInputRange!(Unqual!Range))
{
        struct StreamRange(Range)
        {
                alias R = Unqual!Range;
                R _input;

                this(R input)
                {
                        this._input = input;
                }

                bool empty()
                {
                        return this._input.empty;
                }

                string front()
                {
                        // Do stuff...
                }

                void popFront()
                {
                }
        }

        return StreamRange!(Range)(r);
}

I feel like I'm missing something obscure and it's driving me a bit batty.  Any clue as to why this is happening?  I'd like to not have to worry about creating new variable names between tests.  To me, it seems like each unittest block is independent of each other and I haven't come across anything that contradicts that.  However, I didn't find anything that confirms it either.

Thanks.
April 29, 2020
On Wednesday, 29 April 2020 at 20:43:20 UTC, Casey wrote:
> So, I'm trying to run some tests and I had code that looks similar to this:
>
[...]
>
> I feel like I'm missing something obscure and it's driving me a bit batty.  Any clue as to why this is happening?  I'd like to not have to worry about creating new variable names between tests.  To me, it seems like each unittest block is independent of each other and I haven't come across anything that contradicts that.  However, I didn't find anything that confirms it either.
>
> Thanks.

The code you posted looks correct to me. If you can post a complete example that reproduces the problem, it will be much easier for others to help you debug.
April 29, 2020
On Wednesday, 29 April 2020 at 20:43:20 UTC, Casey wrote:
>                 void popFront()
>                 {
>                 }

I mean, it might be you messed up in posting this, but having an empty popFront and expecting it to do something is a tad optimistic.

Apart from that, it seems like the code should do what you want it to. What's the value of count when the code asserts? I'm afeared we'll need some code that actually compiles and shows off the issue to give any more answers.

--
  Simen
April 30, 2020
Here's a minimal code example that duplicates the issue:

import std.array, std.range, std.stdio, std.traits, std.string;

auto readStream(Range)(auto ref Range r) if (isInputRange!(Unqual!Range))
{
	struct StreamRange(Range)
	{
		alias R = Unqual!Range;
		R _input;

		auto buff = appender!string;

		this(R input)
		{
			this._input = input;
		}

		bool empty()
		{
			return this._input.empty;
		}

		string front()
		{
			if (buff.capacity == 0)
			{
				bool iterate = true;
				bool doCapture = false;
				buff.reserve(1000);

				while (iterate)
				{
					if (this._input.empty)
						break;

					auto value = this._input.front;
					if (value.strip == "<main>")
					{
						doCapture = true;
						buff.put(value.strip);
						buff.put("\n");
					}
					else if (value.strip == "</main>")
					{
						buff.put(value.strip);
						doCapture = false;
						iterate = false;
					}
					else if (doCapture)
					{
						buff.put(value.strip);
						buff.put("\n");
					}
					this._input.popFront();
				}
			}
			return buff[];
		}

		void popFront()
		{
			buff = appender!string;
		}
	}

	return StreamRange!(Range)(r);
}

unittest
{
	auto range = readStream(File("test1.xml").byLine);
	int count = 0;
	while (!range.empty)
	{
		writeln(range.front);
		count++;
		writeln("Current count: ", count);
		range.popFront();
	}
	assert(count == 1);
}

unittest
{
	auto range = readStream(File("test2.xml").byLine);
	int count = 0;
	while (!range.empty)
	{
		writeln(range.front);
		count++;
		writeln("Current count: ", count);
		range.popFront();
	}
	assert(count == 1);
}


Here are the two XML files (very simple):

<?xml version="1.0">
<main>
	<text>Here is some text for the first file.</text>
</main>

<?xml version="1.0">
<main>
	<text>Here is some text for the second file.</text>
</main>

I've even tried putting wrapping the code that is being tested in a function and looping over it with different parameters (filename and expected count) without any luck either.  I've also tried declaring the file handle separate and ensuring it was closed.  No luck.
April 30, 2020
On Wednesday, 29 April 2020 at 22:32:00 UTC, Simen Kjærås wrote:
> I mean, it might be you messed up in posting this, but having an empty popFront and expecting it to do something is a tad optimistic.

I was just trying to get the example to a very minimal state.  I just added more descriptive code that replicates the bug.

>
> What's the value of count when the code asserts?

The count ends up being the count of the previous test + the count of the current test.  In the code I uploaded, the first test will get 1, but the second test will get 2.  I'm printing out the output of the range to force it to actually perform the loop, so you can see that the contents of the first file are printed twice.
April 30, 2020
On Thursday, 30 April 2020 at 13:04:47 UTC, Casey wrote:
> Here's a minimal code example that duplicates the issue:
>
> import std.array, std.range, std.stdio, std.traits, std.string;
>
> auto readStream(Range)(auto ref Range r) if (isInputRange!(Unqual!Range))
> {
> 	struct StreamRange(Range)
> 	{
> 		alias R = Unqual!Range;
> 		R _input;
>
> 		auto buff = appender!string;

Using a default value like this means that it will be shared among all instances of the struct. Instead, you should set `buff = appender!string` in the constructor, so that each struct will have its own appender.
April 30, 2020
On Thursday, 30 April 2020 at 13:23:25 UTC, Paul Backus wrote:
> On Thursday, 30 April 2020 at 13:04:47 UTC, Casey wrote:
>> Here's a minimal code example that duplicates the issue:
>>
>> import std.array, std.range, std.stdio, std.traits, std.string;
>>
>> auto readStream(Range)(auto ref Range r) if (isInputRange!(Unqual!Range))
>> {
>> 	struct StreamRange(Range)
>> 	{
>> 		alias R = Unqual!Range;
>> 		R _input;
>>
>> 		auto buff = appender!string;
>
> Using a default value like this means that it will be shared among all instances of the struct. Instead, you should set `buff = appender!string` in the constructor, so that each struct will have its own appender.

Yup, that's the one. No need to assign it at all, in fact - the line can be replaced with

    Appender!string buff;

And things just work.

--
  Simen
April 30, 2020
On Thursday, 30 April 2020 at 13:23:25 UTC, Paul Backus wrote:
> Using a default value like this means that it will be shared among all instances of the struct. Instead, you should set `buff = appender!string` in the constructor, so that each struct will have its own appender.

I'll give it a try when I get back to it (fixing lint issues), but are you sure that's the issue?  In popFront, I recreate the appender.  So, the appender should be clear before the empty check after it processes the last of the data from _input.  Still a good thing to do as a best practice; I'm just wondering if something else is causing an issue and even if initializing the appender in the constructor solves the problem, I'd be suspect of future issues cropping up.
April 30, 2020
On Thursday, 30 April 2020 at 15:42:03 UTC, Casey wrote:
> I'll give it a try when I get back to it (fixing lint issues), but are you sure that's the issue?  In popFront, I recreate the appender.  So, the appender should be clear before the empty check after it processes the last of the data from _input.  Still a good thing to do as a best practice; I'm just wondering if something else is causing an issue and even if initializing the appender in the constructor solves the problem, I'd be suspect of future issues cropping up.

O.K.  It's working now, but I suspect there may still be something else going on.  The more I think of it, the more I suspect there's a newline at the end of the file that isn't accounted for until after the last tag is read from the file, so the input is not empty, so my code's empty check fails.

Regardless, thanks for the help!
April 30, 2020
On 4/30/20 11:42 AM, Casey wrote:
> On Thursday, 30 April 2020 at 13:23:25 UTC, Paul Backus wrote:
>> Using a default value like this means that it will be shared among all instances of the struct. Instead, you should set `buff = appender!string` in the constructor, so that each struct will have its own appender.
> 
> I'll give it a try when I get back to it (fixing lint issues), but are you sure that's the issue?  In popFront, I recreate the appender.  So, the appender should be clear before the empty check after it processes the last of the data from _input.  Still a good thing to do as a best practice; I'm just wondering if something else is causing an issue and even if initializing the appender in the constructor solves the problem, I'd be suspect of future issues cropping up.

I would say part of the issue is that you are doing all your work in front and not popFront.

What happens is that Appender is a pointer-to-implementation struct, and the compiler will allocate the first one shared amongst all initial StreamRange instances.

On your first call to front, it's going to utilize that shared one. Then on the call to popFront, it will reset it to another one.

For the second unittest, in your first call to front, it notices that it's already been filled, so it doesn't do any work (and returns the existing buffer).

another problem, your empty condition is based on the input, which violates range expectations. indeed, on the first call to front, the range all of a sudden becomes empty.

I'd say:

1. move your work to the popFront function (you then need to call popFront once before returning the range in your factory method).
2. change empty to check if the buffer is empty instead of the input.

Some other observations:

3. You don't need a further Range parameter for the nested struct, it can use the template parameter from the containing function.
4. Make it a static struct, or else it will retain a pointer to the function stack frame needlessly.

-Steve
« First   ‹ Prev
1 2