Skip to the content. Disable Animations :x: :movie_camera:

Empirical++: Power Tools Go Brrr...

jackhammer

Thanks to the software contributions of our WAVES participants and mentors, we’ve got an exciting slew of shiny new tools to show off. And you’ve got an exciting slew of shiny new things to learn about!

🔗 Prefab

Contributed by Sara and Matthew.

The idea of the prefab project is to provide prepackaged utilities for web apps (like a config panel) built out of Empirical’s web widgets with snazzy, mobile-friendly styling already applied.

You can find a showcase of prefab tools with code snippets about how to use them here.

:school_satchel: Pick a prefab element and add it to your evo-algo web app! Maybe add a message explaining what’s going on inside of a bootstrap card? You could even try to put live stats about evolution like a counter of how many generations have elapsed in a different card!

:bangbang: For everything to look right, you’ll have to paste the boilerplate JS and CSS includes at the top of the demo page into your HTML file.

🔗 Installing Test Dependencies

Given the size and complexity of Empirical’s code base, how do we make sure that everything stays in working order? Tests! To get started working on an Empirical project, you’ll want to be familiar with how to run existing tests and how to write new ones.

First thing you’ve got to do is install a few dependencies Luckily for you, we’ve got Empirical’s dependency install process automated as a make rule in the base directory of the repo that should just work.

make install-test-dependencies

If that doesn’t just work, you can try hopping into our pre-built Docker container. You should have better luck with make install-test-dependencies in there.

sudo docker run -it devosoft/empirical:latest

:bangbang: If you’re installing docker on Ubuntu, be sure to run sudo apt-get install docker.io not sudo apt-get install docker. For other platforms & more detailed instructions, see here.

🔗 Run All the Tests

To run our existing battery of native tests, you’ll just

cd tests
make

To run the tests for a particular source subdirectory, just cd into the corresponding subdirectory in tests and make. For example, to run the tests for base

cd tests/base
make

Those options probably take too long, though. You’re more likely to find running the tests for a particular source file more useful. To do that, cd into its subdirectory and make test-BasenameWithoutExtension. For example, to test base/vector.hpp you’d

cd tests/base
make test-vector

🔗 Anatomy of a Native Test

What’s actually going on inside these tests? Getting you to Catch the drift will REQUIRE popping one open for a closer look.

Hopefully, those puns will be very funny to you in a minute.

We use the Catch framework for our native tests. Let’s take a look at tests/math/info_theory.cpp.

#define CATCH_CONFIG_MAIN // Catch magic

#include "third-party/Catch/single_include/catch2/catch.hpp"

#include "emp/math/info_theory.hpp"

#include <sstream>
#include <string>

// Catch uses macro magic to string these TEST_CASE's together.
// You can provide several TEST_CASE's within the same file.
TEST_CASE("Test info_theory", "[tools]")
{

  emp::vector<int> weights = { 100, 100, 200 };
  // REQUIRE is a special Catch macro that must evaluate to true
  // or the executable exits with an error code
  REQUIRE( emp::Entropy(weights) == 1.5 );

}

:biohazard: Some portions of Empirical’s unit tests are kind of a hot mess and shouldn’t be used as a “"”good””” example of how to write unit tests. Generally, try to have small, descriptive TEST_CASE’s and be sure to include lots of inline comments that describe what you’re testing and why.

🔗 Adding a Native Test

Let’s add a header file inside of include/emp/math/ called dumb_add.hpp.

#pragma once

namespace emp {

size_t dumb_add(const size_t first, const size_t second) {
  if (second) return dumb_add(first+1, second-1);
  else return first;
}

}

Now we need to test it!

:school_satchel: Open up tests/math/dumb_add.cpp as a new file and add some tests.

:bangbang: You’ll need to edit the first line of the Makefile inside of tests/math/ (i.e., TEST_NAMES = ...) to add dumb_add.

🔗 Run the Web Tests

To run our existing battery of web tests, you’d

cd tests/web
make

To run the tests for a particular header, for example Widget, just

cd tests/web
make test-web-Widget

:bangbang: There’s also DOM tests for prefab in addition to web inside tests/web right now.

🔗 Anatomy of a Web Test

Let’s take a peek into tests/web/Widget.cpp.

Includes… gotta have ‘em!

#include <functional>
#include <unordered_map>

#include "emp/base/assert.hpp"
#include "emp/web/_MochaTestRunner.hpp"
#include "emp/web/Document.hpp"
#include "emp/web/Element.hpp"
#include "emp/web/web.hpp"

Today, we’re going to be painting coding a happy little tree struct. There’s nothing wrong with having a tree struct as a friend.

