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

Check the API coverage in examples #53

Merged
merged 1 commit into from
Nov 6, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ name := "rxscala"

lazy val root = project in file(".")

lazy val examples = project in file("examples") dependsOn (root % "test->test;compile->compile")
lazy val examples = project in file("examples") dependsOn (root % "test->test;compile->compile") settings(
libraryDependencies ++= Seq(
"org.apache.bcel" % "bcel" % "5.2" % "test"
)
)

scalacOptions in ThisBuild := Seq("-feature", "-unchecked", "-deprecation", "-encoding", "utf8")

Expand Down
108 changes: 108 additions & 0 deletions examples/src/test/scala/rx/lang/scala/examples/APICoverage.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* Copyright 2014 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package rx.lang.scala.examples

import scala.collection.mutable

import org.apache.bcel.Repository
import org.apache.bcel.classfile.Utility
import org.apache.bcel.util.ByteSequence
import org.junit.Assert._
import org.junit.Test
import org.scalatest.junit.JUnitSuite

class APICoverage extends JUnitSuite {

val INVOKEVIRTUAL_PREFIX = "invokevirtual\t"
val INVOKEINTERFACE_PREFIX = "invokeinterface\t"

def searchInvokedMethodsInClass(className : String): (Set[String], Set[String]) = {
val clazz = Repository.lookupClass(className)
val virtualMethods = mutable.Set[String]()
val interfaceMethods = mutable.Set[String]()
for (method <- clazz.getMethods) {
val code = method.getCode
if (code != null) {
val stream = new ByteSequence(code.getCode)
while (stream.available() > 0) {
val instruction = Utility.codeToString(stream, code.getConstantPool, false)
if (instruction.startsWith(INVOKEVIRTUAL_PREFIX)) {
// instruction is something like
// invokevirtual<tab>rx.lang.scala.Observable$.interval<space>(Lscala/concurrent/duration/Duration;)Lrx/lang/scala/Observable;
val invokedMethod = instruction.substring(INVOKEVIRTUAL_PREFIX.length).replaceAll(" ", "")
virtualMethods += invokedMethod
} else if (instruction.startsWith(INVOKEINTERFACE_PREFIX)) {
// instruction is something like
// invokeinterface<tab>rx.lang.scala.Observable.firstOrElse<space>(Lscala/Function0;)Lrx/lang/scala/Observable;2<tab>0
val invokedMethod = instruction.substring(INVOKEINTERFACE_PREFIX.length, instruction.lastIndexOf('\t') - 1).replaceAll(" ", "")
interfaceMethods += invokedMethod
}
}
}
}
(virtualMethods.toSet, interfaceMethods.toSet)
}

def searchInvokedMethodsInPackage(classInThisPackage: Class[_]): (Set[String], Set[String]) = {
val virtualMethods = mutable.Set[String]()
val interfaceMethods = mutable.Set[String]()
val packageName = classInThisPackage.getPackage.getName
val packageDir = new java.io.File(classInThisPackage.getResource(".").toURI)
for(file <- packageDir.listFiles if file.getName.endsWith(".class")) {
val simpleClassName = file.getName.substring(0, file.getName.length - ".class".length)
val className = packageName + "." + simpleClassName
val (vMethods, iMethods) = searchInvokedMethodsInClass(className)
virtualMethods ++= vMethods
interfaceMethods ++= iMethods
}
(virtualMethods.toSet, interfaceMethods.toSet)
}

val nonPublicAPIs = Set(
"<clinit>",
"jObsOfListToScObsOfSeq",
"jObsOfJObsToScObsOfScObs"
)

def getPublicMethods(className: String): Set[String] = {
val clazz = Repository.lookupClass(className)
clazz.getMethods.filter(method => method.isPublic && !nonPublicAPIs.contains(method.getName)).map {
method => className + "." + method.getName + method.getSignature
}.toSet
}

@Test
def assertExampleCoverage(): Unit = {
val objectMethods = getPublicMethods("rx.lang.scala.Observable$")
val classMethods = getPublicMethods("rx.lang.scala.Observable")

val (usedObjectMethods, usedClassMethods) = searchInvokedMethodsInPackage(getClass)

val uncoveredObjectMethods = objectMethods.diff(usedObjectMethods)
val uncoveredClassMethods = classMethods.diff(usedClassMethods)

if (uncoveredObjectMethods.nonEmpty || uncoveredClassMethods.nonEmpty) {
println("Please add examples for the following APIs")
uncoveredObjectMethods.foreach(println)
uncoveredClassMethods.foreach(println)
}

// TODO enable these assertions
// assertTrue("Please add missing examples", uncoveredObjectMethods.isEmpty)
// assertTrue("Please add missing examples", uncoveredClassMethods.isEmpty)
}

}