Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Increase Clip test coverage #592

Open
wants to merge 10 commits into
base: develop
from
Open

Conversation

@ferdnyc
Copy link
Collaborator

@ferdnyc ferdnyc commented Nov 23, 2020

In case anyone was doubting the value of unit tests, I've found three bugs in the Clip code so far.

1: Nullability error

The first, I found while writing tests to cover more failure cases (exceptions thrown, etc.). I added a set of tests, and every time they tried to run, the test program crashed before completion. Turned out, it was impossible to null a clip's Reader using the setter:

openshot::Clip c("video.mp4");
c.Open();
c.Reader(nullptr);

The third line would crash with an unhandled exception, because the code for Reader(openshot::ReaderBase* new_reader) tried to make calls to the reader immediately after setting the member variable to the new pointer value, without first checking whether reader was valid. Fixed.

2: Logic error

The second one, I found just by reading the code on Codecov's website, because it's in the only one of four conditional paths that we don't currently have a test for:

image

Do you see it? The last condition should be

else if (enabled_video == -1 && reader && !reader->info.has_video)
    enabled_video = 0;

As a result, it was impossible to use auto-enabled video on a video file with no audio, or the video would auto-disable itself. Fixed.

3: Confusing order of operations

The third one isn't really a bug per se, but it cleans up a very confusing logical flow in what should be a simple function. The code for Clip::End used to read:

float Clip::End() const
{
        // if a time curve is present, use its length
        if (time.GetCount() > 1)
        {
                // Determine the FPS fo this clip
                float fps = 24.0;
                if (reader)
                        // file reader
                        fps = reader->info.fps.ToFloat();
                else
                        // Throw error if reader not initialized
                        throw ReaderClosed("No Reader has been initialized for this Clip.  Call Reader(*reader) before calling this method.");

                return float(time.GetLength()) / fps;
        }
        else
                // just use the duration (as detected by the reader)
                return end;
}

Setting aside the unnecessary else conditions that can be streamlined with early returns, the innermost if/else also has a throw in the else, which means it'll never get to the code after the else when the if condition is false. Which in turn means the condition doesn't need to be there at all, the throw serves as an early return.

All in all, after spinning everything the right way around, Clip::End is now reduced to:

// Get end position of clip (trim end of video), which can be affected by the time curve.
float Clip::End() const
{
        if (!reader) {
                // Throw error if reader not initialized
                throw ReaderClosed(
                        "No Reader has been initialized for this Clip. "
                        "Call Reader(*reader) before calling this method.");
        }

        if (time.GetCount() <= 1) {
                // if no time curve, just use the duration (as detected by the reader)
                return end;
        }

        float fps = reader->info.fps.ToFloat();
        return float(time.GetLength()) / fps;
}

(This third one actually has nothing to do with unit tests, it's just something I spotted while I was in the code. One nice thing about writing tests is that it makes you read the code, instead of write it, which is a much better way of evaluating it objectively.)

I also used early returns to unspool a bunch of other functions that looked like this:

void Clip::DoSomething() {
    if (reader)
    {
        // Tons and TONS of code
    
        // Like, more than would fit on one screen
    
        // No matter how big your screen is

    }
}

/* That should of course read: */
void Clip::DoSomething() {
    if (!reader)
        return;
    // All the other stuff
}

I'm starting to develop this weird theory that 16:9 aspect screens are causing programmers to be more cavalier about deeply nested functions, because their screens have more width than height at this point. It certainly seem like the art of the early return, and the preference for many separate, succinct functions over functions containing long chains of conditional logic, have been falling by the wayside in recent years.


return float(time.GetLength()) / fps;
if (!reader) {
// Throw error if reader not initialized

This comment has been minimized.

@ferdnyc

ferdnyc Nov 23, 2020
Author Collaborator

Suggested change
// Throw error if reader not initialized

Most of these offensively-redundant comments, I took out if I was touching the code around them, but I left this one in for some reason. Does someone think we can't read the text directly below it!?

// Cache frame
cache.Add(frame);

// Return processed 'frame'
return frame;
Comment on lines +441 to +443

This comment has been minimized.

@ferdnyc

ferdnyc Nov 23, 2020
Author Collaborator

Suggested change
// Cache frame
cache.Add(frame);
// Return processed 'frame'
return frame;
cache.Add(frame);
return frame;

No idea why I left these, either.

@ferdnyc
Copy link
Collaborator Author

@ferdnyc ferdnyc commented Nov 23, 2020

All in all, after spinning everything the right way around, Clip::End is now reduced to:

...The wrong thing, since it now strikes me that the time.GetLength() check needs to come before the reader check. I'll fix that.

...Wait, nope, never mind. I was right the first time, since the comment before the return says "use the duration (as detected by the reader)", so clearly there has to be a reader.

Hah! But the new version of the function does break several of the existing tests, which are clearly assuming that they can get End() for a clip without a reader. I'll have to figure out if that's a reasonable assumption to be making or not, maybe the comment is wrong.

@ferdnyc ferdnyc force-pushed the ferdnyc:clip-coverage branch from c7c9970 to c4387a4 Nov 23, 2020
@codecov
Copy link

@codecov codecov bot commented Nov 23, 2020

Codecov Report

Merging #592 (b2f1260) into develop (6009a26) will increase coverage by 1.18%.
The diff coverage is 52.54%.

Impacted file tree graph

@@             Coverage Diff             @@
##           develop     #592      +/-   ##
===========================================
+ Coverage    52.25%   53.43%   +1.18%     
===========================================
  Files          129      129              
  Lines        10772    10831      +59     
===========================================
+ Hits          5629     5788     +159     
+ Misses        5143     5043     -100     
Impacted Files Coverage Δ
src/Clip.h 83.33% <ø> (+8.33%) ⬆️
src/FFmpegReader.cpp 69.66% <ø> (+0.46%) ⬆️
src/FFmpegReader.h 66.66% <ø> (ø)
src/effects/Mask.cpp 0.00% <ø> (ø)
src/Clip.cpp 55.91% <37.43%> (+12.17%) ⬆️
tests/Clip_Tests.cpp 100.00% <100.00%> (ø)
tests/Coordinate_Tests.cpp 100.00% <100.00%> (ø)
tests/ReaderBase_Tests.cpp 100.00% <100.00%> (ø)
src/ClipBase.h 84.61% <0.00%> (+3.84%) ⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6009a26...b2f1260. Read the comment docs.

@ferdnyc
Copy link
Collaborator Author

@ferdnyc ferdnyc commented Nov 23, 2020

Quit being a pushy jerk, Codecov, I'm working on it! (Sheesh! 😉 )

@github-actions github-actions bot added the conflicts label Dec 27, 2020
@github-actions
Copy link

@github-actions github-actions bot commented Dec 27, 2020

Merge conflicts have been detected on this PR, please resolve.

@ferdnyc ferdnyc removed the conflicts label Dec 31, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

1 participant