Best Kotest code snippet using io.kotest.property.exhaustive.collections.Exhaustive.Companion.collection
WhenDownloadingAwsDeviceFarmArtifacts.kt
Source:WhenDownloadingAwsDeviceFarmArtifacts.kt  
1package io.github.ricardorlg.devicefarm.tractor.controller2import arrow.core.Either3import io.github.ricardorlg.devicefarm.tractor.model.*4import io.github.ricardorlg.devicefarm.tractor.stubs.*5import io.github.ricardorlg.devicefarm.tractor.tempFolder6import io.github.ricardorlg.devicefarm.tractor.utils.prettyName7import io.kotest.assertions.arrow.core.shouldBeLeft8import io.kotest.assertions.arrow.core.shouldBeRight9import io.kotest.assertions.fail10import io.kotest.assertions.withClue11import io.kotest.core.spec.style.StringSpec12import io.kotest.engine.spec.tempfile13import io.kotest.extensions.system.captureStandardOut14import io.kotest.inspectors.forAll15import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder16import io.kotest.matchers.paths.shouldBeADirectory17import io.kotest.matchers.paths.shouldContainFile18import io.kotest.matchers.paths.shouldContainNFiles19import io.kotest.matchers.paths.shouldNotContainFile20import io.kotest.matchers.should21import io.kotest.matchers.shouldBe22import io.kotest.matchers.string.shouldStartWith23import io.kotest.matchers.throwable.shouldHaveMessage24import io.kotest.matchers.types.shouldBeInstanceOf25import io.kotest.property.Exhaustive26import io.kotest.property.checkAll27import io.kotest.property.exhaustive.collection28import software.amazon.awssdk.services.devicefarm.model.Artifact29import software.amazon.awssdk.services.devicefarm.model.ArtifactType30import software.amazon.awssdk.services.devicefarm.model.Job31import software.amazon.awssdk.services.devicefarm.model.Run32import java.nio.file.AccessDeniedException33import java.nio.file.Paths34import java.nio.file.attribute.PosixFilePermissions35import kotlin.io.path.createDirectory36import kotlin.io.path.listDirectoryEntries37import kotlin.time.Duration38import kotlin.time.Duration.Companion.milliseconds39class WhenDownloadingAwsDeviceFarmArtifacts : StringSpec({40    val logger = MockedDeviceFarmLogging()41    val deviceFarmProjectsHandler = MockedDeviceFarmProjectsHandler()42    val devicePoolsHandler = MockedDeviceFarmDevicePoolsHandler()43    val uploadArtifactsHandler = MockedDeviceFarmUploadArtifactsHandler()44    val runScheduleHandler = MockedDeviceFarmRunsHandler()45    val commonArtifactsHandler = MockedDeviceFarmArtifactsHandler()46    val deviceName = "nexus 3"47    val artifactType = ArtifactType.CUSTOMER_ARTIFACT48    val downloadableTypes = listOf(ArtifactType.CUSTOMER_ARTIFACT, ArtifactType.VIDEO)49    val validTypes =50        ArtifactType.values().filter { it != ArtifactType.UNKNOWN && it != ArtifactType.UNKNOWN_TO_SDK_VERSION }51    "It should use a pretty name when the artifact type is video or customer artifact"{52        checkAll(Exhaustive.collection(ArtifactType.values().asList())) { type ->53            when (type) {54                ArtifactType.VIDEO -> type.prettyName() shouldBe "Recorded video"55                ArtifactType.CUSTOMER_ARTIFACT -> type.prettyName() shouldBe "Test reports"56                else -> type.prettyName() shouldBe type.name57            }58        }59    }60    "It should return a DeviceFarmTractorErrorIllegalArgumentException if the searched artifact type is not supported"{61        checkAll(Exhaustive.collection(INVALID_ARTIFACT_TYPES)) { type ->62            //GIVEN63            val customerArtifact = tempfile("test_downloadable_${type.name.lowercase()}_", ".zip")64            val destinyFolder = tempFolder("testReports")65            val artifact = Artifact66                .builder()67                .arn("arn:test:artifact")68                .name(customerArtifact.nameWithoutExtension)69                .extension(customerArtifact.extension)70                .type(artifactType)71                .url(customerArtifact.toURI().toASCIIString())72                .build()73            //WHEN74            val response = DefaultDeviceFarmTractorController(75                logger,76                deviceFarmProjectsHandler,77                devicePoolsHandler,78                uploadArtifactsHandler,79                runScheduleHandler,80                commonArtifactsHandler81            ).downloadAWSDeviceFarmArtifacts(82                artifacts = listOf(artifact),83                deviceName = deviceName,84                path = destinyFolder,85                artifactType = type86            )87            //THEN88            response.shouldBeLeft() should {89                it.shouldBeInstanceOf<DeviceFarmTractorErrorIllegalArgumentException>()90                it shouldHaveMessage "$type is not supported"91            }92            destinyFolder shouldNotContainFile customerArtifact.name93            destinyFolder shouldContainNFiles 094        }95    }96    "It should download an AWS Device farm artifact of a job execution depending of its type"{97        checkAll(Exhaustive.collection(validTypes)) { type ->98            //GIVEN99            val customerArtifact = tempfile("test_downloadable_${type.name.lowercase()}_", ".zip")100            val destinyFolder = tempFolder("testReports")101            val artifact = Artifact102                .builder()103                .arn("arn:test:artifact")104                .name(customerArtifact.nameWithoutExtension)105                .extension(customerArtifact.extension)106                .type(type)107                .url(customerArtifact.toURI().toASCIIString())108                .build()109            //WHEN110            val response = DefaultDeviceFarmTractorController(111                logger,112                deviceFarmProjectsHandler,113                devicePoolsHandler,114                uploadArtifactsHandler,115                runScheduleHandler,116                commonArtifactsHandler117            ).downloadAWSDeviceFarmArtifacts(118                artifacts = listOf(artifact),119                deviceName = deviceName,120                path = destinyFolder,121                artifactType = type122            )123            //THEN124            response.shouldBeRight()125            destinyFolder shouldContainFile customerArtifact.name126            destinyFolder shouldContainNFiles 1127        }128    }129    "It should log a message when the searched artifact type is not found in the job artifacts"{130        checkAll(Exhaustive.collection(validTypes)) { type ->131            //GIVEN132            val customerArtifact = tempfile("test_downloadable_${type.name.lowercase()}_", ".zip")133            val destinyFolder = tempFolder("testReports")134            val artifact = Artifact135                .builder()136                .arn("arn:test:artifact")137                .name(customerArtifact.nameWithoutExtension)138                .extension(customerArtifact.extension)139                .type(ArtifactType.UNKNOWN)140                .url(customerArtifact.toURI().toASCIIString())141                .build()142            val expectedLoggedMessage = JOB_DOES_NOT_HAVE_ARTIFACT_OF_TYPE.format(143                type.name,144                deviceName145            )146            //WHEN147            val loggedMessage = captureStandardOut {148                DefaultDeviceFarmTractorController(149                    MockedDeviceFarmLogging(true),150                    deviceFarmProjectsHandler,151                    devicePoolsHandler,152                    uploadArtifactsHandler,153                    runScheduleHandler,154                    commonArtifactsHandler155                ).downloadAWSDeviceFarmArtifacts(156                    artifacts = listOf(artifact),157                    deviceName = deviceName,158                    path = destinyFolder,159                    artifactType = type160                ).shouldBeRight()161            }.lines()162                .filter(String::isNotBlank)163                .map(String::trim)164                .last()165            //THEN166            loggedMessage shouldBe expectedLoggedMessage167            destinyFolder shouldNotContainFile customerArtifact.name168            destinyFolder shouldContainNFiles 0169        }170    }171    "It should return an ErrorDownloadingArtifact when there is a problem saving the artifact on disk"{172        //GIVEN173        val onlyReadDestinyFolderPermission = PosixFilePermissions.fromString("r--r--r--")174        val customerArtifact = tempfile("test_downloadable", ".zip")175        val destinyFolder =176            tempFolder("testReports", PosixFilePermissions.asFileAttribute(onlyReadDestinyFolderPermission))177        val artifact = Artifact178            .builder()179            .arn("arn:test:artifact")180            .name(customerArtifact.nameWithoutExtension)181            .extension(customerArtifact.extension)182            .type(ArtifactType.CUSTOMER_ARTIFACT)183            .url(customerArtifact.toURI().toASCIIString())184            .build()185        //WHEN186        val response = DefaultDeviceFarmTractorController(187            logger,188            deviceFarmProjectsHandler,189            devicePoolsHandler,190            uploadArtifactsHandler,191            runScheduleHandler,192            commonArtifactsHandler193        ).downloadAWSDeviceFarmArtifacts(194            artifacts = listOf(artifact),195            deviceName = deviceName,196            path = destinyFolder,197            artifactType = artifactType198        )199        //THEN200        response.shouldBeLeft() should {201            it.shouldBeInstanceOf<ErrorDownloadingArtifact>()202            it.cause.shouldBeInstanceOf<AccessDeniedException>()203        }204    }205    "It should not fail if there is no artifacts to download"{206        //GIVEN207        val customerArtifact = tempfile("test_downloadable", ".zip")208        val destinyFolder = tempFolder("testReports")209        //WHEN210        val response = DefaultDeviceFarmTractorController(211            logger,212            deviceFarmProjectsHandler,213            devicePoolsHandler,214            uploadArtifactsHandler,215            runScheduleHandler,216            commonArtifactsHandler217        ).downloadAWSDeviceFarmArtifacts(218            artifacts = emptyList(),219            deviceName = deviceName,220            path = destinyFolder,221            artifactType = artifactType222        )223        //THEN224        response.shouldBeRight()225        destinyFolder shouldNotContainFile customerArtifact.name226        destinyFolder shouldContainNFiles 0227    }228    "It should download all the test reports and recorded videos associated to the test Run"{229        //GIVEN230        val customerArtifactFile = tempfile("test_downloadable", ".zip")231        val recordedVideoFile = tempfile("test_video", ".mp4")232        val destinyFolder = tempFolder("testReports")233        val run = Run234            .builder()235            .name("test run")236            .arn("arn:test:run")237            .build()238        val jobs = (1..10)239            .map { job ->240                Job241                    .builder()242                    .arn("arn:test:job:$job")243                    .name("test job $job")244                    .device {245                        it.name("Test device $job")246                            .arn("arn:test:device:$job")247                    }.build()248            }249        val customerArtifact = Artifact250            .builder()251            .arn("arn:test:customer_artifact")252            .name(customerArtifactFile.nameWithoutExtension)253            .extension(customerArtifactFile.extension)254            .type(ArtifactType.CUSTOMER_ARTIFACT)255            .url(customerArtifactFile.toURI().toASCIIString())256            .build()257        val recordedVideoArtifact = Artifact258            .builder()259            .arn("arn:test:video_artifact")260            .name(recordedVideoFile.nameWithoutExtension)261            .extension(recordedVideoFile.extension)262            .type(ArtifactType.VIDEO)263            .url(recordedVideoFile.toURI().toASCIIString())264            .build()265        val downloadArtifactsHandler = MockedDeviceFarmArtifactsHandler(266            getArtifactsImpl = { Either.Right(listOf(customerArtifact, recordedVideoArtifact)) }267        )268        val runHandler = MockedDeviceFarmRunsHandler(269            getAssociatedJobsImpl = { Either.Right(jobs) }270        )271        val reportDirectoryPath = Paths.get("test_reports_${run.name().lowercase().replace("\\s".toRegex(), "_")}")272        //WHEN273        DefaultDeviceFarmTractorController(274            logger,275            deviceFarmProjectsHandler,276            devicePoolsHandler,277            uploadArtifactsHandler,278            runHandler,279            downloadArtifactsHandler280        ).downloadAllEvidencesOfTestRun(run, destinyFolder, 0.milliseconds)281        //THEN282        destinyFolder shouldContainFile reportDirectoryPath.toFile().name283        destinyFolder shouldContainNFiles 1284        destinyFolder.resolve(reportDirectoryPath).shouldBeADirectory()285        destinyFolder.resolve(reportDirectoryPath) shouldContainNFiles jobs.size286        destinyFolder.resolve(reportDirectoryPath).listDirectoryEntries().forAll {287            it.shouldBeADirectory()288            it shouldContainFile customerArtifactFile.name289            it shouldContainFile recordedVideoFile.name290            it shouldContainNFiles 2291        }292    }293    "It should log an error message when downloading a recorded video or test report fails"{294        checkAll(Exhaustive.collection(downloadableTypes)) { type ->295            //GIVEN296            val testFile = tempfile("test_downloadable_${type.name.lowercase()}", ".zip")297            val destinyFolder = tempFolder("testReports")298            val run = Run299                .builder()300                .name("test run")301                .arn("arn:test:run")302                .build()303            val job = Job304                .builder()305                .arn("arn:test:job")306                .name("test job")307                .device {308                    it.name(deviceName)309                        .arn("arn:test:device")310                }.build()311            val artifact = Artifact312                .builder()313                .arn("arn:test:artifact")314                .name(testFile.nameWithoutExtension)315                .extension(testFile.extension)316                .type(type)317                .url(testFile.toURI().toASCIIString())318                .build()319            val downloadArtifactsHandler = MockedDeviceFarmArtifactsHandler(320                getArtifactsImpl = { Either.Right(listOf(artifact)) }321            )322            val runHandler = MockedDeviceFarmRunsHandler(323                getAssociatedJobsImpl = { Either.Right(listOf(job)) }324            )325            val reportDirectoryPath =326                Paths.get("test_reports_${run.name().lowercase().replace("\\s".toRegex(), "_")}")327            testFile.setReadable(false)328            //WHEN329            val loggedMessages = captureStandardOut {330                DefaultDeviceFarmTractorController(331                    MockedDeviceFarmLogging(true),332                    deviceFarmProjectsHandler,333                    devicePoolsHandler,334                    uploadArtifactsHandler,335                    runHandler,336                    downloadArtifactsHandler337                ).downloadAllEvidencesOfTestRun(run, destinyFolder, 0.milliseconds)338            }.lineSequence()339                .filter(String::isNotBlank)340                .map(String::trim)341            //THEN342            destinyFolder shouldContainNFiles 1343            destinyFolder shouldContainFile reportDirectoryPath.fileName.toString()344            destinyFolder.resolve(reportDirectoryPath).shouldBeADirectory()345            destinyFolder.resolve(reportDirectoryPath) shouldContainNFiles 0346            withClue("The logged messages should contain an error message related to the error downloading the artifact $type") {347                loggedMessages.any {348                    it.startsWith(349                        "There was an error downloading the ${350                            type.prettyName()351                        } of $deviceName test run."352                    )353                }354            }355        }356    }357    "It should download all the reports even if any of them fails"{358        //GIVEN359        val expectedReports = (1..10)360            .map {361                tempfile("test_report_${it}_downloadable", ".zip")362            }363        val reportNotReadable = expectedReports.random()364        if (!reportNotReadable.setReadable(false)) fail("An error happens setting up the test")365        val destinyFolder = tempFolder("testReports")366        val run = Run367            .builder()368            .name("test run")369            .arn("arn:test:run")370            .build()371        val jobs = (1..10)372            .map { job ->373                Job374                    .builder()375                    .arn("arn:test:job:$job")376                    .name("test job $job")377                    .device {378                        it.name("Test device $job")379                            .arn("arn:test:device:$job")380                    }.build()381            }382        val artifacts = expectedReports383            .mapIndexed { index, associatedReport ->384                Artifact385                    .builder()386                    .arn("arn:test:artifact:$index")387                    .name(associatedReport.nameWithoutExtension)388                    .extension(associatedReport.extension)389                    .type(ArtifactType.CUSTOMER_ARTIFACT)390                    .url(associatedReport.toURI().toASCIIString())391                    .build()392            }393        val artifactsProvider = artifacts.iterator()394        val downloadArtifactsHandler = MockedDeviceFarmArtifactsHandler(395            getArtifactsImpl = {396                synchronized(this) {397                    Either.Right(listOf(artifactsProvider.next()))398                }399            }400        )401        val runHandler = MockedDeviceFarmRunsHandler(402            getAssociatedJobsImpl = { Either.Right(jobs) }403        )404        val reportDirectoryPath = Paths.get("test_reports_${run.name().lowercase().replace("\\s".toRegex(), "_")}")405        //WHEN406        DefaultDeviceFarmTractorController(407            logger,408            deviceFarmProjectsHandler,409            devicePoolsHandler,410            uploadArtifactsHandler,411            runHandler,412            downloadArtifactsHandler413        ).downloadAllEvidencesOfTestRun(run, destinyFolder, 0.milliseconds)414        //THEN415        destinyFolder shouldContainFile reportDirectoryPath.fileName.toString()416        destinyFolder shouldContainNFiles 1417        destinyFolder.resolve(reportDirectoryPath).shouldBeADirectory()418        destinyFolder.resolve(reportDirectoryPath) shouldContainNFiles jobs.size - 1419        destinyFolder420            .resolve(reportDirectoryPath)421            .listDirectoryEntries()422            .flatMap {423                it.listDirectoryEntries()424            }425            .map { it.fileName }426            .shouldContainExactlyInAnyOrder(427                expectedReports428                    .filter { it != reportNotReadable }429                    .map { it.toPath().fileName }430            )431    }432    "It should download all the recorded videos even if any of them fails"{433        //GIVEN434        val expectedRecordedVideos = (1..10)435            .map {436                tempfile("recorde_video_${it}_downloadable", ".mp4")437            }438        val recordedVideoNotReadable = expectedRecordedVideos.random()439        if (!recordedVideoNotReadable.setReadable(false)) fail("An error happens setting up the test")440        val destinyFolder = tempFolder("testResults")441        val run = Run442            .builder()443            .name("test run")444            .arn("arn:test:run")445            .build()446        val jobs = (1..10)447            .map { job ->448                Job449                    .builder()450                    .arn("arn:test:job:$job")451                    .name("test job $job")452                    .device {453                        it.name("Test device $job")454                            .arn("arn:test:device:$job")455                    }.build()456            }457        val artifacts = expectedRecordedVideos458            .mapIndexed { index, associatedVideo ->459                Artifact460                    .builder()461                    .arn("arn:test:artifact:$index")462                    .name(associatedVideo.nameWithoutExtension)463                    .extension(associatedVideo.extension)464                    .type(ArtifactType.VIDEO)465                    .url(associatedVideo.toURI().toASCIIString())466                    .build()467            }468        val artifactsProvider = artifacts.iterator()469        val downloadArtifactsHandler = MockedDeviceFarmArtifactsHandler(470            getArtifactsImpl = {471                synchronized(this) {472                    Either.Right(listOf(artifactsProvider.next()))473                }474            }475        )476        val runHandler = MockedDeviceFarmRunsHandler(477            getAssociatedJobsImpl = { Either.Right(jobs) }478        )479        val reportDirectoryPath = Paths.get("test_reports_${run.name().lowercase().replace("\\s".toRegex(), "_")}")480        //WHEN481        DefaultDeviceFarmTractorController(482            logger,483            deviceFarmProjectsHandler,484            devicePoolsHandler,485            uploadArtifactsHandler,486            runHandler,487            downloadArtifactsHandler488        ).downloadAllEvidencesOfTestRun(run, destinyFolder, 0.milliseconds)489        //THEN490        destinyFolder shouldContainFile reportDirectoryPath.fileName.toString()491        destinyFolder shouldContainNFiles 1492        destinyFolder.resolve(reportDirectoryPath).shouldBeADirectory()493        destinyFolder.resolve(reportDirectoryPath) shouldContainNFiles jobs.size - 1494        destinyFolder495            .resolve(reportDirectoryPath)496            .listDirectoryEntries()497            .flatMap {498                it.listDirectoryEntries()499            }500            .map { it.fileName }501            .shouldContainExactlyInAnyOrder(502                expectedRecordedVideos503                    .filter { it != recordedVideoNotReadable }504                    .map { it.toPath().fileName }505            )506    }507    "It should log an error message when creating the test report directory of an specific device fails"{508        //GIVEN509        val destinyFolder = tempFolder("testReports")510        val run = Run511            .builder()512            .name("test run")513            .arn("arn:test:run")514            .build()515        val job = Job516            .builder()517            .arn("arn:test:job")518            .name("test job")519            .device {520                it.name(deviceName)521                    .arn("arn:test:device")522            }.build()523        val downloadArtifactsHandler = MockedDeviceFarmArtifactsHandler(524            getArtifactsImpl = { fail("This should never been called") }525        )526        val runHandler = MockedDeviceFarmRunsHandler(527            getAssociatedJobsImpl = { Either.Right(listOf(job)) }528        )529        val reportDirectoryPath = destinyFolder530            .resolve("test_reports_${run.name().lowercase().replace("\\s".toRegex(), "_")}")531            .createDirectory()532        //WHEN533        val lastOutput = captureStandardOut {534            DefaultDeviceFarmTractorController(535                MockedDeviceFarmLogging(true),536                deviceFarmProjectsHandler,537                devicePoolsHandler,538                uploadArtifactsHandler,539                runHandler,540                downloadArtifactsHandler541            ).downloadAllEvidencesOfTestRun(run, destinyFolder, 0.milliseconds)542        }.lineSequence()543            .filter(String::isNotBlank)544            .map(String::trim)545            .last()546        //THEN547        reportDirectoryPath shouldContainNFiles 0548        lastOutput shouldStartWith "There was a problem creating the folder ${reportDirectoryPath.fileName}"549    }550    "It should log an error message when creating the test reports directory fails"{551        //GIVEN552        val destinyFolder = tempFolder("testReports")553        val run = Run554            .builder()555            .name("test run")556            .arn("arn:test:run")557            .build()558        val job = Job559            .builder()560            .arn("arn:test:job")561            .name("test job")562            .device {563                it.name(deviceName)564                    .arn("arn:test:device")565            }.build()566        val downloadArtifactsHandler = MockedDeviceFarmArtifactsHandler(567            getArtifactsImpl = { fail("This should never been called") }568        )569        val runHandler = MockedDeviceFarmRunsHandler(570            getAssociatedJobsImpl = { Either.Right(listOf(job)) }571        )572        val testReportsName = "test_reports_${run.name().lowercase().replace("\\s".toRegex(), "_")}"573        destinyFolder.toFile().setReadOnly()574        //WHEN575        val lastOutput = captureStandardOut {576            DefaultDeviceFarmTractorController(577                MockedDeviceFarmLogging(true),578                deviceFarmProjectsHandler,579                devicePoolsHandler,580                uploadArtifactsHandler,581                runHandler,582                downloadArtifactsHandler583            ).downloadAllEvidencesOfTestRun(run, destinyFolder, 0.milliseconds)584        }.lineSequence()585            .filter(String::isNotBlank)586            .map(String::trim)587            .last()588        //THEN589        destinyFolder shouldContainNFiles 0590        lastOutput shouldStartWith "There was a problem creating the folder $testReportsName"591    }592    "It should not try to download the reports when an error happens fetching the associated jobs of the test run"{593        //GIVEN594        val destinyFolder = tempFolder("testReports")595        val error = DeviceFarmTractorGeneralError(RuntimeException("test error"))596        val run = Run597            .builder()598            .name("test run")599            .arn("arn:test:run")600            .build()601        val downloadArtifactsHandler = MockedDeviceFarmArtifactsHandler(602            getArtifactsImpl = { fail("This should never been called") }603        )604        val runHandler = MockedDeviceFarmRunsHandler(605            getAssociatedJobsImpl = { Either.Left(error) }606        )607        //WHEN608        DefaultDeviceFarmTractorController(609            logger,610            deviceFarmProjectsHandler,611            devicePoolsHandler,612            uploadArtifactsHandler,613            runHandler,614            downloadArtifactsHandler615        ).downloadAllEvidencesOfTestRun(run, destinyFolder, 0.milliseconds)616        //THEN617        destinyFolder shouldContainNFiles 0618    }619    "It should download the recorded videos and test reports even if any of them fails"{620        //GIVEN621        val expectedReports = (1..10)622            .map {623                tempfile("test_report_${it}_downloadable", ".zip")624            }625        val expectedRecordedVideos = (1..10)626            .map {627                tempfile("test_video_${it}_downloadable", ".mp4")628            }629        val reportNotReadable = expectedReports.random()630        val videoNotReadable = expectedRecordedVideos.random()631        if (!reportNotReadable.setReadable(false)) fail("An error happens setting up the test")632        if (!videoNotReadable.setReadable(false)) fail("An error happens setting up the test")633        val destinyFolder = tempFolder("testReports")634        val run = Run635            .builder()636            .name("test run")637            .arn("arn:test:run")638            .build()639        val jobs = (1..10)640            .map { job ->641                Job642                    .builder()643                    .arn("arn:test:job:$job")644                    .name("test job $job")645                    .device {646                        it.name("Test device $job")647                            .arn("arn:test:device:$job")648                    }.build()649            }650        val customerArtifacts = expectedReports651            .mapIndexed { index, associatedReport ->652                Artifact653                    .builder()654                    .arn("arn:test:customer_artifact:$index")655                    .name(associatedReport.nameWithoutExtension)656                    .extension(associatedReport.extension)657                    .type(ArtifactType.CUSTOMER_ARTIFACT)658                    .url(associatedReport.toURI().toASCIIString())659                    .build()660            }661        val videoArtifacts = expectedRecordedVideos662            .mapIndexed { index, associatedVideo ->663                Artifact664                    .builder()665                    .arn("arn:test:video_artifact:$index")666                    .name(associatedVideo.nameWithoutExtension)667                    .extension(associatedVideo.extension)668                    .type(ArtifactType.VIDEO)669                    .url(associatedVideo.toURI().toASCIIString())670                    .build()671            }672        val customerArtifactsProvider = customerArtifacts.iterator()673        val videoArtifactsProvider = videoArtifacts.iterator()674        val downloadArtifactsHandler = MockedDeviceFarmArtifactsHandler(675            getArtifactsImpl = {676                synchronized(this) {677                    Either.Right(listOf(customerArtifactsProvider.next(), videoArtifactsProvider.next()))678                }679            }680        )681        val runHandler = MockedDeviceFarmRunsHandler(682            getAssociatedJobsImpl = { Either.Right(jobs) }683        )684        val reportDirectoryPath = Paths.get("test_reports_${run.name().lowercase().replace("\\s".toRegex(), "_")}")685        val expectedFiles = expectedReports686            .filter { it != reportNotReadable }687            .map { it.toPath().fileName } + expectedRecordedVideos688            .filter { it != videoNotReadable }689            .map { it.toPath().fileName }690        val expectedFilesSize =691            if (expectedReports.indexOf(reportNotReadable) == expectedRecordedVideos.indexOf(videoNotReadable)) jobs.size - 1 else jobs.size692        //WHEN693        DefaultDeviceFarmTractorController(694            logger,695            deviceFarmProjectsHandler,696            devicePoolsHandler,697            uploadArtifactsHandler,698            runHandler,699            downloadArtifactsHandler700        ).downloadAllEvidencesOfTestRun(run, destinyFolder, 0.milliseconds)701        //THEN702        destinyFolder shouldContainFile reportDirectoryPath.fileName.toString()703        destinyFolder shouldContainNFiles 1704        destinyFolder.resolve(reportDirectoryPath).shouldBeADirectory()705        destinyFolder.resolve(reportDirectoryPath) shouldContainNFiles expectedFilesSize706        destinyFolder707            .resolve(reportDirectoryPath)708            .listDirectoryEntries()709            .flatMap {710                it.listDirectoryEntries()711            }712            .map { it.fileName }713            .shouldContainExactlyInAnyOrder(expectedFiles)714    }715})...WorldMapTest.kt
Source:WorldMapTest.kt  
1package de.gleex.pltcmd.model.world2import de.gleex.pltcmd.model.world.coordinate.Coordinate3import de.gleex.pltcmd.model.world.coordinate.CoordinatePath4import de.gleex.pltcmd.model.world.coordinate.c5import de.gleex.pltcmd.model.world.terrain.Terrain6import de.gleex.pltcmd.model.world.terrain.TerrainHeight.FIVE7import de.gleex.pltcmd.model.world.terrain.TerrainHeight.FOUR8import de.gleex.pltcmd.model.world.terrain.TerrainType.FOREST9import de.gleex.pltcmd.model.world.terrain.TerrainType.GRASSLAND10import de.gleex.pltcmd.model.world.testhelpers.randomSectorAt11import de.gleex.pltcmd.model.world.testhelpers.sectorAtWithTerrain12import de.gleex.pltcmd.util.measure.distance.times13import io.kotest.assertions.throwables.shouldThrow14import io.kotest.core.spec.style.WordSpec15import io.kotest.data.forAll16import io.kotest.data.row17import io.kotest.matchers.collections.shouldContainExactly18import io.kotest.matchers.collections.shouldHaveSize19import io.kotest.matchers.shouldBe20import io.kotest.matchers.string.shouldContain21import io.kotest.matchers.types.shouldBeSameInstanceAs22import io.kotest.property.Exhaustive23import io.kotest.property.checkAll24import io.kotest.property.exhaustive.collection25import mu.KLogging26import mu.KotlinLogging27import java.util.*28import kotlin.math.ceil29import kotlin.math.sqrt30import kotlin.system.measureTimeMillis31import kotlin.time.ExperimentalTime32import kotlin.time.measureTimedValue33private val log = KotlinLogging.logger {  }34@OptIn(ExperimentalTime::class)35class WorldMapTest : WordSpec({36    "A WorldMap" should {37        "not be empty" {38            shouldThrow<IllegalArgumentException> { WorldMap.create(setOf<WorldTile>().toSortedSet()) }39        }40        "be square when calculating its size" {41            forAll(42                    row(1, 1),43                    row(4, 2),44                    row(9, 3),45                    row(16, 4),46                    row(25, 5),47                    row(36, 6),48                    row(49, 7),49                    row(100, 10),50                    // TODO: This test should also work! Currently uses too much memory for the CI-job51                    // row(900, 30)52            ) { sectorCount, sideLengthInSectors ->53                val expectedEdgeLength = sideLengthInSectors * Sector.TILE_COUNT54                val sectors = sectorCount.sectors()55                sectors shouldHaveSize sectorCount * Sector.TILE_COUNT * Sector.TILE_COUNT56                val (map, duration) = measureTimedValue { WorldMap.create(sectors) }57                logger.info { "Creating a world with $sectorCount sectors took $duration" }58                map.width shouldBe expectedEdgeLength59                map.height shouldBe expectedEdgeLength60            }61        }62        "be invalid when not square" {63            val exception = shouldThrow<IllegalArgumentException> {64                WorldMap.create(3.sectors())65            }66            exception.message shouldContain "rectangle"67        }68        "be invalid when not fully connected" {69            val first = randomSectorAt(Coordinate(150, 200))70            val second = randomSectorAt(Coordinate(200, 250))71            shouldThrow<IllegalArgumentException> {72                WorldMap.create((first + second).toSortedSet())73            }74        }75        val origin = Coordinate(150, 200)76        val testSector = randomSectorAt(origin)77        val map = WorldMap.create(testSector)78        "coerce its coordinates to themselves" {79            val allCoordinates = testSector.map{ it.coordinate }80            checkAll(allCoordinates.size, Exhaustive.collection(allCoordinates)) { coordinate ->81                map.moveInside(coordinate) shouldBeSameInstanceAs coordinate82            }83        }84        "coerce outside coordinates to the border" {85            forAll(86                    row(origin.withRelativeEasting(-1), origin),87                    row(origin.withRelativeNorthing(-1), origin),88                    row(origin.movedBy(-1, -1), origin),89                    row(origin.movedBy(-123, -456), origin),90                    row(origin.movedBy(32, -3), origin.movedBy(32, 0)),91                    row(origin.movedBy(-2, 13), origin.movedBy(0, 13)),92                    row(map.last.movedBy(1, 0), map.last),93                    row(map.last.movedBy(0, 1), map.last),94                    row(map.last.movedBy(7, 13), map.last),95                    row(map.last.movedBy(-13, 3), map.last.movedBy(-13, 0)),96                    row(map.last.movedBy(3, -13), map.last.movedBy(0, -13))97            ) { position, expected ->98                map.moveInside(position) shouldBe expected99            }100        }101        "circleAt() should find all titles in the world" {102            // 3x3 sectors103            val s = Sector.TILE_COUNT104            val manySectors = setOf(105                origin.movedBy(0 * s, 0 * s), origin.movedBy(1 * s, 0 * s), origin.movedBy(2 * s, 0 * s),106                origin.movedBy(0 * s, 1 * s), origin.movedBy(1 * s, 1 * s), origin.movedBy(2 * s, 1 * s),107                origin.movedBy(0 * s, 2 * s), origin.movedBy(1 * s, 2 * s), origin.movedBy(2 * s, 2 * s)108            ).map { randomSectorAt(it) }.flatten().toSortedSet()109            val largeMap = WorldMap.create(manySectors)110            val center = origin.movedBy(123, 57)111            val allDurations = mutableListOf<Long>()112            forAll(113                row(10, 349),114                row(50, 6559),115                row(100, 17149),116                row(200, 22500),117                row(300, 22500)118            ) { radius, expected ->119                allDurations.clear()120                repeat(20) {121                    allDurations.add(122                        measureTimeMillis {123                            largeMap.circleAt(center, radius * WorldTile.edgeLength).size shouldBe expected124                        }125                    )126                }127                log.info { "Performance information for radius $radius" }128                log.info { allDurations.joinToString(", ", prefix = "Durations in ms:") }129                val average = allDurations.average()130                log.info { "Average of ${allDurations.size} durations: $average ms" }131            }132        }133    }134    "A coordinate path" should {135        val origin = Coordinate(0, 0)136        val testSector = sectorAtWithTerrain(origin) { coordinate ->137            if(coordinate.eastingFromLeft <= 10) {138                Terrain.of(FOREST, FIVE)139            } else {140                Terrain.of(GRASSLAND, FOUR)141            }142        }143        val map = WorldMap.create(testSector)144        "result in all the terrain when completely inside the map" {145            val path = CoordinatePath(listOf(146                c(8, 5),147                c(9, 5),148                c(10, 5),149                c(11, 5)150            ))151            val terrain = map[path]152            terrain shouldContainExactly listOf(153                Terrain.of(FOREST, FIVE),154                Terrain.of(FOREST, FIVE),155                Terrain.of(FOREST, FIVE),156                Terrain.of(GRASSLAND, FOUR),157            )158        }159        "stop at the world's edge" {160            val path = CoordinatePath(listOf(161                c(9, 2),162                c(10, 1),163                c(11, 0),164                c(12, -1),165                c(13, -2),166                c(14, -3)167            ))168            val terrain = map[path]169            terrain shouldContainExactly listOf(170                Terrain.of(FOREST, FIVE),171                Terrain.of(FOREST, FIVE),172                Terrain.of(GRASSLAND, FOUR)173            )174        }175        "stop at the world's edge even when it returns into the world" {176            val path = CoordinatePath(listOf(177                c(9, 2),178                c(10, 1),179                c(11, 0),180                c(12, -1),181                c(13, -2),182                c(15, -1),183                c(16, 0),184                c(17, 1),185                c(18, 2),186                c(19, 3)187            ))188            val terrain = map[path]189            terrain shouldContainExactly listOf(190                Terrain.of(FOREST, FIVE),191                Terrain.of(FOREST, FIVE),192                Terrain.of(GRASSLAND, FOUR)193            )194        }195    }196}) {197    companion object: KLogging()198}199/**200 * Creates this amount of sectors. The sectors are placed in a square. The square is filled line by line and only full201 * if the amount is a square number.202 **/203private fun Int.sectors(): SortedSet<WorldTile> {204    val sectors = mutableListOf<SortedSet<WorldTile>>()205    val width = ceil(sqrt(toDouble())).toInt()206    (0 until this).forEach { i ->207        val row = i / width208        val column = i - (row * width)209        sectors.add(randomSectorAt(Coordinate(row * Sector.TILE_COUNT, column * Sector.TILE_COUNT)))210    }211    return sectors.flatten().toSortedSet()212}...WeatherApiTest.kt
Source:WeatherApiTest.kt  
1package com.alessandrocandolini.splash2import com.alessandrocandolini.withMockServer3import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory4import io.kotest.core.spec.style.FunSpec5import io.kotest.matchers.collections.shouldBeIn6import io.kotest.property.Exhaustive7import io.kotest.property.Gen8import io.kotest.property.checkAll9import io.kotest.property.exhaustive.collection10import kotlinx.serialization.json.Json11import okhttp3.MediaType.Companion.toMediaType12import okhttp3.mockwebserver.Dispatcher13import okhttp3.mockwebserver.MockResponse14import okhttp3.mockwebserver.RecordedRequest15import retrofit2.Retrofit16import retrofit2.create17class WeatherApiTest : FunSpec() {18    init {19        test("WeatherApi can correctly return parsed responses when the server return 200 with valid json response body") {20            withMockServer { server ->21                val validJsonResponses: Gen<String> = Exhaustive.collection(22                    setOf(23                        """24            {"coord":{"lon":-0.13,"lat":51.51},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03n"}],"base":"stations","main":{"temp":279.48,"feels_like":276.56,"temp_min":278.71,"temp_max":280.37,"pressure":1017,"humidity":81},"visibility":10000,"wind":{"speed":2.1,"deg":50},"clouds":{"all":40},"dt":1606501853,"sys":{"type":1,"id":1414,"country":"GB","sunrise":1606462727,"sunset":1606492689},"timezone":0,"id":2643743,"name":"London","cod":200}25        """,26                        """27            {"coord":{"lon":147.22,"lat":-9.52},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03n"}],"base":"stations","main":{"temp":296.15,"feels_like":298.32,"temp_min":296.15,"temp_max":296.15,"pressure":1007,"humidity":94},"visibility":10000,"wind":{"speed":3.6,"deg":320},"clouds":{"all":40},"dt":1606503876,"sys":{"type":1,"id":42,"country":"PG","sunrise":1606506038,"sunset":1606551473},"timezone":36000,"id":2088122,"name":"Pari","cod":200}28        """,29                        """30            {"coord":{"lon":-10.59,"lat":6.65},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03n"}],"base":"stations","main":{"temp":301.15,"feels_like":306.42,"temp_min":301.15,"temp_max":301.15,"pressure":1010,"humidity":83},"visibility":10000,"wind":{"speed":1.5,"deg":190},"clouds":{"all":40},"dt":1606503899,"sys":{"type":1,"id":2389,"country":"LR","sunrise":1606459026,"sunset":1606501412},"timezone":0,"id":2274890,"name":"New","cod":200}31        """32                    ).map { it.trimIndent() }33                )34                val retrofit = Retrofit.Builder()35                    .addConverterFactory(Json {36                        ignoreUnknownKeys = true37                    }.asConverterFactory("application/json".toMediaType()))38                    .baseUrl(server.url("/"))39                    .build()40                val weatherApi: WeatherApi = retrofit.create()41                checkAll(validJsonResponses) { body ->42                    server.dispatcher = object : Dispatcher() {43                        override fun dispatch(request: RecordedRequest): MockResponse =44                            when (request.requestUrl?.encodedPath) {45                                "/data/2.5/weather" -> MockResponse().setResponseCode(200)46                                    .setBody(body)47                                else -> MockResponse().setResponseCode(404)48                            }49                    }50                    weatherApi.fetchByCityName(city = "anyCity") shouldBeIn listOf(51                        WeatherResponse(2643743, "London"),52                        WeatherResponse(2088122, "Pari"),53                        WeatherResponse(2274890, "New"),54                    )55                }56            }57        }58    }59}...NotePitchTest.kt
Source:NotePitchTest.kt  
1package io.loskunos.midi2import io.kotest.data.forAll3import io.kotest.data.row4import io.kotest.matchers.collections.shouldContainExactly5import io.kotest.matchers.shouldBe6import io.kotest.property.Arb7import io.kotest.property.Exhaustive8import io.kotest.property.arbitrary.element9import io.kotest.property.arbitrary.list10import io.kotest.property.checkAll11import io.kotest.property.exhaustive.collection12import io.kotest.property.exhaustive.enum13import io.loskunos.midi.Octave.Companion.o014import io.loskunos.midi.Octave.Companion.o115import io.loskunos.midi.Octave.Companion.o216import io.loskunos.midi.Octave.Companion.o317import io.loskunos.midi.Octave.Companion.o418import io.loskunos.midi.Octave.Companion.o519import io.loskunos.midi.Octave.Companion.o620import io.loskunos.midi.Octave.Companion.o721import io.loskunos.midi.Octave.Companion.o822import io.loskunos.midi.PitchClass.C23import kotlinx.coroutines.runBlocking24import org.junit.jupiter.api.Nested25import org.junit.jupiter.api.Test26class NotePitchTest {27    private val allPitchClassInstances = PitchClass::class.sealedSubclasses.map { it.objectInstance!! }28    @Nested29    inner class PitchClassToNotePitch {30        @Test31        fun `should be the same note`() {32            runBlocking {33                checkAll(Exhaustive.collection(allPitchClassInstances)) { pitchClass ->34                    pitchClass.octave(o1).pitchClass shouldBe pitchClass35                }36            }37        }38        @Test39        fun `should have given octave`() {40            runBlocking {41                checkAll(Exhaustive.enum<Octave>()) { givenOctave ->42                    C.octave(givenOctave) shouldBe NotePitch(C, givenOctave)43                }44            }45        }46    }47    @Test48    fun `adding octave to a list of PitchClasses returns a list of NotePitches with correct octave`() {49        runBlocking {50            checkAll(iterations = 10, Arb.list(Arb.element(allPitchClassInstances), range = 0..10)) { pitchClasses ->51                checkAll(Exhaustive.enum<Octave>()) { givenOctave ->52                    forAll(53                        row { octave(givenOctave, *pitchClasses.toTypedArray()) },54                        row { octave(givenOctave) { pitchClasses } },55                        row { pitchClasses.octave(givenOctave) }56                    ) { octaveFun ->57                        val result = octaveFun()58                        result.map { it.octave }.forEach { it shouldBe givenOctave }59                        result.map { it.pitchClass } shouldContainExactly pitchClasses60                    }61                }62            }63        }64    }65    @Test66    fun `o0-o8 functions should return NotePitch with correct octave`() {67        C.o0.octave shouldBe o068        C.o1.octave shouldBe o169        C.o2.octave shouldBe o270        C.o3.octave shouldBe o371        C.o4.octave shouldBe o472        C.o5.octave shouldBe o573        C.o6.octave shouldBe o674        C.o7.octave shouldBe o775        C.o8.octave shouldBe o876    }77}...Exhaustive.Companion.collection
Using AI Code Generation
1val collection = Exhaustive.collection(listOf(1, 2, 3))2collection.values.forEach { println(it) }3val intRange = Exhaustive.intRange(1..3)4intRange.values.forEach { println(it) }5val longRange = Exhaustive.longRange(1L..3L)6longRange.values.forEach { println(it) }7val charRange = Exhaustive.charRange('a'..'c')8charRange.values.forEach { println(it) }9val byteRange = Exhaustive.byteRange(1.toByte()..3.toByte())10byteRange.values.forEach { println(it) }11val shortRange = Exhaustive.shortRange(1.toShort()..3.toShort())12shortRange.values.forEach { println(it) }13val doubleRange = Exhaustive.doubleRange(1.0..3.0)14doubleRange.values.forEach { println(it) }15val floatRange = Exhaustive.floatRange(1.0f..3.0f)16floatRange.values.forEach { println(it) }17val boolean = Exhaustive.boolean()18boolean.values.forEach { println(it) }19val string = Exhaustive.string()20string.values.forEach { println(it) }21val char = Exhaustive.char()22char.values.forEach { println(it) }23val byte = Exhaustive.byte()Exhaustive.Companion.collection
Using AI Code Generation
1val exhaustive = Exhaustive.collection(listOf(1, 2, 3, 4))2exhaustive.values().forEach { println(it) }3val exhaustive = Exhaustive.ints(1, 10)4exhaustive.values().forEach { println(it) }5val exhaustive = Exhaustive.longs(1, 10)6exhaustive.values().forEach { println(it) }7val exhaustive = Exhaustive.strings(1, 10)8exhaustive.values().forEach { println(it) }9val exhaustive = Exhaustive.booleans()10exhaustive.values().forEach { println(it) }11val exhaustive = Exhaustive.doubles(1.0, 10.0)12exhaustive.values().forEach { println(it) }13val exhaustive = Exhaustive.floats(1.0f, 10.0f)14exhaustive.values().forEach { println(it) }15val exhaustive = Exhaustive.bytes(1, 10)16exhaustive.values().forEach { println(it) }17val exhaustive = Exhaustive.chars('a', 'z')18exhaustive.values().forEach { println(it) }19val exhaustive = Exhaustive.shorts(1, 10)20exhaustive.values().forEach { println(it) }21val exhaustive = Exhaustive.bigInts(1, 10)22exhaustive.values().forEach { println(itExhaustive.Companion.collection
Using AI Code Generation
1val exhaustive = collection(listOf("A", "B", "C"))2exhaustive.values should containExactlyInAnyOrder("A", "B", "C")3val exhaustive = set(setOf("A", "B", "C"))4exhaustive.values should containExactlyInAnyOrder("A", "B", "C")5val exhaustive = map(mapOf("A" to 1, "B" to 2, "C" to 3))6exhaustive.values should containExactlyInAnyOrder("A" to 1, "B" to 2, "C" to 3)7val exhaustive = byte(Byte.MIN_VALUE..Byte.MAX_VALUE)8exhaustive.values should containExactlyInAnyOrder(Byte.MIN_VALUE, Byte.MAX_VALUE)9val exhaustive = char(Char.MIN_VALUE..Char.MAX_VALUE)10exhaustive.values should containExactlyInAnyOrder(Char.MIN_VALUE, Char.MAX_VALUE)11val exhaustive = double(Double.MIN_VALUE..Double.MAX_VALUE)12exhaustive.values should containExactlyInAnyOrder(Double.MIN_VALUE, Double.MAX_VALUE)13val exhaustive = float(Float.MIN_VALUE..Float.MAX_VALUE)14exhaustive.values should containExactlyInAnyOrder(Float.MIN_VALUE, Float.MAX_VALUE)15val exhaustive = int(Int.MIN_VALUE..Int.MAX_VALUE)16exhaustive.values should containExactlyInAnyOrder(Int.MIN_VALUE, Int.MAX_VALUE)17val exhaustive = long(Long.MIN_VALUE..Long.MAX_VALUE)18exhaustive.values should containExactlyInAnyOrder(Long.MIN_VALUE, Long.MAX_VALUE)Exhaustive.Companion.collection
Using AI Code Generation
1collection shouldBe listOf(2listOf("a"),3listOf("b"),4listOf("c"),5listOf("a", "b"),6listOf("a", "c"),7listOf("b", "c"),8listOf("a", "b", "c")9intRange shouldBe listOf(10longRange shouldBe listOf(Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!
