From b5fa7a79b1c7791be84bb7880ea2bcf6b7b14b58 Mon Sep 17 00:00:00 2001 From: Svetlana Isakova Date: Thu, 30 May 2019 12:44:17 +0200 Subject: [PATCH 1/3] Added solutions --- src/contributors/GitHubService.kt | 11 +++++++++++ src/tasks/Aggregation.kt | 4 +++- src/tasks/Request2Background.kt | 2 +- src/tasks/Request3Callbacks.kt | 8 +++++--- src/tasks/Request4Suspend.kt | 11 ++++++++++- src/tasks/Request5Concurrent.kt | 14 +++++++++++++- src/tasks/Request5NotCancellable.kt | 16 +++++++++++++++- src/tasks/Request6Progress.kt | 15 ++++++++++++++- src/tasks/Request7Channels.kt | 23 ++++++++++++++++++++--- test/contributors/MockGithubService.kt | 4 ---- 10 files changed, 92 insertions(+), 16 deletions(-) diff --git a/src/contributors/GitHubService.kt b/src/contributors/GitHubService.kt index 1829a72..2075ce9 100644 --- a/src/contributors/GitHubService.kt +++ b/src/contributors/GitHubService.kt @@ -27,6 +27,17 @@ interface GitHubService { @Path("owner") owner: String, @Path("repo") repo: String ): Call> + + @GET("orgs/{org}/repos?per_page=100") + suspend fun getOrgRepos( + @Path("org") org: String + ): Response> + + @GET("repos/{owner}/{repo}/contributors?per_page=100") + suspend fun getRepoContributors( + @Path("owner") owner: String, + @Path("repo") repo: String + ): Response> } @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/src/tasks/Aggregation.kt b/src/tasks/Aggregation.kt index f2d6fa7..145b344 100644 --- a/src/tasks/Aggregation.kt +++ b/src/tasks/Aggregation.kt @@ -15,4 +15,6 @@ TODO: Write aggregation code. You can use 'Navigate | Test' menu action (note the shortcut) to navigate to the test. */ fun List.aggregate(): List = - this \ No newline at end of file + groupBy { it.login } + .map { (login, group) -> User(login, group.sumBy { it.contributions }) } + .sortedByDescending { it.contributions } \ No newline at end of file diff --git a/src/tasks/Request2Background.kt b/src/tasks/Request2Background.kt index 0ccfaf4..1ff509b 100644 --- a/src/tasks/Request2Background.kt +++ b/src/tasks/Request2Background.kt @@ -7,6 +7,6 @@ import kotlin.concurrent.thread fun loadContributorsBackground(service: GitHubService, req: RequestData, updateResults: (List) -> Unit) { thread { - loadContributorsBlocking(service, req) + updateResults(loadContributorsBlocking(service, req)) } } \ No newline at end of file diff --git a/src/tasks/Request3Callbacks.kt b/src/tasks/Request3Callbacks.kt index 392c57a..507a2b4 100644 --- a/src/tasks/Request3Callbacks.kt +++ b/src/tasks/Request3Callbacks.kt @@ -11,16 +11,18 @@ fun loadContributorsCallbacks(service: GitHubService, req: RequestData, updateRe service.getOrgReposCall(req.org).onResponse { responseRepos -> logRepos(req, responseRepos) val repos = responseRepos.bodyList() - val allUsers = mutableListOf() + val allUsers = Collections.synchronizedList(mutableListOf()) + val numberOfProcessed = AtomicInteger(0) for (repo in repos) { service.getRepoContributorsCall(req.org, repo.name).onResponse { responseUsers -> logUsers(repo, responseUsers) val users = responseUsers.bodyList() allUsers += users + if (numberOfProcessed.incrementAndGet() == repos.size) { + updateResults(allUsers.aggregate()) + } } } - // TODO: Why this code doesn't work? How to fix that? - updateResults(allUsers.aggregate()) } } diff --git a/src/tasks/Request4Suspend.kt b/src/tasks/Request4Suspend.kt index 37949e7..4ce00dc 100644 --- a/src/tasks/Request4Suspend.kt +++ b/src/tasks/Request4Suspend.kt @@ -3,5 +3,14 @@ package tasks import contributors.* suspend fun loadContributorsSuspend(service: GitHubService, req: RequestData): List { - TODO() + val repos = service + .getOrgRepos(req.org) + .also { logRepos(req, it) } + .bodyList() + + return repos.flatMap { repo -> + service.getRepoContributors(req.org, repo.name) + .also { logUsers(repo, it) } + .bodyList() + }.aggregate() } \ No newline at end of file diff --git a/src/tasks/Request5Concurrent.kt b/src/tasks/Request5Concurrent.kt index 03ab317..971db97 100644 --- a/src/tasks/Request5Concurrent.kt +++ b/src/tasks/Request5Concurrent.kt @@ -4,5 +4,17 @@ import contributors.* import kotlinx.coroutines.* suspend fun loadContributorsConcurrent(service: GitHubService, req: RequestData): List = coroutineScope { - TODO() + val repos = service + .getOrgRepos(req.org) + .also { logRepos(req, it) } + .bodyList() + + val deferreds: List>> = repos.map { repo -> + async { + service.getRepoContributors(req.org, repo.name) + .also { logUsers(repo, it) } + .bodyList() + } + } + deferreds.awaitAll().flatten().aggregate() } \ No newline at end of file diff --git a/src/tasks/Request5NotCancellable.kt b/src/tasks/Request5NotCancellable.kt index eb41acd..87bac9a 100644 --- a/src/tasks/Request5NotCancellable.kt +++ b/src/tasks/Request5NotCancellable.kt @@ -5,5 +5,19 @@ import kotlinx.coroutines.* import kotlin.coroutines.coroutineContext suspend fun loadContributorsNotCancellable(service: GitHubService, req: RequestData): List { - TODO() + val repos = service + .getOrgRepos(req.org) + .also { logRepos(req, it) } + .bodyList() + + val deferreds: List>> = repos.map { repo -> + GlobalScope.async(Dispatchers.Default) { + log("starting loading for ${repo.name}") + delay(3000) + service.getRepoContributors(req.org, repo.name) + .also { logUsers(repo, it) } + .bodyList() + } + } + return deferreds.awaitAll().flatten().aggregate() } \ No newline at end of file diff --git a/src/tasks/Request6Progress.kt b/src/tasks/Request6Progress.kt index 2d484db..bd6b7e3 100644 --- a/src/tasks/Request6Progress.kt +++ b/src/tasks/Request6Progress.kt @@ -7,5 +7,18 @@ suspend fun loadContributorsProgress( req: RequestData, updateResults: suspend (List, completed: Boolean) -> Unit ) { - TODO() + val repos = service + .getOrgRepos(req.org) + .also { logRepos(req, it) } + .bodyList() + + var allUsers = emptyList() + for ((index, repo) in repos.withIndex()) { + val users = service.getRepoContributors(req.org, repo.name) + .also { logUsers(repo, it) } + .bodyList() + + allUsers = (allUsers + users).aggregate() + updateResults(allUsers, index == repos.lastIndex) + } } diff --git a/src/tasks/Request7Channels.kt b/src/tasks/Request7Channels.kt index be93b16..f89b734 100644 --- a/src/tasks/Request7Channels.kt +++ b/src/tasks/Request7Channels.kt @@ -9,8 +9,25 @@ suspend fun loadContributorsChannels( service: GitHubService, req: RequestData, updateResults: suspend (List, completed: Boolean) -> Unit -) { - coroutineScope { - TODO() +) = coroutineScope { + val repos = service + .getOrgRepos(req.org) + .also { logRepos(req, it) } + .bodyList() + + val channel = Channel>() + for (repo in repos) { + launch { + val users = service.getRepoContributors(req.org, repo.name) + .also { logUsers(repo, it) } + .bodyList() + channel.send(users) + } + } + var allUsers = emptyList() + repeat(repos.size) { + val users = channel.receive() + allUsers = (allUsers + users).aggregate() + updateResults(allUsers, it == repos.lastIndex) } } diff --git a/test/contributors/MockGithubService.kt b/test/contributors/MockGithubService.kt index 7542eab..43a3199 100644 --- a/test/contributors/MockGithubService.kt +++ b/test/contributors/MockGithubService.kt @@ -14,9 +14,6 @@ object MockGithubService : GitHubService { return Calls.response(reposMap.getValue(repo).users) } -/* - // Uncomment the following implementations after adding these methods to GitHubService: - override suspend fun getOrgRepos(org: String): Response> { delay(reposDelay) return Response.success(repos) @@ -27,5 +24,4 @@ object MockGithubService : GitHubService { delay(testRepo.delay) return Response.success(testRepo.users) } -*/ } \ No newline at end of file From 93add76c31512c0170a0a896f48802b4ca88d3e5 Mon Sep 17 00:00:00 2001 From: Svetlana Isakova Date: Thu, 6 Jun 2019 12:17:14 +0200 Subject: [PATCH 2/3] Added solutions for 'testing' --- test/tasks/Request4SuspendKtTest.kt | 22 ++++++++-------------- test/tasks/Request5ConcurrentKtTest.kt | 18 ++++++------------ test/tasks/Request6ProgressKtTest.kt | 13 ++++++------- test/tasks/Request7ChannelsKtTest.kt | 15 +++++++-------- 4 files changed, 27 insertions(+), 41 deletions(-) diff --git a/test/tasks/Request4SuspendKtTest.kt b/test/tasks/Request4SuspendKtTest.kt index af92edb..a343a5c 100644 --- a/test/tasks/Request4SuspendKtTest.kt +++ b/test/tasks/Request4SuspendKtTest.kt @@ -3,29 +3,23 @@ package tasks import contributors.MockGithubService import contributors.expectedResults import contributors.testRequestData -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runBlockingTest import org.junit.Assert import org.junit.Test +@UseExperimental(ExperimentalCoroutinesApi::class) class Request4SuspendKtTest { @Test - fun testSuspend() = runBlocking { - val startTime = System.currentTimeMillis() + fun testSuspend() = runBlockingTest { + val startTime = currentTime val result = loadContributorsSuspend(MockGithubService, testRequestData) Assert.assertEquals("Wrong result for 'loadContributorsSuspend'", expectedResults.users, result) - val totalTime = System.currentTimeMillis() - startTime - /* - // TODO: uncomment this assertion + val totalTime = currentTime - startTime Assert.assertEquals( - "The calls run consequently, so the total virtual time should be 4000 ms: " + - "1000 for repos request plus (1000 + 1200 + 800) = 3000 for sequential contributors requests)", + "The calls run consequently," + + "so the total virtual time should be 4000 ms: ", expectedResults.timeFromStart, totalTime ) - */ - Assert.assertTrue( - "The calls run consequently, so the total time should be around 4000 ms: " + - "1000 for repos request plus (1000 + 1200 + 800) = 3000 for sequential contributors requests)", - totalTime in expectedResults.timeFromStart..(expectedResults.timeFromStart + 500) - ) } } \ No newline at end of file diff --git a/test/tasks/Request5ConcurrentKtTest.kt b/test/tasks/Request5ConcurrentKtTest.kt index 24c7e94..82d0846 100644 --- a/test/tasks/Request5ConcurrentKtTest.kt +++ b/test/tasks/Request5ConcurrentKtTest.kt @@ -3,29 +3,23 @@ package tasks import contributors.MockGithubService import contributors.expectedConcurrentResults import contributors.testRequestData -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runBlockingTest import org.junit.Assert import org.junit.Test +@UseExperimental(ExperimentalCoroutinesApi::class) class Request5ConcurrentKtTest { @Test - fun testConcurrent() = runBlocking { - val startTime = System.currentTimeMillis() + fun testConcurrent() = runBlockingTest { + val startTime = currentTime val result = loadContributorsConcurrent(MockGithubService, testRequestData) Assert.assertEquals("Wrong result for 'loadContributorsConcurrent'", expectedConcurrentResults.users, result) - val totalTime = System.currentTimeMillis() - startTime - /* - // TODO: uncomment this assertion + val totalTime = currentTime - startTime Assert.assertEquals( "The calls run concurrently, so the total virtual time should be 2200 ms: " + "1000 ms for repos request plus max(1000, 1200, 800) = 1200 ms for concurrent contributors requests)", expectedConcurrentResults.timeFromStart, totalTime ) - */ - Assert.assertTrue( - "The calls run concurrently, so the total virtual time should be 2200 ms: " + - "1000 ms for repos request plus max(1000, 1200, 800) = 1200 ms for concurrent contributors requests)", - totalTime in expectedConcurrentResults.timeFromStart..(expectedConcurrentResults.timeFromStart + 500) - ) } } \ No newline at end of file diff --git a/test/tasks/Request6ProgressKtTest.kt b/test/tasks/Request6ProgressKtTest.kt index 4c9e441..a8da26d 100644 --- a/test/tasks/Request6ProgressKtTest.kt +++ b/test/tasks/Request6ProgressKtTest.kt @@ -3,24 +3,23 @@ package tasks import contributors.MockGithubService import contributors.progressResults import contributors.testRequestData -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runBlockingTest import org.junit.Assert import org.junit.Test +@UseExperimental(ExperimentalCoroutinesApi::class) class Request6ProgressKtTest { @Test - fun testProgress() = runBlocking { - val startTime = System.currentTimeMillis() + fun testProgress() = runBlockingTest { + val startTime = currentTime var index = 0 loadContributorsProgress(MockGithubService, testRequestData) { users, _ -> val expected = progressResults[index++] - val time = System.currentTimeMillis() - startTime - /* - // TODO: uncomment this assertion + val time = currentTime - startTime Assert.assertEquals("Expected intermediate result after virtual ${expected.timeFromStart} ms:", expected.timeFromStart, time) - */ Assert.assertEquals("Wrong intermediate result after $time:", expected.users, users) } } diff --git a/test/tasks/Request7ChannelsKtTest.kt b/test/tasks/Request7ChannelsKtTest.kt index d82d26a..628dc8e 100644 --- a/test/tasks/Request7ChannelsKtTest.kt +++ b/test/tasks/Request7ChannelsKtTest.kt @@ -3,24 +3,23 @@ package tasks import contributors.MockGithubService import contributors.concurrentProgressResults import contributors.testRequestData -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runBlockingTest import org.junit.Assert import org.junit.Test +@UseExperimental(ExperimentalCoroutinesApi::class) class Request7ChannelsKtTest { @Test - fun testChannels() = runBlocking { - val startTime = System.currentTimeMillis() + fun testChannels() = runBlockingTest { + val startTime = currentTime var index = 0 loadContributorsChannels(MockGithubService, testRequestData) { users, _ -> val expected = concurrentProgressResults[index++] - val time = System.currentTimeMillis() - startTime - /* - // TODO: uncomment this assertion + val time = currentTime - startTime Assert.assertEquals("Expected intermediate result after virtual ${expected.timeFromStart} ms:", - expected.timeFromStart, virtualTime) - */ + expected.timeFromStart, time) Assert.assertEquals("Wrong intermediate result after $time:", expected.users, users) } } From a50f00e2c0573cfc7b4096f104d28828d78b6f0b Mon Sep 17 00:00:00 2001 From: Lukasz Kalnik Date: Wed, 5 Feb 2020 17:28:00 +0100 Subject: [PATCH 3/3] More idiomatic user aggregation --- src/tasks/Aggregation.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tasks/Aggregation.kt b/src/tasks/Aggregation.kt index 145b344..479f627 100644 --- a/src/tasks/Aggregation.kt +++ b/src/tasks/Aggregation.kt @@ -15,6 +15,6 @@ TODO: Write aggregation code. You can use 'Navigate | Test' menu action (note the shortcut) to navigate to the test. */ fun List.aggregate(): List = - groupBy { it.login } - .map { (login, group) -> User(login, group.sumBy { it.contributions }) } + groupBy({ it.login }, { it.contributions }) + .map { (login, contributionsList) -> User(login, contributionsList.sum()) } .sortedByDescending { it.contributions } \ No newline at end of file