Sunday, April 27, 2014

Contributing to the Papyrus Editor Palette

Augmenting the Papyrus Palette

Goal

This entry is the first in a series of articles that details the customizations to the Eclipse Papyrus platform for the UML Testing Tool. These customizations, though specific to the needs of the UML Testing Tool community, can be generalized and used for other products with similar requirements.

The UML Testing Tool is a graphical workbench for the design, visualization, specification, analysis, construction, and documentation of the artifacts involved in testing. The language for modeling these artifacts is based on a UML Testing Profile. This profile defines a set of UML stereotypes that extend a subset of UML elements. There are Similar tools based on other UML profile specifications, such as the NIEM UML Profile.

Papyrus is an environment within Eclipse that enables users to configure graphical editors for UML-based DSLs.

The goal of this first entry is to describe how to register a UML profile with Papyrus and to augment the Papyrus editor palette with stereotyped elements defined in this profile.

tl;dr

The source code for the set of Eclipse plug-ins that contains the UML Testing Profile, its registrations with the Eclipse UML and Papyrus extensions and the test cases described in this post is located here. Feel free to use, explore, contribute to or fork this code for your own needs. A snapshot of the code specific to these examples at the time of writing is here.

Registering the UML Profile

Let's assume that we already have all the boilerplate configuration for an Eclipse plug-in, test fragment, feature and p2-repository in place. For this plug-in, we can use the tycho_new_plugin_project archetype, initially described here.

We will start with an acceptance test to verify that we have correctly registered the UML Testing Profile as a UML dynamic package.

PackageRegistrationTest.java
PackageRegistrationTest.java
package timezra.uml.testing.resources;

import static org.eclipse.uml2.uml.UMLPlugin.getEPackageNsURIToProfileLocationMap;
import static org.eclipse.uml2.uml.resources.util.UMLResourcesUtil.init;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertThat;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.uml2.uml.Profile;
import org.junit.Test;

public class PackageRegistrationTest {
    @Test
    
public void should_register_the_uml_testing_package() {
        
final EObject utp = init(new ResourceSetImpl()).getEObject(
                getEPackageNsURIToProfileLocationMap().get(
"http://www.omg.org/spec/UTP/20120801/utp_1.2"), true);

        assertThat(utp, instanceOf(Profile.
class));
    }
}

In order to satisfy this test, we must include the utp_1.2.xmi and utptypes_1.2.xmi with our plug-in. We will modify the utp_1.2.xmi by adding the xmlns declaration xmlns:cmof="http://www.omg.org/spec/MOF/20110701/cmof.xmi" to the root header. We also need to define the profile through the Eclipse UML Editor.

With the UTP and its supporting type descriptions packaged into a plug-in, we can now register an extension for the UML dynamic package for the profile we just defined through the Eclipse UML Editor.

plugin.xml
plugin.xml
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
    <extension point="org.eclipse.emf.ecore.uri_mapping">
        <mapping source="pathmap://UTP/"
            target="platform:/plugin/timezra.uml.testing.resources/src/main/resources/uml/">
        </mapping>
    </extension>
    <extension point="org.eclipse.emf.ecore.uri_mapping">
        <mapping source="http://www.omg.org/spec/UTP/20120801/utp_1.2.xmi"
            target="pathmap://UTP/utp_1.2.xmi">
        </mapping>
        <mapping source="http://www.omg.org/spec/UTP/20120801/utptypes_1.2.xmi"
            target="pathmap://UTP/utptypes_1.2.xmi">
        </mapping>
    </extension>
    <extension point="org.eclipse.uml2.uml.dynamic_package">
        <profile location="pathmap://UTP/utp_1.2.xmi#_iCl4wOVMEeG84fBOY39c0g"
            uri="http://www.omg.org/spec/UTP/20120801/utp_1.2">
        </profile>
    </extension>
</plugin>

Now, if we install our feature into Eclipse and create a new UML model, the UTP will be available for profile application through the UML Editor. UTP Registered With UML

Profile Registration With Papyrus

In order to register this profile with Papyrus, we can use similar steps: create a test to verify when the behavior we are trying to expose works and expose the profile location through an extension.

ProfileRegistrationTest.java
ProfileRegistrationTest.java
package timezra.uml.testing.papyrus.palettes;

import static org.eclipse.papyrus.uml.extensionpoints.profile.RegisteredProfile.getRegisteredProfile;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;

import org.junit.Test;

public class ProfileRegistrationTest {
    @Test
    
public void should_register_the_uml_testing_profile() {
        assertThat(getRegisteredProfile(
"UTP"), notNullValue());
    }
}

