Merge branch 'stable-2.16' into stable-3.0 * stable-2.16: Promote aggregation key to case class Add ability to ignore specific files suffixes Change-Id: Ifceb12d03188813db37816fe84ac38ff72f79fb6
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md index 6c08a06..e9d9718 100644 --- a/src/main/resources/Documentation/config.md +++ b/src/main/resources/Documentation/config.md
@@ -34,4 +34,24 @@ ```ini [contributors] extract-issues = true + ``` + +- `contributors.ignore-file-suffix` + + List of file suffixes to be ignored from the analytics. + Files matching any of the specified suffixes will not be accounted for in + `num_files`, `num_distinct_files`, `added_lines` and `deleted_lines` fields + nor will they be listed in the `commits.files` array field. + This can be used to explicitly ignore binary files for which, file-based + statistics makes little or no sense. + + Default: empty + + Example: + ```ini + [contributors] + ignore-file-suffix = .dmg + ignore-file-suffix = .ko + ignore-file-suffix = .png + ignore-file-suffix = .exe ``` \ No newline at end of file
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/AnalyticsConfig.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/AnalyticsConfig.scala index fd31626..10eb8ed 100644 --- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/AnalyticsConfig.scala +++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/AnalyticsConfig.scala
@@ -23,15 +23,18 @@ trait AnalyticsConfig { def botlikeFilenameRegexps: List[String] def isExtractIssues: Boolean + def ignoreFileSuffixes: List[String] } class AnalyticsConfigImpl @Inject() (val pluginConfigFactory: PluginConfigFactory, @PluginName val pluginName: String) extends AnalyticsConfig{ lazy val botlikeFilenameRegexps: List[String] = pluginConfigBotLikeFilenameRegexp lazy val isExtractIssues: Boolean = pluginConfig.getBoolean(Contributors, null, ExtractIssues, false) + lazy val ignoreFileSuffixes: List[String] = pluginConfig.getStringList(Contributors, null, IgnoreFileSuffix).toList private lazy val pluginConfig: Config = pluginConfigFactory.getGlobalPluginConfig(pluginName) private val Contributors = "contributors" private val BotlikeFilenameRegexp = "botlike-filename-regexp" private val ExtractIssues = "extract-issues" + private val IgnoreFileSuffix = "ignore-file-suffix" private lazy val pluginConfigBotLikeFilenameRegexp = pluginConfig.getStringList(Contributors, null, BotlikeFilenameRegexp).toList }
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/Contributors.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/Contributors.scala index f634725..98e4e06 100644 --- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/Contributors.scala +++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/Contributors.scala
@@ -24,8 +24,6 @@ import com.googlesource.gerrit.plugins.analytics.common._ import org.kohsuke.args4j.{Option => ArgOption} -import scala.util.Try - @CommandMetaData(name = "contributors", description = "Extracts the list of contributors to a project") class ContributorsCommand @Inject()(val executor: ContributorsService, val projects: ProjectsCollection, @@ -130,10 +128,11 @@ } class ContributorsService @Inject()(repoManager: GitRepositoryManager, - projectCache:ProjectCache, + projectCache: ProjectCache, histogram: UserActivityHistogram, gsonFmt: GsonFormatter, commitsStatisticsCache: CommitsStatisticsCache) { + import RichBoolean._ def get(projectRes: ProjectResource, startDate: Option[Long], stopDate: Option[Long], @@ -141,7 +140,7 @@ : TraversableOnce[UserActivitySummary] = { ManagedResource.use(repoManager.openRepository(projectRes.getNameKey)) { repo => - val stats = new Statistics(projectRes.getNameKey, commitsStatisticsCache) + val stats = new Statistics(projectRes.getNameKey, commitsStatisticsCache) val branchesExtractor = extractBranches.option(new BranchesExtractor(repo)) histogram.get(repo, new AggregatedHistogramFilterByDates(startDate, stopDate, branchesExtractor, aggregationStrategy)) @@ -155,10 +154,10 @@ case class IssueInfo(code: String, link: String) -case class UserActivitySummary(year: Integer, - month: Integer, - day: Integer, - hour: Integer, +case class UserActivitySummary(year: Option[Int], + month: Option[Int], + day: Option[Int], + hour: Option[Int], name: String, email: String, numCommits: Integer, @@ -179,24 +178,30 @@ def apply(statisticsHandler: Statistics)(uca: AggregatedUserCommitActivity) : Iterable[UserActivitySummary] = { - def stringToIntOrNull(x: String): Integer = Try(new Integer(x)).getOrElse(null) + statisticsHandler.forCommits(uca.getIds: _*).map { stat => + val maybeBranches = + uca.key.branch.fold(Array.empty[String])(Array(_)) - uca.key.split("/", AggregationStrategy.MAX_MAPPING_TOKENS) match { - case Array(email, year, month, day, hour, branch) => - statisticsHandler.forCommits(uca.getIds: _*).map { stat => - val maybeBranches = - Option(branch).filter(_.nonEmpty).map(b => Array(b)).getOrElse(Array.empty) - - UserActivitySummary( - stringToIntOrNull(year), stringToIntOrNull(month), stringToIntOrNull(day), stringToIntOrNull(hour), - uca.getName, email, stat.commits.size, - stat.numFiles, stat.numDistinctFiles, stat.addedLines, stat.deletedLines, - stat.commits.toArray, maybeBranches, stat.issues.map(_.code) - .toArray, stat.issues.map(_.link).toArray, uca.getLatest, stat - .isForMergeCommits,stat.isForBotLike - ) - } - case _ => throw new Exception(s"invalid key format found ${uca.key}") + UserActivitySummary( + uca.key.year, + uca.key.month, + uca.key.day, + uca.key.hour, + uca.getName, + uca.key.email, + stat.commits.size, + stat.numFiles, + stat.numDistinctFiles, + stat.addedLines, + stat.deletedLines, + stat.commits.toArray, + maybeBranches, + stat.issues.map(_.code).toArray, + stat.issues.map(_.link).toArray, + uca.getLatest, + stat.isForMergeCommits, + stat.isForBotLike + ) } } }
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AggregatedCommitHistogram.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AggregatedCommitHistogram.scala index 74abf08..f39fc73 100644 --- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AggregatedCommitHistogram.scala +++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AggregatedCommitHistogram.scala
@@ -16,19 +16,19 @@ import java.util.Date -import com.googlesource.gerrit.plugins.analytics.common.AggregationStrategy.BY_BRANCH +import com.googlesource.gerrit.plugins.analytics.common.AggregationStrategy.{AggregationKey, BY_BRANCH} import org.eclipse.jgit.lib.PersonIdent import org.eclipse.jgit.revwalk.RevCommit import org.gitective.core.stat.{CommitHistogram, CommitHistogramFilter, UserCommitActivity} -class AggregatedUserCommitActivity(val key: String, val name: String, val email: String) +class AggregatedUserCommitActivity(val key: AggregationKey, val name: String, val email: String) extends UserCommitActivity(name, email) class AggregatedCommitHistogram(var aggregationStrategy: AggregationStrategy) extends CommitHistogram { def includeWithBranches(commit: RevCommit, user: PersonIdent, branches: Set[String]): Unit = { - for ( branch <- branches ) { + for (branch <- branches) { val originalStrategy = aggregationStrategy this.aggregationStrategy = BY_BRANCH(branch, aggregationStrategy) this.include(commit, user) @@ -38,11 +38,13 @@ override def include(commit: RevCommit, user: PersonIdent): AggregatedCommitHistogram = { val key = aggregationStrategy.mapping(user, commit.getAuthorIdent.getWhen) - val activity = Option(users.get(key)) match { + val keyString = key.toString + + val activity = Option(users.get(keyString)) match { case None => val newActivity = new AggregatedUserCommitActivity(key, user.getName, user.getEmailAddress) - users.put(key, newActivity) + users.put(keyString, newActivity) newActivity case Some(foundActivity) => foundActivity } @@ -56,7 +58,7 @@ } object AggregatedCommitHistogram { - type AggregationStrategyMapping = (PersonIdent, Date) => String + type AggregationStrategyMapping = (PersonIdent, Date) => AggregationKey } abstract class AbstractCommitHistogramFilter(aggregationStrategy: AggregationStrategy)
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AggregationStrategy.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AggregationStrategy.scala index 079c307..3292fc9 100644 --- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AggregationStrategy.scala +++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AggregationStrategy.scala
@@ -41,33 +41,57 @@ def utc: LocalDateTime = d.toInstant.atZone(ZoneOffset.UTC).toLocalDateTime } + case class AggregationKey(email: String, + year: Option[Int] = None, + month: Option[Int] = None, + day: Option[Int] = None, + hour: Option[Int] = None, + branch: Option[String] = None) + object EMAIL extends AggregationStrategy { val name: String = "EMAIL" - val mapping: (PersonIdent, Date) => String = (p, _) => s"${p.getEmailAddress}/////" + val mapping: (PersonIdent, Date) => AggregationKey = (p, _) => + AggregationKey(email = p.getEmailAddress) } object EMAIL_YEAR extends AggregationStrategy { val name: String = "EMAIL_YEAR" - val mapping: (PersonIdent, Date) => String = (p, d) => s"${p.getEmailAddress}/${d.utc.getYear}////" + val mapping: (PersonIdent, Date) => AggregationKey = (p, d) => + AggregationKey(email = p.getEmailAddress, year = Some(d.utc.getYear)) } object EMAIL_MONTH extends AggregationStrategy { val name: String = "EMAIL_MONTH" - val mapping: (PersonIdent, Date) => String = (p, d) => s"${p.getEmailAddress}/${d.utc.getYear}/${d.utc.getMonthValue}///" + val mapping: (PersonIdent, Date) => AggregationKey = (p, d) => + AggregationKey(email = p.getEmailAddress, + year = Some(d.utc.getYear), + month = Some(d.utc.getMonthValue)) } object EMAIL_DAY extends AggregationStrategy { val name: String = "EMAIL_DAY" - val mapping: (PersonIdent, Date) => String = (p, d) => s"${p.getEmailAddress}/${d.utc.getYear}/${d.utc.getMonthValue}/${d.utc.getDayOfMonth}//" + val mapping: (PersonIdent, Date) => AggregationKey = (p, d) => + AggregationKey(email = p.getEmailAddress, + year = Some(d.utc.getYear), + month = Some(d.utc.getMonthValue), + day = Some(d.utc.getDayOfMonth)) } object EMAIL_HOUR extends AggregationStrategy { val name: String = "EMAIL_HOUR" - val mapping: (PersonIdent, Date) => String = (p, d) => s"${p.getEmailAddress}/${d.utc.getYear}/${d.utc.getMonthValue}/${d.utc.getDayOfMonth}/${d.utc.getHour}/" + val mapping: (PersonIdent, Date) => AggregationKey = (p, d) => + AggregationKey(email = p.getEmailAddress, + year = Some(d.utc.getYear), + month = Some(d.utc.getMonthValue), + day = Some(d.utc.getDayOfMonth), + hour = Some(d.utc.getHour)) } - case class BY_BRANCH(branch: String, baseAggregationStrategy: AggregationStrategy) extends AggregationStrategy { + case class BY_BRANCH(branch: String, + baseAggregationStrategy: AggregationStrategy) + extends AggregationStrategy { val name: String = s"BY_BRANCH($branch)" - val mapping: (PersonIdent, Date) => String = (p, d) => s"${baseAggregationStrategy.mapping(p, d)}$branch" + val mapping: (PersonIdent, Date) => AggregationKey = (p, d) => + baseAggregationStrategy.mapping(p, d).copy(branch = Some(branch)) } }
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsCache.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsCache.scala index 8d573f0..c684c57 100644 --- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsCache.scala +++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsCache.scala
@@ -20,7 +20,6 @@ import com.googlesource.gerrit.plugins.analytics.common.CommitsStatisticsCache.COMMITS_STATISTICS_CACHE import org.eclipse.jgit.lib.ObjectId -@ImplementedBy(classOf[CommitsStatisticsCacheImpl]) trait CommitsStatisticsCache { def get(project: String, objectId: ObjectId): CommitsStatistics }
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsCacheModule.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsCacheModule.scala index 9b2e358..5e4fe2f 100644 --- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsCacheModule.scala +++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsCacheModule.scala
@@ -21,6 +21,7 @@ class CommitsStatisticsCacheModule extends CacheModule() { override protected def configure(): Unit = { + bind(classOf[CommitsStatisticsCache]).to(classOf[CommitsStatisticsCacheImpl]) persist(CommitsStatisticsCache.COMMITS_STATISTICS_CACHE, classOf[CommitsStatisticsCacheKey], classOf[CommitsStatistics]) .version(1) .diskLimit(-1)
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsLoader.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsLoader.scala index 1144901..8025922 100644 --- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsLoader.scala +++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsLoader.scala
@@ -34,7 +34,8 @@ gitRepositoryManager: GitRepositoryManager, projectCache: ProjectCache, botLikeExtractor: BotLikeExtractor, - config: AnalyticsConfig + config: AnalyticsConfig, + ignoreFileSuffixFilter: IgnoreFileSuffixFilter ) extends CacheLoader[CommitsStatisticsCacheKey, CommitsStatistics] { override def load(cacheKey: CommitsStatisticsCacheKey): CommitsStatistics = { @@ -70,6 +71,7 @@ val df = new DiffFormatter(DisabledOutputStream.INSTANCE) df.setRepository(repo) + df.setPathFilter(ignoreFileSuffixFilter) df.setDiffComparator(RawTextComparator.DEFAULT) df.setDetectRenames(true) val diffs = df.scan(oldTree, newTree).asScala
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/GsonFormatter.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/GsonFormatter.scala index d36cfef..b054554 100644 --- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/GsonFormatter.scala +++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/GsonFormatter.scala
@@ -15,19 +15,18 @@ package com.googlesource.gerrit.plugins.analytics.common import java.io.PrintWriter - -import com.google.gerrit.json.OutputFormat -import com.google.gson.{Gson, GsonBuilder, JsonSerializer} -import com.google.inject.Singleton import java.lang.reflect.Type -import com.google.gson._ +import com.google.gerrit.json.OutputFormat +import com.google.gson.{Gson, GsonBuilder, JsonSerializer, _} +import com.google.inject.Singleton @Singleton class GsonFormatter { val gsonBuilder: GsonBuilder = OutputFormat.JSON_COMPACT.newGsonBuilder .registerTypeHierarchyAdapter(classOf[Iterable[Any]], new IterableSerializer) + .registerTypeHierarchyAdapter(classOf[Option[Any]], new OptionSerializer()) def format[T](values: TraversableOnce[T], out: PrintWriter) { val gson: Gson = gsonBuilder.create @@ -45,4 +44,12 @@ } } + class OptionSerializer extends JsonSerializer[Option[Any]] { + def serialize(src: Option[Any], typeOfSrc: Type, context: JsonSerializationContext): JsonElement = { + src match { + case None => JsonNull.INSTANCE + case Some(v) => context.serialize(v) + } + } + } }
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/IgnoreFileSuffixFilter.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/IgnoreFileSuffixFilter.scala new file mode 100644 index 0000000..d875eb0 --- /dev/null +++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/IgnoreFileSuffixFilter.scala
@@ -0,0 +1,35 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// 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 com.googlesource.gerrit.plugins.analytics.common + +import com.google.inject.{Inject, Singleton} +import com.googlesource.gerrit.plugins.analytics.AnalyticsConfig +import org.eclipse.jgit.treewalk.TreeWalk +import org.eclipse.jgit.treewalk.filter.TreeFilter +import org.gitective.core.PathFilterUtils + +@Singleton +case class IgnoreFileSuffixFilter @Inject() (config: AnalyticsConfig) extends TreeFilter { + + private lazy val suffixFilter = + if (config.ignoreFileSuffixes.nonEmpty) + PathFilterUtils.orSuffix(config.ignoreFileSuffixes:_*).negate() + else + TreeFilter.ALL + + override def include(treeWalk: TreeWalk): Boolean = treeWalk.isSubtree || suffixFilter.include(treeWalk) + override def shouldBeRecursive(): Boolean = suffixFilter.shouldBeRecursive() + override def clone(): TreeFilter = this +}
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/analytics/common/BotLikeExtractorImplSpec.scala b/src/test/scala/com/googlesource/gerrit/plugins/analytics/common/BotLikeExtractorImplSpec.scala index 5bb73c5..42b0d1c 100644 --- a/src/test/scala/com/googlesource/gerrit/plugins/analytics/common/BotLikeExtractorImplSpec.scala +++ b/src/test/scala/com/googlesource/gerrit/plugins/analytics/common/BotLikeExtractorImplSpec.scala
@@ -49,5 +49,6 @@ private def newBotLikeExtractorImpl(botLikeRegexps: List[String]) = new BotLikeExtractorImpl(new AnalyticsConfig { override lazy val botlikeFilenameRegexps = botLikeRegexps override lazy val isExtractIssues: Boolean = false + override def ignoreFileSuffixes: List[String] = List.empty }) }
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/analytics/common/IgnoreFileSuffixFilterSpec.scala b/src/test/scala/com/googlesource/gerrit/plugins/analytics/common/IgnoreFileSuffixFilterSpec.scala new file mode 100644 index 0000000..4ed373c --- /dev/null +++ b/src/test/scala/com/googlesource/gerrit/plugins/analytics/common/IgnoreFileSuffixFilterSpec.scala
@@ -0,0 +1,54 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// 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 com.googlesource.gerrit.plugins.analytics.common + +import com.google.gerrit.acceptance.UseLocalDisk +import com.googlesource.gerrit.plugins.analytics.AnalyticsConfig +import com.googlesource.gerrit.plugins.analytics.test.GerritTestDaemon +import org.eclipse.jgit.treewalk.TreeWalk +import org.scalatest.{FlatSpec, Matchers} + +@UseLocalDisk +class IgnoreFileSuffixFilterSpec extends FlatSpec with Matchers with GerritTestDaemon { + + behavior of "IgnoreFileSuffixFilter" + + it should "include a file with suffix not listed in configuration" in { + val ignoreSuffix = ".dmg" + val fileSuffix = ".txt" + val aFile = s"aFile$fileSuffix" + val commit = testFileRepository.commitFile(aFile, "some content") + + val walk = TreeWalk.forPath(testFileRepository.getRepository, aFile, commit.getTree) + + newIgnoreFileSuffix(ignoreSuffix).include(walk) shouldBe true + } + + it should "not include a file with suffix listed in configuration" in { + val ignoreSuffix = ".dmg" + val aFile = s"aFile$ignoreSuffix" + val commit = testFileRepository.commitFile(aFile, "some content") + + val walk = TreeWalk.forPath(testFileRepository.getRepository, aFile, commit.getTree) + + newIgnoreFileSuffix(ignoreSuffix).include(walk) shouldBe false + } + + private def newIgnoreFileSuffix(suffixes: String*) = IgnoreFileSuffixFilter(new AnalyticsConfig { + override lazy val botlikeFilenameRegexps = List.empty + override lazy val isExtractIssues: Boolean = false + override def ignoreFileSuffixes: List[String] = suffixes.toList + }) +}
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/CommitStatisticsSpec.scala b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/CommitStatisticsSpec.scala index b2ae9e7..8c5eb60 100644 --- a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/CommitStatisticsSpec.scala +++ b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/CommitStatisticsSpec.scala
@@ -16,7 +16,7 @@ import com.google.gerrit.acceptance.UseLocalDisk import com.googlesource.gerrit.plugins.analytics.CommitInfo -import com.googlesource.gerrit.plugins.analytics.common.{CommitsStatistics, CommitsStatisticsLoader, Statistics} +import com.googlesource.gerrit.plugins.analytics.common.{CommitsStatistics, Statistics} import org.scalatest.{FlatSpec, Inside, Matchers} @UseLocalDisk @@ -131,5 +131,4 @@ case wrongContent => fail(s"Expected two results instead got $wrongContent") } } - }
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/ContributorsServiceSpec.scala b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/ContributorsServiceSpec.scala new file mode 100644 index 0000000..acefed1 --- /dev/null +++ b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/ContributorsServiceSpec.scala
@@ -0,0 +1,83 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// 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 com.googlesource.gerrit.plugins.analytics.test + +import java.lang.reflect.Type + +import com.google.gerrit.acceptance.UseLocalDisk +import com.google.gson._ +import com.googlesource.gerrit.plugins.analytics.UserActivitySummary +import com.googlesource.gerrit.plugins.analytics.common.AggregationStrategy.EMAIL_HOUR +import com.googlesource.gerrit.plugins.analytics.common.GsonFormatter +import com.googlesource.gerrit.plugins.analytics.test.TestAnalyticsConfig.IGNORED_FILE_SUFFIX +import org.scalatest.{FlatSpec, Inside, Matchers} + +import scala.collection.JavaConverters._ + +@UseLocalDisk +class ContributorsServiceSpec extends FlatSpec with Matchers with GerritTestDaemon with Inside { + + "ContributorsService" should "get commit statistics" in { + val aContributorName = "Contributor Name" + val aContributorEmail = "contributor@test.com" + val aFileName = "file.txt" + val anIgnoredFileName = s"file$IGNORED_FILE_SUFFIX" + + val commit = testFileRepository.commitFiles( + List(anIgnoredFileName -> "1\n2\n", aFileName -> "1\n2\n"), + newPersonIdent(aContributorName, aContributorEmail) + ) + + val statsJson = daemonTest.restSession.get(s"/projects/${fileRepositoryName.get()}/analytics~contributors?aggregate=${EMAIL_HOUR.name}") + + statsJson.assertOK() + + val stats = TestGson().fromJson(statsJson.getEntityContent, classOf[UserActivitySummary]) + + inside(stats) { + case UserActivitySummary(_, _, _, _, theAuthorName, theAuthorEmail, numCommits, numFiles, numDistinctFiles, addedLines, deletedLines, commits, _, _, _, _, _, _) => + theAuthorName shouldBe aContributorName + theAuthorEmail shouldBe aContributorEmail + numCommits shouldBe 1 + numFiles shouldBe 1 + numDistinctFiles shouldBe 1 + addedLines shouldBe 2 + deletedLines shouldBe 0 + commits.head.files should contain only aFileName + commits.head.sha1 shouldBe commit.name + } + } +} + +object TestGson { + + class SetStringDeserializer extends JsonDeserializer[Set[String]] { + override def deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Set[String] = + json.getAsJsonArray.asScala.map(_.getAsString).toSet + } + + class OptionDeserializer extends JsonDeserializer[Option[Any]] { + override def deserialize(jsonElement: JsonElement, `type`: Type, jsonDeserializationContext: JsonDeserializationContext): Option[Any] = { + Some(jsonElement) + } + } + + def apply(): Gson = + new GsonFormatter() + .gsonBuilder + .registerTypeHierarchyAdapter(classOf[Iterable[String]], new SetStringDeserializer) + .registerTypeHierarchyAdapter(classOf[Option[Any]], new OptionDeserializer()) + .create() +} \ No newline at end of file
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/GerritTestDaemon.scala b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/GerritTestDaemon.scala index 0024e1e..1b6b674 100644 --- a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/GerritTestDaemon.scala +++ b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/GerritTestDaemon.scala
@@ -20,9 +20,13 @@ import com.google.gerrit.acceptance.{AbstractDaemonTest, GitUtil} import com.google.gerrit.extensions.annotations.PluginName import com.google.gerrit.extensions.client.SubmitType +import com.google.gerrit.acceptance._ +import com.google.gerrit.extensions.restapi.RestApiModule import com.google.gerrit.reviewdb.client.Project -import com.google.inject.{AbstractModule, Module} -import com.googlesource.gerrit.plugins.analytics.AnalyticsConfig +import com.google.gerrit.server.project.ProjectResource.PROJECT_KIND +import com.google.inject.AbstractModule +import com.googlesource.gerrit.plugins.analytics.{AnalyticsConfig, ContributorsResource} +import com.googlesource.gerrit.plugins.analytics.common.CommitsStatisticsCache import org.eclipse.jgit.api.MergeCommand.FastForwardMode import org.eclipse.jgit.api.{Git, MergeResult} import org.eclipse.jgit.internal.storage.file.FileRepository @@ -69,6 +73,7 @@ new PersonIdent(new PersonIdent(name, email), ts) override def beforeEach { + daemonTest.setUpTestPlugin() fileRepositoryName = daemonTest.newProject(testSpecificRepositoryName) fileRepository = daemonTest.getRepository(fileRepositoryName) testFileRepository = GitUtil.newTestRepository(fileRepository) @@ -136,9 +141,14 @@ } } -object GerritTestDaemon extends AbstractDaemonTest { +@TestPlugin( + name = "analytics", + sysModule = "com.googlesource.gerrit.plugins.analytics.test.GerritTestDaemon$TestModule" +) +object GerritTestDaemon extends LightweightPluginDaemonTest { baseConfig = new Config() AbstractDaemonTest.temporaryFolder.create() + tempDataDir.create() def newProject(nameSuffix: String) = { resourcePrefix = "" @@ -151,15 +161,21 @@ def adminAuthor = admin.newIdent def getInstance[T](clazz: Class[T]): T = - server.getTestInjector.getInstance(clazz) + plugin.getSysInjector.getInstance(clazz) - override def createModule(): Module = new AbstractModule { + def getCanonicalWebUrl: String = canonicalWebUrl.get() + + def restSession: RestSession = adminRestSession + + class TestModule extends AbstractModule { override def configure(): Unit = { - bind(classOf[AnalyticsConfig]).toInstance(new AnalyticsConfig { - override def botlikeFilenameRegexps: List[String] = List.empty - override def isExtractIssues: Boolean = true + bind(classOf[CommitsStatisticsCache]).to(classOf[CommitsStatisticsNoCache]) + bind(classOf[AnalyticsConfig]).toInstance(TestAnalyticsConfig) + install(new RestApiModule() { + override protected def configure() = { + get(PROJECT_KIND, "contributors").to(classOf[ContributorsResource]) + } }) - bind(classOf[String]).annotatedWith(classOf[PluginName]).toInstance("analytics") } } } \ No newline at end of file
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/TestAnalyticsConfig.scala b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/TestAnalyticsConfig.scala new file mode 100644 index 0000000..598df31 --- /dev/null +++ b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/TestAnalyticsConfig.scala
@@ -0,0 +1,24 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// 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 com.googlesource.gerrit.plugins.analytics.test + +import com.googlesource.gerrit.plugins.analytics.AnalyticsConfig + +object TestAnalyticsConfig extends AnalyticsConfig { + val IGNORED_FILE_SUFFIX = ".bin" + val botlikeFilenameRegexps: List[String] = List.empty + val isExtractIssues: Boolean = true + val ignoreFileSuffixes: List[String] = List(IGNORED_FILE_SUFFIX) +}
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/TestCommitStatisticsNoCache.scala b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/TestCommitStatisticsNoCache.scala index 8834418..b0b962f 100644 --- a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/TestCommitStatisticsNoCache.scala +++ b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/TestCommitStatisticsNoCache.scala
@@ -14,6 +14,7 @@ package com.googlesource.gerrit.plugins.analytics.test +import com.google.inject.Inject import com.googlesource.gerrit.plugins.analytics.common._ import org.eclipse.jgit.lib.ObjectId @@ -23,7 +24,7 @@ lazy val commitsStatisticsNoCache = CommitsStatisticsNoCache(daemonTest.getInstance(classOf[CommitsStatisticsLoader])) } -case class CommitsStatisticsNoCache(commitsStatisticsLoader: CommitsStatisticsLoader) extends CommitsStatisticsCache { +case class CommitsStatisticsNoCache @Inject() (commitsStatisticsLoader: CommitsStatisticsLoader) extends CommitsStatisticsCache { override def get(project: String, objectId: ObjectId): CommitsStatistics = commitsStatisticsLoader.load(CommitsStatisticsCacheKey(project, objectId)) }