BakedOpDispatcherTests.cpp revision 284b765e3c1647859d4dac772744e8859c033216
1419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik/* 2419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik * Copyright (C) 2016 The Android Open Source Project 3419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik * 4419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik * Licensed under the Apache License, Version 2.0 (the "License"); 5419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik * you may not use this file except in compliance with the License. 6419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik * You may obtain a copy of the License at 7419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik * 8419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik * http://www.apache.org/licenses/LICENSE-2.0 9419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik * 10419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik * Unless required by applicable law or agreed to in writing, software 11419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik * distributed under the License is distributed on an "AS IS" BASIS, 12419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik * See the License for the specific language governing permissions and 14419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik * limitations under the License. 15419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik */ 16419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik 17419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik#include <gtest/gtest.h> 18419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik 19419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik#include <RecordedOp.h> 20419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik#include <BakedOpDispatcher.h> 21419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik#include <BakedOpRenderer.h> 22284b765e3c1647859d4dac772744e8859c033216sergeyv#include <FrameBuilder.h> 23284b765e3c1647859d4dac772744e8859c033216sergeyv#include <SkBlurDrawLooper.h> 24284b765e3c1647859d4dac772744e8859c033216sergeyv#include <hwui/Paint.h> 25419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik#include <tests/common/TestUtils.h> 26419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik 27e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik#include <SkDashPathEffect.h> 28e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik 29419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craikusing namespace android::uirenderer; 30419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik 31419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craikstatic BakedOpRenderer::LightInfo sLightInfo; 32284b765e3c1647859d4dac772744e8859c033216sergeyvconst FrameBuilder::LightGeometry sLightGeometry = { {100, 100, 100}, 50}; 33419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craikstatic Rect sBaseClip(100, 100); 34419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik 35419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craikclass ValidatingBakedOpRenderer : public BakedOpRenderer { 36419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craikpublic: 37419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik ValidatingBakedOpRenderer(RenderState& renderState, std::function<void(const Glop& glop)> validator) 38419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik : BakedOpRenderer(Caches::getInstance(), renderState, true, sLightInfo) 39419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik , mValidator(validator) { 40419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik mGlopReceiver = ValidatingGlopReceiver; 41419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik } 42419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craikprivate: 43419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik static void ValidatingGlopReceiver(BakedOpRenderer& renderer, const Rect* dirtyBounds, 44419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik const ClipBase* clip, const Glop& glop) { 45419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik 46419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik auto vbor = reinterpret_cast<ValidatingBakedOpRenderer*>(&renderer); 47419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik vbor->mValidator(glop); 48419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik } 49419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik std::function<void(const Glop& glop)> mValidator; 50419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik}; 51419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik 52284b765e3c1647859d4dac772744e8859c033216sergeyvtypedef void (*TestBakedOpReceiver)(BakedOpRenderer&, const BakedOpState&); 53419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik 54419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craikstatic void testUnmergedGlopDispatch(renderthread::RenderThread& renderThread, RecordedOp* op, 55419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik std::function<void(const Glop& glop)> glopVerifier) { 56419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik // Create op, and wrap with basic state. 57419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik LinearAllocator allocator; 58419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), sBaseClip); 59419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik auto state = BakedOpState::tryConstruct(allocator, *snapshot, *op); 60419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik ASSERT_NE(nullptr, state); 61419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik 62419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik int glopCount = 0; 63419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik auto glopReceiver = [&glopVerifier, &glopCount] (const Glop& glop) { 64419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik ASSERT_EQ(glopCount++, 0) << "Only one Glop expected"; 65419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik glopVerifier(glop); 66419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik }; 67419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik ValidatingBakedOpRenderer renderer(renderThread.renderState(), glopReceiver); 68419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik 69419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik // Dispatch based on op type created, similar to Frame/LayerBuilder dispatch behavior 70419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik#define X(Type) \ 71419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik [](BakedOpRenderer& renderer, const BakedOpState& state) { \ 72419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik BakedOpDispatcher::on##Type(renderer, static_cast<const Type&>(*(state.op)), state); \ 73419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik }, 74284b765e3c1647859d4dac772744e8859c033216sergeyv static TestBakedOpReceiver unmergedReceivers[] = BUILD_RENDERABLE_OP_LUT(X); 75419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik#undef X 76419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik unmergedReceivers[op->opId](renderer, *state); 77419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik ASSERT_EQ(1, glopCount) << "Exactly one Glop expected"; 78419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik} 79419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik 80e98a046d3110eb2ff11a17f4ff27764b803a1379Chris CraikRENDERTHREAD_TEST(BakedOpDispatcher, pathTexture_positionOvalArc) { 81419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik SkPaint strokePaint; 82419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik strokePaint.setStyle(SkPaint::kStroke_Style); 83419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik strokePaint.setStrokeWidth(4); 84e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik 85e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik float intervals[] = {1.0f, 1.0f}; 86e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik auto dashEffect = SkDashPathEffect::Create(intervals, 2, 0); 87e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik strokePaint.setPathEffect(dashEffect); 88e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik dashEffect->unref(); 89e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik 90e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik auto textureGlopVerifier = [] (const Glop& glop) { 91419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik // validate glop produced by renderPathTexture (so texture, unit quad) 92419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik auto texture = glop.fill.texture.texture; 93419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik ASSERT_NE(nullptr, texture); 94419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik float expectedOffset = floor(4 * 1.5f + 0.5f); 95419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik EXPECT_EQ(expectedOffset, reinterpret_cast<PathTexture*>(texture)->offset) 96419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik << "Should see conservative offset from PathCache::computeBounds"; 97419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik Rect expectedBounds(10, 15, 20, 25); 98419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik expectedBounds.outset(expectedOffset); 99e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik 100419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik Matrix4 expectedModelView; 101419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik expectedModelView.loadTranslate(10 - expectedOffset, 15 - expectedOffset, 0); 102419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik expectedModelView.scale(10 + 2 * expectedOffset, 10 + 2 * expectedOffset, 1); 103419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik EXPECT_EQ(expectedModelView, glop.transform.modelView) 104419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik << "X and Y offsets, and scale both applied to model view"; 105e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik }; 106e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik 107e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik // Arc and Oval will render functionally the same glop, differing only in texture content 108e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik ArcOp arcOp(Rect(10, 15, 20, 25), Matrix4::identity(), nullptr, &strokePaint, 0, 270, true); 109e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik testUnmergedGlopDispatch(renderThread, &arcOp, textureGlopVerifier); 110e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik 111e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik OvalOp ovalOp(Rect(10, 15, 20, 25), Matrix4::identity(), nullptr, &strokePaint); 112e98a046d3110eb2ff11a17f4ff27764b803a1379Chris Craik testUnmergedGlopDispatch(renderThread, &ovalOp, textureGlopVerifier); 113419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik} 114419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik 115419a1e7ef53468e494d21c66ea7f63c0c522d208Chris CraikRENDERTHREAD_TEST(BakedOpDispatcher, onLayerOp_bufferless) { 116419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik SkPaint layerPaint; 117419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik layerPaint.setAlpha(128); 118419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik OffscreenBuffer* buffer = nullptr; // no providing a buffer, should hit rect fallback case 119419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik LayerOp op(Rect(10, 10), Matrix4::identity(), nullptr, &layerPaint, &buffer); 120419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik testUnmergedGlopDispatch(renderThread, &op, [&renderThread] (const Glop& glop) { 121419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik // rect glop is dispatched with paint props applied 122419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik EXPECT_EQ(renderThread.renderState().meshState().getUnitQuadVBO(), 123419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik glop.mesh.vertices.bufferObject) << "Unit quad should be drawn"; 124419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik EXPECT_EQ(nullptr, glop.fill.texture.texture) << "Should be no texture when layer is null"; 125419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik EXPECT_FLOAT_EQ(128 / 255.0f, glop.fill.color.a) << "Rect quad should use op alpha"; 126419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik }); 127419a1e7ef53468e494d21c66ea7f63c0c522d208Chris Craik} 12892a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv 12992a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyvstatic int getGlopTransformFlags(renderthread::RenderThread& renderThread, RecordedOp* op) { 13092a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv int result = 0; 13192a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv testUnmergedGlopDispatch(renderThread, op, [&result] (const Glop& glop) { 13292a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv result = glop.transform.transformFlags; 13392a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv }); 13492a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv return result; 13592a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv} 13692a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv 13792a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyvRENDERTHREAD_TEST(BakedOpDispatcher, offsetFlags) { 13892a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv Rect bounds(10, 15, 20, 25); 13992a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv SkPaint paint; 14092a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv SkPaint aaPaint; 14192a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv aaPaint.setAntiAlias(true); 14292a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv 14392a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv RoundRectOp roundRectOp(bounds, Matrix4::identity(), nullptr, &paint, 0, 270); 14492a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &roundRectOp)) 14592a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv << "Expect no offset for round rect op."; 14692a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv 14792a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv const float points[4] = {0.5, 0.5, 1.0, 1.0}; 14892a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv PointsOp antiAliasedPointsOp(bounds, Matrix4::identity(), nullptr, &aaPaint, points, 4); 14992a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &antiAliasedPointsOp)) 15092a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv << "Expect no offset for AA points."; 15192a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv PointsOp pointsOp(bounds, Matrix4::identity(), nullptr, &paint, points, 4); 15292a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv EXPECT_EQ(TransformFlags::OffsetByFudgeFactor, getGlopTransformFlags(renderThread, &pointsOp)) 15392a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv << "Expect an offset for non-AA points."; 15492a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv 15592a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv LinesOp antiAliasedLinesOp(bounds, Matrix4::identity(), nullptr, &aaPaint, points, 4); 15692a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &antiAliasedLinesOp)) 15792a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv << "Expect no offset for AA lines."; 15892a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv LinesOp linesOp(bounds, Matrix4::identity(), nullptr, &paint, points, 4); 15992a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv EXPECT_EQ(TransformFlags::OffsetByFudgeFactor, getGlopTransformFlags(renderThread, &linesOp)) 16092a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv << "Expect an offset for non-AA lines."; 161284b765e3c1647859d4dac772744e8859c033216sergeyv} 162284b765e3c1647859d4dac772744e8859c033216sergeyv 163284b765e3c1647859d4dac772744e8859c033216sergeyvRENDERTHREAD_TEST(BakedOpDispatcher, renderTextWithShadow) { 164284b765e3c1647859d4dac772744e8859c033216sergeyv auto node = TestUtils::createNode(0, 0, 100, 100, 165284b765e3c1647859d4dac772744e8859c033216sergeyv [](RenderProperties& props, TestCanvas& canvas) { 166284b765e3c1647859d4dac772744e8859c033216sergeyv 167284b765e3c1647859d4dac772744e8859c033216sergeyv android::Paint shadowPaint; 168284b765e3c1647859d4dac772744e8859c033216sergeyv shadowPaint.setColor(SK_ColorRED); 169284b765e3c1647859d4dac772744e8859c033216sergeyv 170284b765e3c1647859d4dac772744e8859c033216sergeyv SkScalar sigma = Blur::convertRadiusToSigma(5); 171284b765e3c1647859d4dac772744e8859c033216sergeyv shadowPaint.setLooper(SkBlurDrawLooper::Create(SK_ColorWHITE, sigma, 3, 3))->unref(); 172284b765e3c1647859d4dac772744e8859c033216sergeyv 173284b765e3c1647859d4dac772744e8859c033216sergeyv TestUtils::drawUtf8ToCanvas(&canvas, "A", shadowPaint, 25, 25); 174284b765e3c1647859d4dac772744e8859c033216sergeyv TestUtils::drawUtf8ToCanvas(&canvas, "B", shadowPaint, 50, 50); 175284b765e3c1647859d4dac772744e8859c033216sergeyv }); 176284b765e3c1647859d4dac772744e8859c033216sergeyv 177284b765e3c1647859d4dac772744e8859c033216sergeyv int glopCount = 0; 178284b765e3c1647859d4dac772744e8859c033216sergeyv auto glopReceiver = [&glopCount] (const Glop& glop) { 179284b765e3c1647859d4dac772744e8859c033216sergeyv if (glopCount < 2) { 180284b765e3c1647859d4dac772744e8859c033216sergeyv // two white shadows 181284b765e3c1647859d4dac772744e8859c033216sergeyv EXPECT_EQ(FloatColor({1, 1, 1, 1}), glop.fill.color); 182284b765e3c1647859d4dac772744e8859c033216sergeyv } else { 183284b765e3c1647859d4dac772744e8859c033216sergeyv // two text draws merged into one, drawn after both shadows 184284b765e3c1647859d4dac772744e8859c033216sergeyv EXPECT_EQ(FloatColor({1, 0, 0, 1}), glop.fill.color); 185284b765e3c1647859d4dac772744e8859c033216sergeyv } 186284b765e3c1647859d4dac772744e8859c033216sergeyv glopCount++; 187284b765e3c1647859d4dac772744e8859c033216sergeyv }; 188284b765e3c1647859d4dac772744e8859c033216sergeyv 189284b765e3c1647859d4dac772744e8859c033216sergeyv ValidatingBakedOpRenderer renderer(renderThread.renderState(), glopReceiver); 190284b765e3c1647859d4dac772744e8859c033216sergeyv 191284b765e3c1647859d4dac772744e8859c033216sergeyv FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, 192284b765e3c1647859d4dac772744e8859c033216sergeyv sLightGeometry, Caches::getInstance()); 193284b765e3c1647859d4dac772744e8859c033216sergeyv frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); 194284b765e3c1647859d4dac772744e8859c033216sergeyv 195284b765e3c1647859d4dac772744e8859c033216sergeyv frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); 196284b765e3c1647859d4dac772744e8859c033216sergeyv ASSERT_EQ(3, glopCount) << "Exactly three glops expected"; 19792a5d4b99a5554adca0e94627d44d2bd7f4f0bc0sergeyv}