plugin.xml
plugin.xml
....
   <extension
         point="org.eclipse.papyrus.uml.extensionpoints.UMLProfile">
      <profile
            description="UML Testing Profile"
            iconpath="icons/full/obj16/utp.png"
            name="UTP"
            path="http://www.omg.org/spec/UTP/20120801/utp_1.2.xmi#_iCl4wOVMEeG84fBOY39c0g"
            provider="omg.org">
      </profile>
   </extension>
   <extension
         point="org.eclipse.papyrus.uml.extensionpoints.UMLLibrary">
      <library
            description="UML Testing Profile Types"
            iconpath="icons/full/obj16/utptypes.png"
            name="UTP Types"
            path="http://www.omg.org/spec/UTP/20120801/utptypes_1.2.xmi"
            provider="omg.org">
      </library>
   </extension>
....

The UTP is now available in the Papyrus repository, and it can be applied from the Profile tab of the Properties View. UTP Registered With Papyrus

Palette Customization

Once we are able to apply the UTP to a Model, we can specify tests with UML elements extended by this profile. Suppose we begin by creating a TestContext from the Papyrus palette.

ClassDiagramPaletteTest.java
ClassDiagramPaletteTest.java
package timezra.uml.testing.papyrus.palettes;

import static org.eclipse.papyrus.uml.diagram.common.util.URIUtil.getFile;
import static org.eclipse.papyrus.uml.tools.model.UmlUtils.getUmlResource;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;

import org.eclipse.gef.EditDomain;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.Tool;
import org.eclipse.gef.palette.PaletteContainer;
import org.eclipse.gef.palette.PaletteEntry;
import org.eclipse.gef.palette.ToolEntry;
import org.eclipse.gef.ui.palette.PaletteViewer;
import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.parts.IDiagramWorkbenchPart;
import org.eclipse.papyrus.editor.PapyrusMultiDiagramEditor;
import org.eclipse.papyrus.infra.core.editor.IMultiDiagramEditor;
import org.eclipse.papyrus.uml.diagram.clazz.CreateClassDiagramCommand;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.widgets.Event;
import org.eclipse.uml2.uml.Class;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Package;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;

import timezra.uml.testing.papyrus.palettes.rules.AppliesTheUTP;
import timezra.uml.testing.papyrus.palettes.rules.CreatesAPapyrusModel;
import timezra.uml.testing.papyrus.palettes.rules.CreatesAProject;
import timezra.uml.testing.papyrus.palettes.rules.CreatesAServicesRegistry;
import timezra.uml.testing.papyrus.palettes.rules.OpensEditor;
import timezra.uml.testing.papyrus.palettes.rules.SavesAModel;

public class ClassDiagramPaletteTest {

    
private CreatesAServicesRegistry theServicesRegistry;
    
private CreatesAProject theProject;
    
private CreatesAPapyrusModel thePapyrusModel;
    
private OpensEditor<IMultiDiagramEditor> theEditor;

    @Rule
    
public final TestRule ruleChain = RuleChain
            .outerRule(theServicesRegistry = 
new CreatesAServicesRegistry())
            .around(theProject = 
new CreatesAProject(getClass().getSimpleName()))
            .around(thePapyrusModel = 
new CreatesAPapyrusModel(theServicesRegistry, theProject, "model.di",
                    
new CreateClassDiagramCommand()))
            .around(
new AppliesTheUTP(thePapyrusModel))
            .around(
new SavesAModel(thePapyrusModel))
            .around(theEditor = 
new OpensEditor<IMultiDiagramEditor>(
                    () -> getFile(getUmlResource(thePapyrusModel.get()).getURI()), PapyrusMultiDiagramEditor.EDITOR_ID));

    @Test
    
public void can_create_a_test_context() {
        
final IGraphicalEditPart theActivePart = ((IDiagramWorkbenchPart) theEditor.get().getActiveEditor())
                .getDiagramEditPart();
        
final EditPartViewer theEditPartViewer = theActivePart.getViewer();
        doubleClick(findThePaletteTool(theEditPartViewer, 
"UTP/Test Context (Class)"), theEditPartViewer);

        
final Element theTestContext = ((Package) theActivePart.resolveSemanticElement())
                .getPackagedElement(
"TestContext1");

        assertThat(theTestContext, instanceOf(Class.
class));
        assertThat(theTestContext.getAppliedStereotype(
"utp::TestContext"), notNullValue());
    }

    
private Tool findThePaletteTool(final EditPartViewer theEditPartViewer, final String toolPath) {
        
final EditDomain theDomain = theEditPartViewer.getEditDomain();
        
final PaletteViewer thePaletteViewer = theDomain.getPaletteViewer();
        
final ToolEntry toolEntry = findByLabel(thePaletteViewer.getPaletteRoot(), toolPath);
        thePaletteViewer.setActiveTool(toolEntry);

        
final Tool theTool = toolEntry.createTool();
        theTool.setViewer(theEditPartViewer);
        theTool.setEditDomain(theDomain);

        
return theTool;
    }

