diff --git a/src/contributors/GitHubService.kt b/src/contributors/GitHubService.kt index 3dc8b7c..8157082 100644 --- a/src/contributors/GitHubService.kt +++ b/src/contributors/GitHubService.kt @@ -25,6 +25,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> } @Serializable 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 diff --git a/test/tasks/AggregationKtTest.kt b/test/tasks/AggregationKtTest.kt index d9759e3..ea3baeb 100644 --- a/test/tasks/AggregationKtTest.kt +++ b/test/tasks/AggregationKtTest.kt @@ -10,9 +10,11 @@ class AggregationKtTest { val actual = listOf( User("Alice", 1), User("Bob", 3), User("Alice", 2), User("Bob", 7), + User("Beth", 3), User("Beth", 7), User("Charlie", 3), User("Alice", 5) ).aggregate() val expected = listOf( + User("Beth", 10), User("Bob", 10), User("Alice", 8), User("Charlie", 3) diff --git a/test/tasks/Request4SuspendKtTest.kt b/test/tasks/Request4SuspendKtTest.kt index af92edb..47515ad 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 +@OptIn(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..6f1933b 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 +@OptIn(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..4d9f446 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 +@OptIn(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 f377e85..d054f7f 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 +@OptIn(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, time) - */ Assert.assertEquals("Wrong intermediate result after $time:", expected.users, users) } }