Bob Ross Matthew Andres Moreno

This struct will inherit from BaseTest, which handles much of the boilerplate for us behind the scenes.

struct Test_WidgetWrapWith : emp::web::BaseTest {

Step the first: set up some DOM state in our constructor.

  // Construct the following HTML structure:
  // <div id="wrapper2"> <!-- inserted with WrapWith -->
  //   <p id="parent">
  //     parent
  //     <div id="wrapper"> <!-- inserted with WrapWith -->
  //       wrapper
  //       <button id="child"></button>
  //     </div>
  //   </p>
  //   <br/><br/>
  // </div>
  Test_WidgetWrapWith()
  : BaseTest({"emp_test_container"})
  {

    emp::web::Element parent("p", "parent");
    parent << "parent";
    Doc("emp_test_container") << parent;

    emp::web::Button child(
      []() {
        EM_ASM({
          $("#child_button").attr("clicked", "yes");
        });
      },
      "child"
    );
    child.SetAttr("clicked", "no");
    child.SetAttr("id", "child_button");
    parent << child;

    emp::web::Div wrapper("wrapper");
    wrapper << "wrapper";
    child.WrapWith(wrapper);

    parent.WrapWith(
      emp::web::Div("wrapper2").SetCSS("background-color", "red")
    ).SetCSS("background-color", "blue");

    Doc("emp_test_container").Div("wrapper2") << "<br/><br/>";

  }

Step the second: make sure the DOM state ended up the way we expected it to.

:bangbang: Everything inside the EM_ASM is actually JavaScript, not C++. It’s built using the Mocha test framework and Chai BDD/TDD assertion library, so if you’re curious about the syntax kicking around or you want to learn even more fancy footwork take a peek over there.

  void Describe() override {

    // Test that the HTML components created in constructor are correct.
    EM_ASM({

      describe("Widget::WrapWith", function() {
        describe("#wrapper2", function() {
          it('should have parent #emp_test_container', function() {
            const parent_id = $("#wrapper2").parent().attr("id");
            chai.assert.equal(parent_id, "emp_test_container");
          });
          it('should have child #parent', function() {
            var children = $("#wrapper2").children();
            // Get ids of child
            var child_ids = [];
            for (i = 0; i < children.length; i++) {
              child_ids.push(children[i].getAttribute("id"));
            }
            chai.assert.include(child_ids, "parent");
            chai.assert.equal($("#wrapper2").children("#parent").length, 1);
          });
        });
        describe("#parent", function() {
          it('should have parent #wrapper2', function() {
            const parent_id = $("#parent").parent().attr("id");
            chai.assert.equal(parent_id, "wrapper2");
          });
          it('should have child #wrapper', function() {
            chai.assert.equal($("#parent").children("#wrapper").length, 1);
          });
        });
        describe("#wrapper", function() {
          it('should have parent #parent', function() {
            const parent_id = $("#wrapper").parent().attr("id");
            chai.assert.equal(parent_id, "parent");
          });
          it('should have child #child_button', function() {
            chai.assert.equal($("#wrapper").children("#child_button").length, 1);
          });
        });
        describe("#child_button", function() {
          it('should have parent #wrapper', function() {
            const parent_id = $("#child_button").parent().attr("id");
            chai.assert.equal(parent_id, "wrapper");
          });
        });
        describe("button#child_button", function() {
          it('should do stuff when clicked', function() {
            const before = $("#child_button").attr("clicked");
            chai.assert.equal(before, "no", "check initial clicked value");
            $("#child_button").trigger( "click" );
            const after = $("#child_button").attr("clicked");
            chai.assert.equal(after, "yes", "check post-click clicked value");
          });
        });
      });
    });
  }

Tie up our happy little struct.

};

All that’s left is to

  1. instantiate a emp::web::MochaTestRunner (ahoy, more boilerplate-hiding magics),
  2. give it a vector of div’s that need to be in our HTML document before our happy little struct runs,
  3. register our happy little struct with the test runner, and
  4. hit the go button.
emp::web::MochaTestRunner test_runner;
int main() {

  // put a div with id emp_test_container into our HTML document
  test_runner.Initialize({"emp_test_container"});

  test_runner.AddTest<Test_WidgetWrapWith>("Test Widget::WrapWith");
  test_runner.Run();
}

To run this test, you’ll use the make command make test-web-Widget inside tests/web.

🔗 Layin’ Down the Law

Github Actions yells at us when tests don’t pass.

CodeCov yells at us when we don’t write tests. You can use their web interface to check which code lines of your pull request haven’t been tested yet.