    @SuppressWarnings(
"unchecked")
    
private <T extends PaletteEntry> T findByLabel(final PaletteContainer thePaletteContainer, final String theLabel) {
        
final String[] path = theLabel.split("/");
        PaletteEntry nextEntry = thePaletteContainer;
        NEXT_SEGMENT: 
for (final String segment : path) {
            
if (nextEntry instanceof PaletteContainer) {
                
for (final Object o : ((PaletteContainer) nextEntry).getChildren()) {
                    
final PaletteEntry paletteEntry = (PaletteEntry) o;
                    
if (segment.equals(paletteEntry.getLabel())) {
                        nextEntry = paletteEntry;
                        
continue NEXT_SEGMENT;
                    }
                }
                
return null;
            } 
else {
                
return null;
            }
        }
        
return (T) nextEntry;
    }

    
private void doubleClick(final Tool theTool, final EditPartViewer theEditPartViewer) {
        
final Event theEvent = new Event();
        theEvent.widget = theEditPartViewer.getControl();
        
final MouseEvent mouseEvent = new MouseEvent(theEvent);
        mouseEvent.button = 1;
        theTool.mouseDoubleClick(mouseEvent, theEditPartViewer);
    }
}

Now we will describe a palette entry for the Class Diagram Editor that is in a UTP drawer and that will apply the utp::TestContext stereotype to a UML class.

UTP.clazz.palette.xml
UTP.clazz.palette.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<paletteDefinition>
    <content>
        <drawer iconpath="/icons/drawer.gif" id="UTP_Drawer" name="UTP">
            <aspectTool description="Create a Test Context (Class)"
                iconpath="platform:/plugin/timezra.uml.testing.papyrus.palettes/icons/full/obj16/test_context_class.png"
                id="clazz.tool.class_UTP_TEST_CONTEXT" name="Test Context (Class)" refToolId="clazz.tool.class">
                <postAction id="org.eclipse.papyrus.applystereotypeactionprovider">
                    <stereotypesToApply>
                        <stereotype stereotypeName="utp::TestContext" />
                    </stereotypesToApply>
                </postAction>
            </aspectTool>
        </drawer>
    </content>
</paletteDefinition>

Finally, we will register this palette configuration with a Papyrus extension.

plugin.xml
Copy of plugin.xml
....
<extension point="org.eclipse.papyrus.uml.diagram.common.paletteDefinition">
    <paletteDefinition ID="timezra.uml.testing.papyrus.palettes.UTP_clazz"
        class="org.eclipse.papyrus.uml.diagram.common.service.PluginPaletteProvider"
        icon="icons/full/obj16/utp_palette.png" name="UTP Class Diagram Elements"
        path="palettes/UTP.clazz.palette.xml">
        <Priority name="Medium">
        </Priority>
        <editor id="org.eclipse.papyrus.uml.diagram.clazz">
        </editor>
    </paletteDefinition>
</extension>
....

Once we register the palette definition with Papyrus, the test will pass and the TestContext contribution will be visible for UML Models that have the UML Testing Profile applied. TestContext palette contribution

Conclusion

In this article, we expose the following behaviors from the Eclipse workbench: the ability to apply the UML Testing Profile within the UML and Papyrus Editors and the ability to create UML elements extended by UML stereotypes defined in the UTP directly from a Papyrus editor palette. We describe these behaviors through tests and define them with plug-in extensions and external configuration. The code for these examples is here.

Monday, February 24, 2014

Deleting Directories Whose Contents Have Long Names From C#

Deleting Directories Whose Contents Have Long Names In C#

Goal:

To use C# to delete directories that contain files whose fully qualified names are longer than 260 characters.

Deleting a Directory That Contains Contents With Short Names

Suppose we need to delete a directory and all its contents from C#. If the fully qualified names of all its contents are less than 260 characters, then the standard DirectoryInfo.Delete method is sufficient, as we can see from the following test case.

LongFileNamesTest.cs
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;

namespace Timezra.LongFileNames
{
  [TestClass]
  public class LongFileNamesTest
  {
    [TestMethod]
    public void ShouldDeleteALocalDirectoryWithContents()
    {
      var directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
      var fileName = Path.Combine(directory.FullName, Path.GetRandomFileName());
      System.IO.File.Create(fileName).Dispose();

      directory.Delete(true); // Regular Directory Delete

      Assert.IsFalse(directory.Exists);
    }
  }
}

