From 1ba1e87ef07ffd4ff37cc12340c63367d698134e Mon Sep 17 00:00:00 2001 From: zsxwing Date: Tue, 4 Nov 2014 20:54:53 +0800 Subject: [PATCH] Check the API coverage in examples --- build.sbt | 6 +- .../rx/lang/scala/examples/APICoverage.scala | 108 ++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 examples/src/test/scala/rx/lang/scala/examples/APICoverage.scala diff --git a/build.sbt b/build.sbt index d8bf1f45..36d7345a 100644 --- a/build.sbt +++ b/build.sbt @@ -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") diff --git a/examples/src/test/scala/rx/lang/scala/examples/APICoverage.scala b/examples/src/test/scala/rx/lang/scala/examples/APICoverage.scala new file mode 100644 index 00000000..fca9dcd6 --- /dev/null +++ b/examples/src/test/scala/rx/lang/scala/examples/APICoverage.scala @@ -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 + // invokevirtualrx.lang.scala.Observable$.interval(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 + // invokeinterfacerx.lang.scala.Observable.firstOrElse(Lscala/Function0;)Lrx/lang/scala/Observable;20 + 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( + "", + "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) + } + +}