Deleting a Directory That Contains Contents With Long Names

Unfortunately, if the directory contains contents whose fully-qualified names are longer than 260 characters, this same method will fail.

LongFileNamesTest.cs
    [TestMethod]
    public void ShouldDeleteALocalDirectoryWithContentsThatHaveLongNames()
    {
      const int longFileNameLength = 247;
      var directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
      var fileName = Path.Combine(directory.FullName, new string('z', longFileNameLength) + ".txt");
      System.IO.File.Create(fileName).Dispose();

      directory.Delete(true); // Regular Directory Delete

      Assert.IsFalse(directory.Exists);
    }

This test case will fail with a message similar to this because we cannot even create the file with a fully-qualified name longer than 260 characters.

Test Failed - ShouldDeleteALocalDirectoryWithContentsThatHaveLongNames
Result Message:
Test method Timezra.LongFileNames.LongFileNamesTest.ShouldDeleteALocalDirectoryWithContentsThatHaveLongNames threw exception:
System.IO.PathTooLongException: The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.
Result StackTrace:
at System.IO.PathHelper.GetFullPathName()
  at System.IO.Path.NormalizePath(String path, Boolean fullCheck, Int32 maxPathLength)
  at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
  at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize)
  at System.IO.File.Create(String path)
  at Timezra.LongFileNames.LongFileNamesTest.ShouldDeleteALocalDirectoryWithContentsThatHaveLongNames()

Even if we managed to create the file outside of the test case (say by extracting an archive that contains files with long names), then we would see a more cryptic error on delete.

Test Failed - ShouldDeleteALocalDirectoryWithContentsThatHaveLongNames
Result Message:
Test method Timezra.LongFileNames.LongFileNamesTest.ShouldDeleteALocalDirectoryWithContentsThatHaveLongNames threw exception:
System.IO.DirectoryNotFoundException: Could not find a part of the path....
Result StackTrace:
at System.IO.Directory.DeleteHelper(String fullPath, String userPath, Boolean recursive, Boolean throwOnTopLevelDirectoryNotFound)
  at System.IO.Directory.Delete(String fullPath, String userPath, Boolean recursive, Boolean checkHost)
  at System.IO.DirectoryInfo.Delete(Boolean recursive)
  at Timezra.LongFileNames.LongFileNamesTest.ShouldDeleteALocalDirectoryWithContentsThatHaveLongNames()

We can get around this limit on creating and deleting long files by using the Microsoft Scripting Runtime and a special prefix on our file path. First, our project needs a Reference to COM -> Microsoft Scripting Runtime. Then we can use methods on the FileSystemObject, along with the \\?\ prefix to our path.

LongFileNamesTest.cs
    [TestMethod]
    public void ShouldDeleteALocalDirectoryWithContentsThatHaveLongNames()
    {
      const int longFileNameLength = 247;
      var directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
      var fileName = Path.Combine(directory.FullName, new string('z', longFileNameLength) + ".txt");
      var fso = new Scripting.FileSystemObject();
      fso.CreateTextFile(@"\\?\" + fileName).Close();

      fso.DeleteFolder(@"\\?\" + directory.FullName, true); // Local Directory Delete When Contents Have Long Names

      Assert.IsFalse(directory.Exists);
    }

Deleting a Remote Directory That Contains Contents With Long Names

Suppose we also need to support the deletion of remote directories with contents that are longer than 260 characters. The same principle applies, but our prefix is slightly different, i.e., \\?\UNC. Fortunately, we can test this by converting our local directory path to a UNC directory path.

LongFileNamesTest.cs
    [TestMethod]
    public void ShouldDeleteARemoteDirectoryWithContentsThatHaveLongNames()
    {
      const int longFileNameLength = 247;
      var directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
      var fileName = Path.Combine(directory.FullName, new string('z', longFileNameLength) + ".txt");
      var fso = new Scripting.FileSystemObject();
      fso.CreateTextFile(@"\\?\" + fileName).Close();

      var uncDirectoryName = @"\\" + System.Environment.MachineName + @"\" + directory.FullName.Replace(':', '$');
      fso.DeleteFolder(@"\\?\UNC" + uncDirectoryName.Substring(1), true); // Remote Directory Delete When Contents Have Long Names

      Assert.IsFalse(directory.Exists);
    }

Wednesday, October 16, 2013

JaCoCo and Scala

JaCoCo and Scala

Goal

To generate a reasonably accurate JaCoCo code coverage report for a Scala project.

tl;dr

A Maven plug-in for creating JaCoCo code coverage reports is available on github and will be a helpful resource for following this article. This project contains an example that integrates Maven, JaCoCo and ScalaTest with this plug-in and can be used as a template for your own projects. The interesting technologies showcased include JaCoCo and Scala.

The Project

Suppose our development team would like to track how much of our Scala code is covered by our test suite. Now suppose we would like to configure Maven to generate these reports with each build.

Using The Jacoco Maven Plug-in

The JaCoCo Maven plug-in can easily be configured to record and report coverage metrics for compiled Scala because JaCoCo uses a java agent to instrument bytecode on the fly. For example, given a project with standard layout, where Example.scala and ExampleSpec.scala represent a worker and its ScalaTest specification, then the pom configuration is fairly straightforward.

The project layout


  jacoco-scalatest-maven-plugin-example
  .
  |____pom.xml
  | src
  | | main
  | | | scala
  | | |____Example.scala
  | src
  | | test
  | | | scala
  | | |____ExampleSpec.scala

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>timezra.maven</groupId>
  <artifactId>jacoco-scalatest-maven-plugin-example</artifactId>
  <version>0.6.3.2-SNAPSHOT</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.scala-lang</groupId>
      <artifactId>scala-library</artifactId>
      <version>2.10.0</version>
    </dependency>
    <dependency>
      <groupId>org.scalatest</groupId>
      <artifactId>scalatest_2.10</artifactId>
      <version>2.0.RC2</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <sourceDirectory>src/main/scala</sourceDirectory>
    <testSourceDirectory>src/test/scala</testSourceDirectory>
    <plugins>
      <plugin>
        <groupId>org.scala-tools</groupId>
        <artifactId>maven-scala-plugin</artifactId>
        <version>2.15.2</version>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>testCompile</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <version>0.6.3.201306030806</version>
        <executions>
          <execution>
            <id>pre-test</id>
            <goals>
              <goal>prepare-agent</goal>
            </goals>
          </execution>
          <execution>
            <id>post-integration-test</id>
            <phase>post-integration-test</phase>
            <goals>
              <goal>report</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.16</version>
        <configuration>
          <skipTests>true</skipTests>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.scalatest</groupId>
        <artifactId>scalatest-maven-plugin</artifactId>
        <version>1.0-RC1</version>
        <configuration>
          <reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory>
          <junitxml>.</junitxml>
        </configuration>
        <executions>
          <execution>
            <id>test</id>
            <phase>integration-test</phase>
            <goals>
              <goal>test</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Traits and Mixed-in Methods

Unfortunately, this configuration will soon begin reporting false negative results when even the most basic Scala features are employed. For example, suppose our Example class extends a Scala trait.

ExampleTrait.scala

trait ExampleTrait {
  def thisIsMixedIn() {
    println("Hello world")
  }
}

TraitExample.scala

class TraitExample extends ExampleTrait {
  def sayHello() {
    println("Hello world")
  }
}

ExampleSpec.scala

import org.scalatest.FunSpec

class ExampleSpec extends FunSpec {
  describe("A Canary") {
    it("should cover a class that mixes in a trait") {
      val traitExample = new TraitExample
      traitExample.sayHello
    }
  }
}

The coverage results show a false negative result for the mixed-in method ExampleTrait#thisIsMixedIn. We expect to see 100% coverage but instead see 75%.
The coverage results for the TraitExample include a false negative report for the method mixed-in from the ExampleTrait.

We can see from the bytecode for ExampleTrait that the Scala compiler mixes in traits by generating bytecode for methods in the extending classes and these generated methods delegate to the implemented traits via static calls.

public class TraitExample implements ExampleTrait {
  public void thisIsMixedIn();
    0  aload_0 [this]
    1  invokestatic ExampleTrait$class.thisIsMixedIn(ExampleTrait) : void [17]
    4  return
      Line numbers:
        [pc: 0, line: 22]
      Local variable table:
        [pc: 0, pc: 5] local: this index: 0 type: TraitExample

....

  public TraitExample();
    0  aload_0 [this]
    1  invokespecial java.lang.Object() [35]
    4  aload_0 [this]
    5  invokestatic ExampleTrait$class.$init$(ExampleTrait) : void [38]
    8  return
      Line numbers:
        [pc: 0, line: 22]
      Local variable table:
        [pc: 0, pc: 9] local: this index: 0 type: TraitExample
}

We can also see from the bytecode that the line numbers for the mixed-in method and the constructor are the same. Perhaps this coincidence can give us enough information to filter out these types of false negatives from our coverage report.

Case Classes

Similarly, case classes generate even more bytecode for methods that can be excluded from our coverage reports.

CaseExample.scala

case class CaseExample(
    greeting: String = "Hello",
    name: String = "world") {
  def sayHello() {
    println(s"${greeting} ${name}")
  }
}

ExampleSpec.scala

import org.scalatest.FunSpec

class ExampleSpec extends FunSpec {
  describe("A Canary") {
    it("should cover a case class") {
      val caseExample = new CaseExample
      caseExample.sayHello
    }
  }
}

The coverage results show significantly more false negative results for mixed-in and generated methods. We expect to see coverage results closer to 100% than to 32%.
The coverage results for the CaseExample include false negative reports for mixed-in and generated methods.

Again, we can see from the bytecode that the line numbers for the mixed-in methods and the constructor are the same. We can also see from the bytecode that generated methods follow a pattern: curried(), tupled() and (\w|\$)+\$default\$\d+\(\) methods. These methods unfortunately cannot be filtered by their debug line numbers, but they can be identified consistently by name. This information may be sufficient for eliminating these false negative results so that our coverage trend can at least look more reasonable even if not exact.

public class CaseExample implements scala.Product, scala.Serializable {
  private final java.lang.String greeting;
  private final java.lang.String name;

  public static java.lang.String apply$default$2();
    0  getstatic CaseExample$.MODULE$ : CaseExample. [20]
    3  invokevirtual CaseExample$.apply$default$2() : java.lang.String [22]
    6  areturn

  public static java.lang.String apply$default$1();
    0  getstatic CaseExample$.MODULE$ : CaseExample. [20]
    3  invokevirtual CaseExample$.apply$default$1() : java.lang.String [25]
    6  areturn

  public static java.lang.String $lessinit$greater$default$2();
    0  getstatic CaseExample$.MODULE$ : CaseExample. [20]
    3  invokevirtual CaseExample$.$lessinit$greater$default$2() : java.lang.String [28]
    6  areturn

  public static java.lang.String $lessinit$greater$default$1();
    0  getstatic CaseExample$.MODULE$ : CaseExample. [20]
    3  invokevirtual CaseExample$.$lessinit$greater$default$1() : java.lang.String [31]
    6  areturn

  public static scala.Function1 tupled();
    0  getstatic CaseExample$.MODULE$ : CaseExample. [20]
    3  invokevirtual CaseExample$.tupled() : scala.Function1 [35]
    6  areturn

  public static scala.Function1 curried();
    0  getstatic CaseExample$.MODULE$ : CaseExample. [20]
    3  invokevirtual CaseExample$.curried() : scala.Function1 [38]
    6  areturn

  public java.lang.String greeting();
    0  aload_0 [this]
    1  getfield CaseExample.greeting : java.lang.String [43]
    4  areturn
      Line numbers:
        [pc: 0, line: 23]
      Local variable table:
        [pc: 0, pc: 5] local: this index: 0 type: CaseExample
  
  public java.lang.String name();
    0  aload_0 [this]
    1  getfield CaseExample.name : java.lang.String [47]
    4  areturn
      Line numbers:
        [pc: 0, line: 24]
      Local variable table:
        [pc: 0, pc: 5] local: this index: 0 type: CaseExample
  
....  

  public CaseExample copy(java.lang.String greeting, java.lang.String name);
     0  new CaseExample [2]
     3  dup
     4  aload_1 [greeting]
     5  aload_2 [name]
     6  invokespecial CaseExample(java.lang.String, java.lang.String) [95]
     9  areturn
      Line numbers:
        [pc: 0, line: 22]
        [pc: 4, line: 23]
        [pc: 5, line: 24]
        [pc: 6, line: 22]
      Local variable table:
        [pc: 0, pc: 10] local: this index: 0 type: CaseExample
        [pc: 0, pc: 10] local: greeting index: 1 type: java.lang.String
        [pc: 0, pc: 10] local: name index: 2 type: java.lang.String
  
  public java.lang.String copy$default$1();
    0  aload_0 [this]
    1  invokevirtual CaseExample.greeting() : java.lang.String [76]
    4  areturn
      Line numbers:
        [pc: 0, line: 23]
      Local variable table:
        [pc: 0, pc: 5] local: this index: 0 type: CaseExample
  
  public java.lang.String copy$default$2();
    0  aload_0 [this]
    1  invokevirtual CaseExample.name() : java.lang.String [78]
    4  areturn
      Line numbers:
        [pc: 0, line: 24]
      Local variable table:
        [pc: 0, pc: 5] local: this index: 0 type: CaseExample
  
  public java.lang.String productPrefix();
    0  ldc [99]
    2  areturn
      Line numbers:
        [pc: 0, line: 22]
      Local variable table:
        [pc: 0, pc: 3] local: this index: 0 type: CaseExample
  
  public int productArity();
    0  iconst_2
    1  ireturn
      Line numbers:
        [pc: 0, line: 22]
      Local variable table:
        [pc: 0, pc: 2] local: this index: 0 type: CaseExample
  
  public java.lang.Object productElement(int x$1);
     0  iload_1 [x$1]
     1  istore_2
     2  iload_2
     3  tableswitch default: 24
          case 0: 46
          case 1: 39
    24  new java.lang.IndexOutOfBoundsException [105]
    27  dup
    28  iload_1 [x$1]
    29  invokestatic scala.runtime.BoxesRunTime.boxToInteger(int) : java.lang.Integer [111]
    32  invokevirtual java.lang.Object.toString() : java.lang.String [114]
    35  invokespecial java.lang.IndexOutOfBoundsException(java.lang.String) [117]
    38  athrow
    39  aload_0 [this]
    40  invokevirtual CaseExample.name() : java.lang.String [78]
    43  goto 50
    46  aload_0 [this]
    47  invokevirtual CaseExample.greeting() : java.lang.String [76]
    50  areturn
      Line numbers:
        [pc: 0, line: 22]
      Local variable table:
        [pc: 0, pc: 51] local: this index: 0 type: CaseExample
        [pc: 0, pc: 51] local: x$1 index: 1 type: int
      Stack map table: number of frames 4
        [pc: 24, append: {int}]
        [pc: 39, same]
        [pc: 46, same]
        [pc: 50, same_locals_1_stack_item, stack: {java.lang.String}]
  
  public scala.collection.Iterator productIterator();
    0  getstatic scala.runtime.ScalaRunTime$.MODULE$ : scala.runtime.ScalaRunTime. [126]
    3  aload_0 [this]
    4  invokevirtual scala.runtime.ScalaRunTime$.typedProductIterator(scala.Product) : scala.collection.Iterator [130]
    7  areturn
      Line numbers:
        [pc: 0, line: 22]
      Local variable table:
        [pc: 0, pc: 8] local: this index: 0 type: CaseExample
  
  public boolean canEqual(java.lang.Object x$1);
    0  aload_1 [x$1]
    1  instanceof CaseExample [2]
    4  ireturn
      Line numbers:
        [pc: 0, line: 22]
      Local variable table:
        [pc: 0, pc: 5] local: this index: 0 type: CaseExample
        [pc: 0, pc: 5] local: x$1 index: 1 type: java.lang.Object
  
  public int hashCode();
    0  getstatic scala.runtime.ScalaRunTime$.MODULE$ : scala.runtime.ScalaRunTime. [126]
    3  aload_0 [this]
    4  invokevirtual scala.runtime.ScalaRunTime$._hashCode(scala.Product) : int [138]
    7  ireturn
      Line numbers:
        [pc: 0, line: 22]
      Local variable table:
        [pc: 0, pc: 8] local: this index: 0 type: CaseExample
  
  public java.lang.String toString();
    0  getstatic scala.runtime.ScalaRunTime$.MODULE$ : scala.runtime.ScalaRunTime. [126]
    3  aload_0 [this]
    4  invokevirtual scala.runtime.ScalaRunTime$._toString(scala.Product) : java.lang.String [142]
    7  areturn
      Line numbers:
        [pc: 0, line: 22]
      Local variable table:
        [pc: 0, pc: 8] local: this index: 0 type: CaseExample
  
  public boolean equals(java.lang.Object x$1);
     0  aload_0 [this]
     1  aload_1 [x$1]
     2  if_acmpeq 92
     5  aload_1 [x$1]
     6  instanceof CaseExample [2]
     9  ifeq 96
    12  aload_1 [x$1]
    13  checkcast CaseExample [2]
    16  astore_2
    17  aload_0 [this]
    18  invokevirtual CaseExample.greeting() : java.lang.String [76]
    21  aload_2
    22  invokevirtual CaseExample.greeting() : java.lang.String [76]
    25  astore_3
    26  dup
    27  ifnonnull 38
    30  pop
    31  aload_3
    32  ifnull 45
    35  goto 88
    38  aload_3
    39  invokevirtual java.lang.Object.equals(java.lang.Object) : boolean [145]
    42  ifeq 88
    45  aload_0 [this]
    46  invokevirtual CaseExample.name() : java.lang.String [78]
    49  aload_2
    50  invokevirtual CaseExample.name() : java.lang.String [78]
    53  astore 4
    55  dup
    56  ifnonnull 68
    59  pop
    60  aload 4
    62  ifnull 76
    65  goto 88
    68  aload 4
    70  invokevirtual java.lang.Object.equals(java.lang.Object) : boolean [145]
    73  ifeq 88
    76  aload_2
    77  aload_0 [this]
    78  invokevirtual CaseExample.canEqual(java.lang.Object) : boolean [147]
    81  ifeq 88
    84  iconst_1
    85  goto 89
    88  iconst_0
    89  ifeq 96
    92  iconst_1
    93  goto 97
    96  iconst_0
    97  ireturn
      Line numbers:
        [pc: 0, line: 22]
      Local variable table:
        [pc: 0, pc: 98] local: this index: 0 type: CaseExample
        [pc: 0, pc: 98] local: x$1 index: 1 type: java.lang.Object
      Stack map table: number of frames 9
        [pc: 38, full, stack: {java.lang.String}, locals: {CaseExample, java.lang.Object, CaseExample, java.lang.String}]
        [pc: 45, same]
        [pc: 68, full, stack: {java.lang.String}, locals: {CaseExample, java.lang.Object, CaseExample, java.lang.String, java.lang.String}]
        [pc: 76, same]
        [pc: 88, chop 1 local(s)]
        [pc: 89, same_locals_1_stack_item, stack: {int}]
        [pc: 92, chop 2 local(s)]
        [pc: 96, same]
        [pc: 97, same_locals_1_stack_item, stack: {int}]
  
  public CaseExample(java.lang.String greeting, java.lang.String name);
     0  aload_0 [this]
     1  aload_1 [greeting]
     2  putfield CaseExample.greeting : java.lang.String [43]
     5  aload_0 [this]
     6  aload_2 [name]
     7  putfield CaseExample.name : java.lang.String [47]
    10  aload_0 [this]
    11  invokespecial java.lang.Object() [149]
    14  aload_0 [this]
    15  invokestatic scala.Product$class.$init$(scala.Product) : void [155]
    18  return
      Line numbers:
        [pc: 0, line: 23]
        [pc: 5, line: 24]
        [pc: 10, line: 22]
      Local variable table:
        [pc: 0, pc: 19] local: this index: 0 type: CaseExample
        [pc: 0, pc: 19] local: greeting index: 1 type: java.lang.String
        [pc: 0, pc: 19] local: name index: 2 type: java.lang.String
}

JaCoCo Scala Maven Plugin

The JaCoCo project maintainers are currently in the process of collecting use cases for the types of configurable filtering options that the community needs. Until that general-purpose solution has been implemented, the jacoco-scala-maven-plugin can be used to fulfill two of those specific filtering needs, i.e., SCALAC.MIXIN and SCALAC.CASE. These two filters eliminate methods that have the same debug line numbers as the constructor and the names tupled(), curried() and (\w|\$)+\$default\$\d+\(\), as noted above.

In our pom, instead of configuring the jacoco-maven-plugin to emit a report, we can now pass these two filtering options to the jacoco-scala-maven-plugin.
pom.xml

<project ....>
....
  <build>
....
    <plugins>
....
      <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <version>0.6.3.201306030806</version>
        <executions>
          <execution>
            <id>pre-test</id>
            <goals>
              <goal>prepare-agent</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>timezra.maven</groupId>
        <artifactId>jacoco-scala-maven-plugin</artifactId>
        <version>0.6.3.1</version>
        <executions>
          <execution>
            <id>post-integration-test</id>
            <phase>post-integration-test</phase>
            <goals>
              <goal>report</goal>
            </goals>
            <configuration>
              <filters>
                <filter>SCALAC.CASE</filter>
                <filter>SCALAC.MIXIN</filter>
              </filters>
            </configuration>
          </execution>
        </executions>
      </plugin>
....
    </plugins>
  </build>
  <pluginRepositories>
    <pluginRepository>
      <id>tims-repo</id>
      <url>http://timezra.github.com/maven/releases</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </pluginRepository>
  </pluginRepositories>
</project>

When using these two filters, the coverage results for the TraitExample and CaseExample no longer include generated and mixed-in methods, and instead give us a more accurate coverage score of 100% for both.
The TraitExample coverage report without false negatives.
The CaseExample coverage report without false negatives.

Conclusion

By examining and resolving anomalies in the code coverage statistics for our project through the introduction of a jacoco-scala-maven-plugin, we have gained an insight into how the Scala compiler mixes and injects methods into generated bytecode (which can also give insight into how Scala is able to chain super calls when multiple traits are linearized). While there are certainly other generated instructions that can be ignored during a coverage run, these two filters get us closer to being able to gather useful coverage trend information over time.

If you notice any other filtering options that you would like to see from the jacoco-scala-maven-plugin, please let me know, either here or on github.